持久化的数据存储 - mysql

699 阅读19分钟

普通文件和数据库存储的区别

在大多数企业开发或web开发中,都会社会数据的存储和检索,存储数据有两种基本方法

  • 普通文件(File System)
  • 数据库(Database)

文件存储常见,简单,操作系统提供完善的API,所以在早期项目中都会使用文件存储载体,但是顺着企业业务越来越复杂,网站访问量也越来越大时,对数据的并发性和检索速度有更高的要求,所以慢慢的也就引入使用数据库作为数据存储了

  • 文件系统用文件来保存,不易共享 / 数据库系统用数据库存储数据
  • 文件系统中的程序(代码)和数据有一定的联系 / 数据库系统中的程序和数据分离
  • 文件系统没有锁的概念,不能加密锁定 / 数据库系统数据安全

数据库分类

  • 非关系型数据库 — json文档对象之间的关系
    • mongodb
  • 关系型数据库 — 表格和表格之间的关系
    • MySQL

相对来说Node.js更适合操作非关系型数据库,更多的采用mongodb,存取中小型数据

Node.js也可以操作MySQL数据库,相对mongodb,MySQL更适合存储大型数据库

MySQL安装

群辉nas安装

群晖 docker 安装 mysql - 简书 (jianshu.com)

  1. 打开 docker,搜索 mysql,选择版本下载映像

  2. 在 File Station → docker文件夹下,新建一个mysql文件夹,内部新建文件夹命名数据库

  3. 启动映像,命名数据库名称,高级设置 →

    存储空间 → 添加文件夹,添加刚创建好的目录

    image-20211007154542545

    端口设置 → 与容器端口保持一致,如果端口冲突,改成其他端口

    image-20211007154608634

    环境 新增 MYSQL_ROOT_PASSWORD 密码

    image-20211007154741090
  4. 应用 → 下一步 → 完成,查看容器,数据库已经运行中

    image-20211007155221203

windows 安装

下载 MySQL :: Download MySQL Community Server (Archived Versions)

安装 MySql安装和基本管理

略过....

sql语句操作

打开容器 → 终端机 → 新增 → bash 内输入 mysqld

image-20211007164753699
  • 进入数据库

bash 输入命令 mysql -u root -p ,提示输入密码,注意输入密码是隐藏的,没有字符显示,输入完成后回车就可以了

-u 为 user ,root 为根用户,-p 是 password

image-20211007165605097

登录完成后,就进入了mysql的解释器,在这里做相应的事情

基本的mysql语句 语句末尾分号不能丢

  • 查看本机上数据库的数量
image-20211007170841711
  • 创建新的数据库

db1 是数据库名字,charset utf8 是设置编码格式为 utf-8

image-20211007171820635
  • 打开对应数据库
image-20211007172105613
  • 创建一个表

创建的表 表头,id为int类型,name为varchar类型限制20个字符

image-20211007172405313
  • 查看当前数据库有多少个表
image-20211007172752343
  • 查看当前表
image-20211007172918351
  • 当前表插入一条数据

在s1的 id 和 name位置 插入值为 1 和 xxx

image-20211007173307824
  • 查看当前表内的数据
image-20211007173534661
  • 插入多条数据
image-20211007173755536

select * from s1 查看

image-20211007173905108
  • 查询指定id
image-20211007174113013

Navicat 可视化工具使用

下载 Navicat 可视化数据库管理开发

image-20211007160323676

点击左上角连接 → 输入Nas ip 和端口号,点击测试连接,提示连接成功,确定

image-20211007161031825

右键连接名称,创建新数据库

image-20211007175625155

双击数据库打开,表右键,新建表

image-20211007191301439
  • 点击添加字段,添加表头
  • 不是null勾选,则不能为空
  • 设置键,则这里的值是唯一的
  • 默认填写为空时的默认值
  • 自动递增,当没有指定id时,该值自动递增
  • 一个表中只能有一个id

ctrl + s 保存 → 输入表名保存

打开表下保存的表 左下角加号添加一条数据,ctrl + s 保存这条数据,id因为设置为自动递增,不填写id只填写name保存,id会自动 +1

image-20211007192350414

点击查询 → 新建查询 → 输入mysql语句,点击右侧运行,就会得到运行结果,# 注释

image-20211007192947442image-20211007194237267

Node.js 原生驱动链接 MySQL

创建服务器

nodejs监听mysql端口 — nodejs中mysql用法

初始化项目 和 安装mysql模块

npm init --yes
npm i mysql -s

创建server.js导入mysql

// 引入模块
const mysql = require('mysql')

// 创建连接对象
const conn = mysql.createConnection({
  host: '192.168.31.107',
  port: '3307',
  user: 'root',
  password: '601109',
  database: 'db1'
})

// 调用connect方法连接数据库
conn.connect(err => {
  if(err) throw err;
  console.log('连接成功');
})

// 增删改查------------------------------------------


// 一定要有这个断开链接的操作,不然会消耗多余性能
conn.end()

运行服务器 nodemon server.js

image-20211007202104176

编写命令行

sql语句可以在Navicat内编写测试,新建查询输入命令行后,点击运行,测试语句

这条语句运行完成,表内F5刷新,就可以看到新创建的表

image-20211008103252682

以及对表的增加、查询、删除语句

image-20211008220546346

接下来在Node.js内原生驱动,对数据库进行增删改查

创建表

// 增删改查------------------------------------------

// 声明语句
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test(id INT NOT NULL PRIMARY KEY auto_increment,name VARCHAR(20))`
// 第一个参数为sql语句,第二个参数为回调函数
conn.query(CREATE_SQL,(error,results,fields) => {
  if(error) throw error
  console.log(results)
})

运行后,打印results结果如下

OkPacket {
  fieldCount: 0,
  affectedRows: 0,
  insertId: 0,
  serverStatus: 2,
  warningCount: 1,
  message: '',
  protocol41: true,
  changedRows: 0
}

回到可视化工具刷新,新的表就被创建好了

创建表后增加一条或多条数据

前端调用后端接口,后端传入请求体数据来获取服务区数据, ? 表示一个占位符

如果需要传入请求体数据,可以在query函数的sql语句后,接收一个数组

// 增删改查------------------------------------------
// 声明语句
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test(id INT NOT NULL PRIMARY KEY auto_increment,name VARCHAR(20))`
const INSERT_SQL = `INSERT INTO test(name) VALUE(?),(?)`
// 第一个参数为sql语句,第二个参数为回调函数
conn.query(CREATE_SQL,(error,results,fields) => {
  if(error) throw error
  conn.query(INSERT_SQL,['Max','Min'],(error,results,fields) => {
    if(error) throw error
    console.log(results)
    // 当使用query嵌套,end()方法要放到最后一个query内
    conn.end()
  })
})

运行后,打印results结果如下

OkPacket {
  fieldCount: 0, // 字段数
  affectedRows: 1, // 受影响的行1,在表的第一行
  insertId: 5, // 插入的id
  serverStatus: 2, // 服务器状态
  warningCount: 0, // 警告计数
  message: '', 
  protocol41: true, // 协议41...
  changedRows: 0 // 改变的行
}
  affectedRows: 2,
  insertId: 6,
  serverStatus: 2,
  warningCount: 0,
  message: '&Records: 2  Duplicates: 0  Warnings: 0', // 记录:2 重复:0 警告:0 
  protocol41: true,
  changedRows: 0
}

查询数据

const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test(id INT NOT NULL PRIMARY KEY auto_increment,name VARCHAR(20))`
const INSERT_SQL = `INSERT INTO test(name) VALUE(?),(?)`
const SELECT_SQL = `SELECT * FROM test WHERE id=?`
conn.query(CREATE_SQL,(error,results,fields) => {
  if(error) throw error
  conn.query(INSERT_SQL,['Max','Min'],(error,results,fields) => {
    if(error) throw error
    // 继续嵌套查询语句
    conn.query(SELECT_SQL,[6], (error,results,fields) => {
      if(error) throw error
      // 因为取到的数据是个数组,而这里只取出了一条数据,所以加了[0]
      console.log(results[0]);
      conn.end()
    })
  })
})

打印结果

RowDataPacket { id: 6, name: 'Max' }

如果需要返回的结果为name,则 results[0].name

删除数据

const DELETE_SQL = `DELETE FROM test WHERE id=?`

conn.query(DELETE_SQL,[9],(error,results) => {
  if(error) throw error
  console.log(results);
  conn.end()
})
OkPacket {
  fieldCount: 0,
  affectedRows: 1,
  insertId: 0,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0
}

封装mysql的query方法方便操作

多层嵌套的query对同一个表进行多次的操作容易形成回调地狱

回调地狱: 就是为是实现回调函数代码顺序执行而出现的一种操作,它会造成代码可读性非常差,后期不好维护

我们不希望项目的配置对象、方法操作 和 业务代码关联性非常高,需要解耦,将mysql的配置文件和操作函数从server.js中分离出来

image-20211009005055022

Promise方法

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易,Promise 构造函数接收一个回调函数作为参数,这个回调函数接收两个参数,分别为promise的resolve(成功) rejecte(失败) 两个函数

const sql = mysql.format( ) format()是mysql的格式化方法,传入两个参数,第一个为期待参数的sql语句,第二个为需要传入的参数,返回一个包含参数的sql语句

const UPDATE_SQL = 'UPDATE posts SET modified = ? WHERE ID = ?'
const params = [CURRNT_TIMESTAMP, 42]
const sql = mysql.format(UPDATE_SQL, params)
console.log(sql) // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42

mysqlConfig.js

module.exports = {
  host: '192.168.31.107',
  port: '3307',
  user: 'root',
  password: '601109',
  database: 'db1'
}

mysql.js 数据库函数传入数据库配置

使用createConnection方法创建一个表示与mysql数据库服务器之间连接的connection对象,下方有连接池写法

// 引入mysql模块
const mysql = require('mysql')
// 引入数据库配置
const mysqlConfig = require('./mysqlConfig')

module.exports = {
  // 声明抛出对象的一个query方法,接收等待插入的语句和参数
  query: function (sql, params) {
    // 调用query后返回给await的,等待的是一个Promise对象
    return new Promise((resolve, reject) => {
      // 创建连接对象
      const conn = mysql.createConnection(mysqlConfig)
      conn.connect(err => {
        console.log('连接成功');
      })
      // format()接收传入的传参sql语句和sql参数数组,格式化,返回一个完整sql语句
      sql = mysql.format(sql,params)
      // 查询
      conn.query(sql,(err,results,fields) => {
        // 如果有错误,返回Promise对象的reject错误
        if(err) throw reject(err)
        // 如果连接成功并完成sql语句查询,将数据库返回的数据赋值给Promise对象
        resolve(results)
        // 停止连接数据库,必须在查询语句后
        conn.end()
      })
    })
  }
}

server.js 业务代码 声明语句,引入事先数据库函数

通过 async 声明函数,try内调用数据库函数并传入sql语句,await来等待异步函数返回的Promise对象,获得返回值res

// 声明语句
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test(id INT NOT NULL PRIMARY KEY auto_increment,name VARCHAR(20))`

const db = require('./db/mysql')

async function asyncQuery() {
  try {
    const res = await db.query(CREATE_SQL); // 创建test表
    console.log(res);
  } catch (err) {
    console.log(err)
  }
}

asyncQuery() // 上方声明了函数,下方调用

返回的结果,正常运行没有任何问题

连接成功
OkPacket {
  fieldCount: 0,
  affectedRows: 0,
  insertId: 0,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0
}

接下来逐步调用

const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test(id INT NOT NULL PRIMARY KEY auto_increment,name VARCHAR(20))`
const INSERT_SQL = `INSERT INTO test(name) VALUE(?),(?)`

const db = require('./db/mysql')

async function asyncQuery() {
  try {
    const res1 = await db.query(CREATE_SQL); // 创建test表
    const res2 = await db.query(INSERT_SQL,['Max','Min']) // 添加name为 Max 和 Min 两条数据
    console.log(res2);
  } catch (err) {
    console.log(err)
  }
}

asyncQuery()

获得结果

连接成功
OkPacket {
  fieldCount: 0,
  affectedRows: 2,
  insertId: 1,
  serverStatus: 2,
  warningCount: 0,
  message: '&Records: 2  Duplicates: 0  Warnings: 0',
  protocol41: true,
  changedRows: 0
}

查询操作

const SELECT_SQL = `SELECT * FROM test WHERE id=?`

const db = require('./db/mysql')

async function asyncQuery() {
  try {
    const res3 = await db.query(SELECT_SQL,[1]) // 查询id为1的一条数据
    console.log(res3);
  } catch (err) {
    console.log(err)
  }
}

asyncQuery()

打印结果

[ RowDataPacket { id: 1, name: 'Max' } ]

删除操作

const DELETE_SQL = `DELETE FROM test WHERE id=?`

const db = require('./db/mysql')

async function asyncQuery() {
  try {
    const res4 = await db.query(DELETE_SQL,[1]) // 删除id为1的一条数据
    console.log(res4);
  } catch (err) {
    console.log(err)
  }
}

asyncQuery()

打印结果

OkPacket {
  fieldCount: 0,
  affectedRows: 1,
  insertId: 0,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0
}

连接池

在开发web应用程序时,连接池是一个很重要的概念。建立一个数据库连接所消耗的性能成本是很高的。在服务器应用程序中,如果为每一个接收到的客户端请求都建立一个或多个数据库连接,将严重降低应用程序性能。

因此在服务器应用程序中通常需要为多个数据库连接创建并维护一个连接池,当连接不再需要时,这些连接可以缓存在连接池中,当接收到下一个客户端请求时,从连接池中取出连接并重新利用,而不需要再重新建立连接。

// 引入mysql模块
const mysql = require("mysql");
// 引入数据库配置

const pool = mysql.createPool({
  host: "192.168.31.107",
  port: "3307",
  user: "root",
  password: "601109",
  database: "db1",
})

module.exports = {
  query: function (sql, params) {
    return new Promise((resolve, reject) => {
      pool.getConnection((err, connection) => {
        if (err) {
          if (err.errno == "ECONNREFUSED") {
            console.log("数据库连接失败,请确认你的mysql服务是否已启动")
          } else {
            reject(err);
          }
        } else {
          sql = mysql.format(sql, params);

          connection.query(sql, (err, results, fields) => {
            if (err) {
              if (err.errno == 1055) {
                console.log("数据库查询失败,请检查mysql的配置")
              } else {
                reject(err)
              }
            } else {
              resolve(results)
            }
            connection.release()
          })
        }
      })
    })
  }
}

连接池问题

在mysql模块中,使用createPool方法创建连接池,在建立了连接池之后,可以直接使用连接池对象的getConnection方法从连接池中获取一个连接,如果连接池中没有可用连接,将隐式的建立一个数据库连接

1.connection.release() 当一个连接不需要使用时,使用该方法将其归还到连接池中

2.connection.destroy() 当一个连接不需要使用且需要从连接池中移除时,可以使用该方法

3.pool.end() 当一个连接池不需要使用时,可以使用该方法关闭连接池

注意:可以使用createPool方法的属性connectionLimit设置连接池中的最大连接数,默认为10

以上链接池方法对错误信息进行了两次判断处理,所以显得代码有点多,去掉错误判断就简单了

module.exports = {
  // 声明抛出对象的query方法,并等待传入语句和参数
  query: function (sql, params) {
    // 返回一个Promise对象
    return new Promise((resolve, reject) => {
      // 连接数据库,如果成功则返回一个数据库连接对象connection
      pool.getConnection((err, connection) => {
        if (err) {
          console.loog(err)
        } else {
          // 连接成功则调用format格式化sql语句
          sql = mysql.format(sql, params);
          // 数据库连接对象传入完整的sql语句,通过回调函数接收错误或者结果
          connection.query(sql, (err, results, fields) => {
            if (err) {
               reject(err)
            } else {
              // 如果返回结果则通过resolve将结果返回给Promise对象
              resolve(results)
            }
            connection.release()
          })
        }
      })
    })
  }
}

封装思想

对当前框架上的方法,重复的调用操作进行包装,包装成async await 的方法进行调用,将需要频繁重复使用的代码封装为一个模块引用,简单一行代码调用来完成操作

ORM框架Sequelize的基本使用和数据类型介绍

对象关系映射 (Object Relational Mapping,简称ORM) 是通过使用描述对象和数据至今映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中

概述

Sequelize.js 是一款基于 Promise 的针对 node.js 的 ORM 框架,具体就是突出一个支持广泛,配置和查询方法统一,它支持关系型数据库,包括:PostgreSQL、MySQL、MariaDB、SQLite 和 MSSQL

为什么选择它?

使用node.js连接过数据库的人肯定对数据库不陌生了,如果是直接连接,需要自己建立并管理连接,还需要手动编写sql语句,简单的项目倒无所谓,可是一档项目设计的东西比较复杂,表比较多的时候整个sql的编写就非常消耗精力

在java 和 c# 等语言中已经有轻量的数据库框架或者解决方案了,在node.js中则推荐Sequellize.js,它是一个很成熟的框架,在速度和性能上也非常有优势,而其中最关键的地方在于,日常开发只需要管理对象的创建、查询方法的调用即可,极少需要编写sql语句,这一个好处就省去了复杂的sql语句维护,同时也避免了因sql而引起的不必要的 bug

安装

Sequelize 中文网

npm init --yes
npm i sequelize mysql2 -s

配置并定义表模型

server.js

// 引入模块
const Sequelize = require("sequelize");

// 建立连接并返回sequelize对象
const sequelize = new Sequelize("db1", "root", "123456", {
  host: "192.168.31.107",
  port: "3306",
  dialect: "mysql",
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
});

// 定义模型 返回一个表实例
const books = sequelize.define(
  "books", // 第一个参数接收字符串为定义表名
  // 第二个参数为一个对象,定义表中的字段,对象中的属性
  {
    id: {
      type: Sequelize.INTEGER, // 定义数据类型为整数
      primaryKey: true, // 设置主键
      autoIncrement: true, // 自动递增
      comment: "自增id", // 注释
    },
    name: {
      type: Sequelize.STRING,
      allowNull: false, // 不允许为空 默认为true
    },
    price: {
      type: Sequelize.FLOAT,
      allowNull: false,
    },
    count: {
      type: Sequelize.INTEGER,
      defaultValue: 0,
    },
  },
  {
    // 相关配置
    timestamps: false, // 指定是否创建createAt和updatedAt字段,默认为true
    freezTabelName: true // 表名冻结,Model对应的表名将于model名相同,默认为true
  }
)

// 将模型与数据库同步
// 如果希望Sequelize根据刚定义的模型自动创建表(或根据需要进行修改),可以使用sync方法
// 如果没有books表就创建,有的话就删除后重建
books.sync({force: true}).then(() => {
  
})

image-20211009193624965

终端显示警告信息 (node:12324) [SEQUELIZE0006] 弃用警告:不支持此数据库引擎版本,请更新您的数据库服务器,下方为被执行的sql语句

数据库表刷新,表就被创建了

image-20211009193514580

可以看到后面 createdAt 和 updatedAt 并不是上面定义的字段,不需要的话就可以配置定义模型的第三个参数,timestamps: false,上方已经配置,再次运行将不会有这个两个字段

sequelize数据类型

Sequelize.STRING                      // VARCHAR(255)                  类型:字符串 最大值: 65535个字符
Sequelize.STRING(1234)                // VARCHAR(1234)                 类型:变长 最大值: 65535个字符
Sequelize.TEXT                        // TEXT                          类型:字符串 最大值:65535个字符
Sequelize.TEXT('tiny')                // TINYTEXT                      类型:字符串 最大值:255个字符

Sequelize.INTEGER                     // INTEGER                       类型:整型 最大值:范围(-2147483648~2147483647)
Sequelize.BIGINT                      // BIGINT                        类型:整型 最大值:范围(+-9.22*10的18次方)
Sequelize.BIGINT(11)                  // BIGINT(11)                    类型:整型 最大值:范围(+-9.22*10的18次方)

Sequelize.FLOAT                       // FLOAT                         类型:单精度浮点型  8位精度(4字节)
Sequelize.FLOAT(11)                   // FLOAT(11)                     类型:单精度浮点型 8位精度(4字节)
Sequelize.FLOAT(11, 12)               // FLOAT(11,12)                  类型:精度浮点型 8位精度(4字节) m总个数,d小数位


Sequelize.DOUBLE                      // DOUBLE                        类型:双精度浮点型 16位精度(8字节) 
Sequelize.DOUBLE(11)                  // DOUBLE(11)                    类型:双精度浮点型 16位精度(8字节) 
Sequelize.DOUBLE(11, 12)              // DOUBLE(11,12)                 类型:双精度浮点型 16位精度(8字节) m总个数,d小数位

Sequelize.DECIMAL                     // DECIMAL                       类型:定点数型
Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)                 类型:定点数型 参数m<65 是总个数,d<30且 d<m 是小数位

Sequelize.DATE                        // DATETIME                      类型:日期时间类型 范例:'2009-05-12 02:31:44'
Sequelize.DATE(6)                     // DATETIME(6)    
Sequelize.DATEONLY                    // DATE without time.
Sequelize.BOOLEAN                     // TINYINT(1)                    类型:整型 范围(-128~127)

Sequelize.ENUM('value 1', 'value 2')  // ENUM                          类型:枚举

Sequelize.BLOB                        // BLOB                          类型:二进制数据
Sequelize.BLOB('tiny')                // TINYBLOB                      类型:二进制数据  

插入数据

对定义的books模型调用sync方法创建表 ( 或根据需要对表进行修改 ),通过then来处理需要添加的数据

通过 books.create({ }) 传入一个对象添加这条数据

**force: true:**如果books表存在的话就删除后重建,再写入数据,除非必要,不写

books.sync({force: true}).then(() => {
  // sequelize是基于Promise的封装,这里需要返回一个Promise,通过then来处理
  return books.create({
    name: 'javascript',
    price: 12.9,
    count: 10
  })
})

刷新数据库,这条数据就插入完成了

image-20211009221537406

插入多条数据

上方插入一条数据用的是create({ })

插入多条数据用的是bulkCreae([{ }, { }])

books.sync({ force: true }).then(() => {
  return books.bulkCreate([
    {
      name: "Vue3",
      price: 20.8,
      count: 18,
    },
    {
      name: "React",
      price: 34.8,
      count: 30,
    },
    {
      name: "Node.js",
      price: 28,
      count: 45,
    },
  ]);
});

刷新数据库,就可以看到,因为 force: true 的关系,javascript不存在,表被覆盖重写了,所以除非必要,不要写这个就可以了

image-20211009222223532

查询

模型查询(基础) | Sequelize 中文网

查询所有数据
返回整个表数据

调用findAll查询数据,返回的是js数组对象,如下面,需要调用JSON.stringify转换为JSON字符串

books.findAll().then(books => {
  console.log(JSON.stringify(books));
})
image-20211009231647360

终端第一行,是服务器打印的 Executing / 执行 后面是 sql语句

下方返回的才是请求得到的json数据

image-20211009231130447

可以设置 null, 4 来格式化一下数据便于查看

JSON.stringify(books,null,4)
[
    {
        "id": 1,
        "name": "Vue3",
        "price": 20.8,
        "count": 18
    },
    {
        "id": 2,
        "name": "React",
        "price": 34.8,
        "count": 30
    },
    {
        "id": 3,
        "name": "Node.js",
        "price": 28,
        "count": 45
    }
]

也可以通过 books[0].count 来获取第一个对象的count值,为18

传入查询条件

传入查询条件,限制count为30

books.findAll({
  where:{
    count: 30
  }
}).then(books => {
  console.log(JSON.stringify(books, null, 4));
})

打印结果

image-20211010003123451
修改

也可以通过赋值,更改数据库内的值,打印结果为100

books.findAll().then(books => {
  books[0].count = 100
  console.log(books[0].count); // 100
})
计算
  • 实例扩展

如果前端传过来50本,来计算总价,但是每次都这样做,如果是有复杂的计算,是很不方便的

books.findAll().then(books => {
  const totalPrice = books[0].price * 50
})

可以给实例扩充一个方法来进行计算

books.prototype.totalPrice = function(count) {
  return this.price * count
}

books.findAll().then(books => {
  const totalPrice = books[0].totalPrice(50)
  console.log(totalPrice); // 1040
})
  • 模型扩展
books.classify = function(name) {
  const frontEnd = ['Vue3', 'React']
  // 判断传进来的name,调用数组的includes(),判断传入的name是否包含在frountEnd数组内,如果是则返回前端框架
  return frontEnd.includes(name) ? '前端框架':'其他'
}
// 前端传来的数据,来查询这两个是不是前端框架
const arr = ['Vue3', 'Node.js']

// 对前端传来的数组调用forEach方法,分别传入每一个值,将每一个值传入模型的classify方法进行判断并返回结果
arr.forEach(name => {
  console.log(books.classify(name));
})

打印结果

前端框架
其他
查询一条数据
返回表第一条

运行下面的代码,findOne() 就会返回一条数据

books.findOne().then(book => {
  const res = JSON.stringify(book,null,4)
  console.log(res);
})
{
    "id": 1,
    "name": "Vue3",
    "price": 20.8,
    "count": 18
}

可以发现这是整个表格的第一条数据

传入查询条件

需要加入条件的话,在方法内传入对象来限定

books.findOne({
  where:{
    id: 3
  }
}).then(book => {
  const res = JSON.stringify(book,null,4)
  console.log(res);
})

打印

image-20211010003445807
查询条件操作符
const Op = Sequelize.Op

[Op.and]: {a: 5}           // 且 (a = 5)
[Op.or]: [{a: 5}, {a: 6}]  // (a = 5 或 a = 6)
[Op.gt]: 6,                // id > 6
[Op.gte]: 6,               // id >= 6
[Op.lt]: 10,               // id < 10
[Op.lte]: 10,              // id <= 10
[Op.ne]: 20,               // id != 20
[Op.eq]: 3,                // = 3
[Op.not]: true,            // 不是 TRUE
[Op.between]: [6, 10],     // 在 6 和 10 之间
[Op.notBetween]: [11, 15], // 不在 11 和 15 之间
[Op.in]: [1, 2],           // 在 [1, 2] 之中
[Op.notIn]: [1, 2],        // 不在 [1, 2] 之中
[Op.like]: '%hat',         // 包含 '%hat'
[Op.notLike]: '%hat'       // 不包含 '%hat'
[Op.iLike]: '%hat'         // 包含 '%hat' (不区分大小写)  (仅限 PG)
[Op.notILike]: '%hat'      // 不包含 '%hat'  (仅限 PG)
[Op.regexp]: '^[h|a|t]'    // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.iRegexp]: '^[h|a|t]'    // ~* '^[h|a|t]' (仅限 PG)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG)
[Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike
[Op.overlap]: [1, 2]       // && [1, 2] (PG数组重叠运算符)
[Op.contains]: [1, 2]      // @> [1, 2] (PG数组包含运算符)
[Op.contained]: [1, 2]     // <@ [1, 2] (PG数组包含于运算符)
[Op.any]: [2,3]            // 任何数组[2, 3]::INTEGER (仅限PG)
[Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG

如果想要查询price大于22的

// js上方获取到Op实例
const Op = Sequelize.Op


books.findAll({
  where:{
    price: {
      [Op.gt]: 22
    }
  }
}).then(books => {
  console.log(JSON.stringify(books, null, 4));
})

打印结果

image-20211010005439566
排序

模型查询(基础) | 排序

需要一个数组或者sequelize方法来对order进行配置,来对查询结果进行排序

books.findAll({
  where:{
    price: {
      // 价格大于22的
      [Op.gt]: 22
    },
  },
  order:[
    ['id', 'DESC'] // 根据价格倒序
  ]
}).then(books => {
  console.log(JSON.stringify(books, null, 4));
})

顺序被颠倒了

image-20211010010938611
限制条数和分页

模型查询(基础) | 限制和分页

只传出4条数据 (总数据被我事先加到来了30条)

books.findAll({
  where:{
    price: {
      // 价格大于18的
      [Op.gt]: 15
    },
  },
  order:[
    ['id', 'DESC'] // 根据价格倒序
  ],
  limit: 4
}).then(books => {
  console.log(JSON.stringify(books, null, 4));
})
image-20211010011554897
// 提取10个实例/行
Project.findAll({ limit: 10 });

// 跳过8个实例/行
Project.findAll({ offset: 8 });

// 跳过5个实例,然后获取5个实例
Project.findAll({ offset: 5, limit: 5 });
只获取部分属性
books.findAll({
  where:{
    price: {
      // 价格大于22的
      [Op.gt]: 22
    },
  },
  order:[
    ['id', 'DESC'] // 根据价格倒序
  ],
  limit: 4,
  attributes: ['name','price'] // 只获取 name 和 price 属性
}).then(books => {
  console.log(JSON.stringify(books, null, 4));
})

打印结果

image-20211010011944677
获取这个属性以外的属性
attributes: {
  exclude: ['id']
}
实用方法
模型查询(基础) | 实用方法
bookks.count('id').then( counts => {
  console.log(couunts)
})

返回数据库内id字段的总数 27

// 查询books表的数据总条数
books.count().then(counts=>console.log(counts))

//查询books表name为Vue3的总条数 
books.count({
  where: {
    name: 'Vue3'
  }
}).then(counts=>console.log(counts))

// 返回最大值,因为数据类型为浮点 34.79999923706055
books.max('price').then(maxPrice=>console.log(maxPrice))

// 返回最大值 15
books.min('price').then(minPrice=>console.log(minPrice))

// 返回总和 837
books.sum('count').then(allCount=>console.log(allCount))

// 计算name为Vue3的count总和 9个Vue3 * 18个 = 总共162
books.sum('count',{
  where: {
    name: 'Vue3'
  }
}).then(allCount=>console.log(allCount))

更新

更新id为1的商品价格为50,返回 [ 1 ]

books.update(
  {
    price: 50
  },
  {
    where: {
      id: 1
    }
  }
).then(res => {
  console.log(res);
})

更新 name 为 Vue3 的数据价格为22,返回 [ 9 ]

books.update(
  {
    price: 22
  },
  {
    where: {
      name: 'Vue3'
    }
  }
).then(res => {
  console.log(res);
})

得知 返回值为被更新的数据条数

删除

删除id为10的数据,返回 [ 1 ],同上表示被删除的条数

books.destroy({
  where: {
    id: 10
  }
}).then(res => {
  console.log(res);
})