Skip to content

Latest commit

 

History

History
284 lines (247 loc) · 9.52 KB

第4章:数据持久化.md

File metadata and controls

284 lines (247 loc) · 9.52 KB

第4章:数据持久化

数据存放在哪里

现在我们希望每次添加的todo都被记录下来,那么数据就要永久的保存下来。你可以将数据保存在一个文本文档中,然后每次存取,查询,修改的时候都对它进行修改。随着技术的发展,人们发现针对存取读写以及相关功能(例如权限管理)进行了一系列优化,这就是数据库。

数据库可以视为电子化的文件柜。它们能被多个用户共享,与应用程序彼此独立。

现代Web应用的数据一般都存放在数据库中。


数据库

数据库分为两种:关系型数据库和非关系型数据库。

关系型数据库也称SQL数据库,存储的方式类似于表格。 一个数据库包含一个或多个是以来组织的。

常用的免费关系型数据库有MySQLSQLite等。

关系型数据库的缺点

关系型数据库的最大缺点是关系模型和内存中的数据结构之间存在差异,把数据组织成表和行使得行中的值必须要足够简单,不能有嵌套、列表或数组这样的结构存在,如果想要实现这种结构,就要将其转换为关系的形式,这种现象称为‘抗阻失谐’。

非关系型数据库

非关系型数据库(NoSQL)是指一切不是SQL类型的数据库,因此两个NoSQL数据库之间的差距甚至可能大于SQL和NoSQL数据库的差距,这是因为各种NoSQL数据库被创造之初就是为了实现不同的目的。

例如Redis是一个简单的键值对数据库,它速度超群,支持多种数据类型,但是难以支持数据量增长很快的应用。很适合股票,数据分析,实时通讯这类应用。 而MongoDB适合动态查询,可以嵌入子文档,但是不支持事务操作,占用空间大。适合用于存储日志,博客类应用。

一个简单的对比

现在我们有一个结构,结构中有文章创建人评论三个关键字

关系型数据库中的存储

  • 表1_文章用户映射表
_id 内容 用户
1001 'hello world' 'admin'
  • 表2_文章评论用户表
_id 目标文章id 内容 评论人
1 1001 'Well done!' 'user01'

MongoDB中的存储

{
  _id: 1001,
  用户: 'admin',
  内容: 'hello world',
  评论: [
    {
      _id: 1,
      内容: 'Well done!',
      评论人: 'user01'
    }
  ]
}

对象关系映射和对象文档映射

对象关系映射(ORM)是一种技术手段,把程序中的对象和关系型数据库中的数据表连接起来。使用 ORM,程序中对象的属性和对象之间的关系可以通过一种简单的方法从数据库获取,无需直接编写SQL语句,也不过度依赖特定的数据库种类。

也就是ORM可以帮助我们在后端通过编写程序来操作数据库。

类似于对象关系映射是针对SQL数据库而言,对象文档映射(ODM)是针对文档类数据库(例如MongoDB)的,使用ODM可以让我们在后端环境中通过代码操作数据库。


用Mongoose存储你的数据

在阅读本节之前,你应该下载安装好MongoDB

MongoDB的基本概念

MongoDB是使用集合(collection)和文档(document)存储数据的数据库,一个数据库中有一个或多个集合,一个集合中有一个或多个文档。文档是以类似于JSON的格式来存储的,支持多种数据结构

一个典型的文档长这样:

{
  string: 'string',
  number: 1,
  boolean: true,
  array: [1,2,3,4,5],
  object: {
    name: 'name'
  }
}

想了解MongoDB的更多基础知识MongoDB,还有如何使用MongoDB的命令行。你可以去MongoDB中文文档阅读相关知识。

Mongoose是一个MongoDB的ODM,它允许我们在Node.js环境中直接操作MongoDB。

打开你的项目文件夹,开启命令行,输入

npm i mongoose

这样就会在项目目录下安装mongoose了,等它安装好。

Mongoose为我们提供了很多API,让我们便利的操作数据库。打开server.js文件,我们先进行数据库的连接:

//引入mongoose
const mongoose = require('mongoose')
/**
 * 连接数据库
 * dbuser: 用于登陆数据库的用户,默认为空
 * password: 用户的密码,默认为空
 * dbhost: 数据库服务的host,默认是localhost
 * dbport: 数据库服务的端口号,默认为27017
 * dbname: 要使用的数据库
 * 如果你的mongodb刚刚安装,括号里的内容可以为:
 * mongodb://localhost:27017/test
 */
const db = mongoose.connect('mongodb://dbuser:password@dbhost:dbport/dbname')
db.connection.on('open', ()=> {
  console.log('link to mongodb succeed')
})

MongoDB数据库默认是不需要用户名和密码的,如果有兴趣了解权限配置的相关知识,可以阅读我的一篇博客

运行服务器,如果你看到命令行打印出了'link to mongodb succeed'的字样,说明数据库的连接成功了

连接好数据库后,就可以进行数据库的读写操作了。

//声明存储的文档结构
const TodoSchema = new mongoose.Schema({
  todo: {type: String},
  done: {type: Boolean, default: false}
})
//创建文档的模型,这里的todo会在数据库中生成一个todos集合,收集创建的文档
const Todo = db.model('todo', TodoSchema)
//添加一个文档
new Todo({
  todo: 'test todo',
  done: 'false'
}).save((err, doc) => {
  if(err) {
    //如果有错误
    console.log(err)
  } else {
    //添加成功,打印添加的文档
    console.log(doc)
  }
})
//查询一个文档
Todo.where({todo: 'test todo'}).findOne((err, doc) => {
  if(err) {
    console.log(err)
  } else {
    console.log(doc)
  }
})
//查询多个文档
Todo.find((err, docs) => {
  if(err) {
    console.log(err)
  } else {
    //docs应该是一个数组
    console.log(docs)
  }
})
//删除一个文档
Todo.findOneAndRemove({todo: 'text todo'}, (err)=> {
  if(err) {
    console.log(err)
  } else {
    console.log('delete succeed')
  }
})
//删除多个文档
Todo.remove((err) => {
  if(err) {
    console.log(err)
  } else {
    console.log('all todos have been deleted')
  }
})

更多操作请到mongoose官方文档进行查阅。

现在,我们要把读写数据库的代码和之前的后端代码结合使用了

//引入http模块
const http = require('http')
//引入filesystem模块
const fs = require('fs')
//引入url模块
const url = require('url')
//引入path模块
const path = require('path')
//引入querystring模块
const querystring = require('querystring')
//引入mongoose
const mongoose = require('mongoose')
const db = mongoose.connect('mongodb://fsn:0000@localhost:27017/fsn')
//声明存储的文档结构
const TodoSchema = new mongoose.Schema({
  todo: {type: String},
  done: {type: Boolean, default: false}
})
const Todo = db.model('todo', TodoSchema)
//创建http服务器
http.createServer((req, res) => {
  const pathname = url.parse(req.url).pathname
  //获取请求的文件后缀
  const ext = path.extname(pathname).slice(1)
  if(ext) {
    switch(ext) {
      //如果后缀是js
      case 'js': {
        res.writeHead(200, {'Content-Type': 'text/javascript'})
        res.end(fs.readFileSync('./todo.js'))
        break
      }
      //如果后缀是css
      case 'css': {
        res.writeHead(200, {'Content-Type': 'text/css'})
        res.end(fs.readFileSync('./todo.css'))
        break
      }
    }
  } else {
    switch(pathname) {
      //请求为 localhost:3000/todo时
      case '/todo': {
        //若为post请求
        if(req.method === 'POST') {
          req.on('data', (msg) => {
            //将接受的信息解析为对象
            let data = querystring.parse(msg.toString())
            //创建新的todo添加到数据库中
            new Todo({
              todo: data.todo
            }).save((err, doc) => {
              if(err) {
                res.writeHead(502, {'Content-Type': 'text/plain'})
                res.end('can\'t add todo')
              } else {
                res.end(JSON.stringify(doc))
              }
            })
          })
        } else {
          //否则为get请求
          Todo.where().find((err, docs) => {
            if(err) {
              res.writeHead(502, {'Content-Type': 'text/plain'})
              res.end('can\'t find todo')
            } else {
              res.end(JSON.stringify(docs))
            }
          })
        }
        break
      }
      //其它情况
      default: {
        //使用fs模块读取todo.html文件
        res.write(fs.readFileSync('./todo.html'))
        res.end()
      }
    }
  }
}).listen(3000)

再打开todo.html,我们需要进行一些修改来查看效果

<!--将input放在form表单中-->
<form method="POST" action="/todo">
  <input id="input" name="todo" type="text" />
  <input id="submit" type="submit" value="添加" />
</form>
...
<!--注释掉js文件,让它不再生效-->
<!--<script src="todo.js"></script>-->

重启服务器,打开localhost:3000,添加一个todo,然后进入localhost:3000/todo查看效果。

如果可以看到刚才添加的todo的相关信息,说明数据已经被我们保存在数据库里了。