使用 Sequelize(ORM) + mysql2 操作 mysql

ORM 是关系对象映射,通过 ORM 框架,可以自动地把程序中的对象和数据库关联,只需要操作对象就可以修改数据库的数据,并且能轻易完成数据库的移植

安装

npm i sequelize
npm i mysql2

连接

连接数据库

创建 config 文件夹,在 config 中创建 db.js 文件用于放置数据库的配置项

// config/db.js
/**
 * mysql 数据库配置
 */

// 生产环境
let sqlConfig = {
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'books',
    port: 3306
}

// 本地环境
// process.env.NODE_ENV 取决于package.json里面的配置
if (process.env.NODE_ENV !== 'production') {
    let sqlConfig = {
        host: 'localhost',
        user: 'root',
        password: 'sql2008',
        database: 'books',
        port: 3306
    }
}

module.exports = sqlConfig

与 config 同级,创建一个db文件夹,用于存放数据操作,在 db 文件夹下创建 sequelize.js 文件,进行数据库的连接

// db/sequelize.js
const Sequelize = require('sequelize')
const {database,host,user,password} = require('../config/db')
// 连接数据库
const sequelize = new Sequelize(database,user,password,{
    host: host,
    port: 3306,
    dialect: 'mysql',// 数据库,默认是 mysql
    logging: null, // 日志,详情可以去官网看
    // 使用数据池
    pool: {
        max: 20,
        min: 1,
        acquire: 60000,
        idle: 10000
    },
})

module.exports = sequelize
// 测试连接
// sequelize.authenticate().then(_=>{
//     console.log('连接成功')
// }).catch(err=>{
//     console.log('连接失败'+err)
// })
// 关闭连接
// sequelize.close()

使用

定义模型

sequelize.define(moduleName,attributes,options)

根据数据库建立一个简单的模型

const { DataTypes } = require('sequelize')
const sequelize = require('../sequelize')

const BooksModel = sequelize.define('mybooks', {
    id: {
        type: DataTypes.INTEGER(4),
        allowNull: false,
        primaryKey: true,
        autoIncreament: true
    },
    name: {
        type: DataTypes.STRING(50),
        allowNull: false,
        defaultValue: '没有书名'
    },
    author: {
        type: DataTypes.STRING(50),
        defaultValue: '匿名',
    },
    price: {
        type: DataTypes.DECIMAL(10, 2),
        allowNull: false,
        defaultValue: 0.00
    },
    type: {
        type: DataTypes.ENUM('A', 'B', 'C', 'D', 'N'),
        allowNull: false,
        defaultValue: 'N'
    },
    note: {
        type: DataTypes.STRING,
        allowNull: true,
        // 自定义,根据数据库中其他字段的值计算出该字段
        // type: DataTypes.VIRTUAL,
        // get() {
        //     return this.id.toString() + '' + this.name + '€'
        // }
    }
}, {
    // 模型不需要添加事件戳,如果不设置,那么保存到数据库中会因为缺少 createAt 和 updateAt 而报错
    timestamps: false,
    // 该模式中会自动将表格名称修改为复数,有两种解决办法:1. 在配置项中设置:freezeTableName: true;2. 直接设置表格名称(在这里我设置成复数)
    tableName: 'mybooks'
})
module.exports = BooksModel

扩展 Model

const { DataTypes,Model } = require('sequelize')
const sequelize = require('../sequelize')
// 继承 Model 类
class UsersModel extends Model{}

UsersModel.init({
    id: {
        type: DataTypes.INTEGER(4),
        allowNull: false,
        primaryKey: true,
        autoIncreament: true
    },
    name: {
        type: DataTypes.STRING(50),
        allowNull: false,
        defaultValue: '没有名字'
    },
    age:{
        type: DataTypes.INTEGER(3),
        allowNull: false,
        defaultValue: 0
    },
    birthdate:{
        type: DataTypes.DATEONLY, 
        allowNull: true, 
        defaultValue: DataTypes.NOW()
    }
}, {
    // 连接实例
    sequelize:sequelize,
    // 模型名称
    moduleName:'UsersModel',
    timestamps: false,
    tableName: 'users'
})
module.exports = UsersModel

数据操作

与 model 文件夹同级,新建 service 文件夹,用于数据操作

新增

build()+save()

const addbooks = async(id,name,price,author,type) =>{
    let books = BookModel.build({
        id:id,
        name:name,
        price:price,
        author:author,
        type:type
    })
    books = await books.save()
    // 将模型实例直接返回会有很多问题,因为实例上有许多附加条件,可以使用 .toJSON() 或者 JSON.stringify() 操作后返回
    return books.toJSON()
}
addbooks(11,'静夜思',10,'李白','D').then(data=>console.log(data))

create()

  • create()
    实际上就是 build() + save(),操作起来更快
// db/service/BookService.js
const BookModel = require('../model/BookModel');
const UserModel = require('../model/UserModel')
const { Op } = require('sequelize');
const sequelize = require('../db/sequelize')

const addbooks = async(id,name,price,author,type) =>{
    const books = await BookModel.create({
        id:id,
        name:name,
        price:price,
        author:author,
        type:type
    })
    return books.toJSON()
}
addbooks(11,'静夜思',10,'李白','D').then(data=>console.log(data))

删除

  • destroy()
// where 条件匹配,用的非常多,可以去官网仔细看看
const deletebooks = async(name)=>{
    const books = await BookModel.destroy({
        where:{name:name}
    })
    return books
}
deletebooks('静夜思').then(data=>console.log(data))

修改

  • update
const updatebooks = async()=>{
    const books = await BookModel.update({note:'价格便宜'},{
        where:{
            price:{
                // 查找价格小于 20 的
                [Op.lt]:20
            }
        }
    })
    return books;
}
updatebooks().then(data=>console.log(data))

操作符(官网上的)

where: {
    [Op.and]: [{ a: 5 }, { b: 6 }],            // (a = 5) AND (b = 6)
    [Op.or]: [{ a: 5 }, { b: 6 }],             // (a = 5) OR (b = 6)
    someAttribute: {
      // 基本
      [Op.eq]: 3,                              // = 3
      [Op.ne]: 20,                             // != 20
      [Op.is]: null,                           // IS NULL
      [Op.not]: true,                          // IS NOT TRUE
      [Op.or]: [5, 6],                         // (someAttribute = 5) OR (someAttribute = 6)

      // 使用方言特定的列标识符 (以下示例中使用 PG):
      [Op.col]: 'user.organization_id',        // = "user"."organization_id"

      // 数字比较
      [Op.gt]: 6,                              // > 6
      [Op.gte]: 6,                             // >= 6
      [Op.lt]: 10,                             // < 10
      [Op.lte]: 10,                            // <= 10
      [Op.between]: [6, 10],                   // BETWEEN 6 AND 10
      [Op.notBetween]: [11, 15],               // NOT BETWEEN 11 AND 15

      // 其它操作符

      [Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1)

      [Op.in]: [1, 2],                         // IN [1, 2]
      [Op.notIn]: [1, 2],                      // NOT IN [1, 2]

      [Op.like]: '%hat',                       // LIKE '%hat'
      [Op.notLike]: '%hat',                    // NOT LIKE '%hat'
      [Op.startsWith]: 'hat',                  // LIKE 'hat%'
      [Op.endsWith]: 'hat',                    // LIKE '%hat'
      [Op.substring]: 'hat',                   // LIKE '%hat%'
      [Op.iLike]: '%hat',                      // ILIKE '%hat' (不区分大小写) (仅 PG)
      [Op.notILike]: '%hat',                   // NOT ILIKE '%hat'  (仅 PG)
      [Op.regexp]: '^[h|a|t]',                 // REGEXP/~ '^[h|a|t]' (仅 MySQL/PG)
      [Op.notRegexp]: '^[h|a|t]',              // NOT REGEXP/!~ '^[h|a|t]' (仅 MySQL/PG)
      [Op.iRegexp]: '^[h|a|t]',                // ~* '^[h|a|t]' (仅 PG)
      [Op.notIRegexp]: '^[h|a|t]',             // !~* '^[h|a|t]' (仅 PG)

      [Op.any]: [2, 3],                        // ANY ARRAY[2, 3]::INTEGER (仅 PG)
      [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // 匹配文本搜索字符串 'fat' 和 'rat' (仅 PG)

      // 在 Postgres 中, Op.like/Op.iLike/Op.notLike 可以结合 Op.any 使用:
      [Op.like]: { [Op.any]: ['cat', 'hat'] }  // LIKE ANY ARRAY['cat', 'hat']

    }
  }

查看

// 查找给定类别、给定价格区间内的书本,只要求输出 name,作者和 price,并按照 id 倒序输出
const getbook = async (price1, price2, type) => {
    const books = await BookModel.findAll({
        attributes: ['name', ['author', '作者'], 'price'],
        where: {
            [Op.and]: [
                {
                    price: {
                        [Op.gt]: price1,
                        [Op.lt]: price2,
                    }
                },
                { type: type }
            ]
        },
        order: [
            ['id', 'DESC']
        ]
    })
    return JSON.parse(JSON.stringify(books));
}
getbook(10, 50, 'A').then(data => console.log(data))
  • 聚类统计问题
// 统计出给定类的书本价格之和,使用的是聚合函数 SUM 和 group
const getbooks = async (type) => {
    const books = await BookModel.findAll({
        attributes: [
            'type',
            [sequelize.fn('SUM', sequelize.col('price')), 'sum']
        ],
        group: 'type',
        where:{
            type:type
        }
    })
    return JSON.parse(JSON.stringify(books));
}
getbooks('A').then(data => console.log(data))
  • 分页问题
// 给定页面大小和页码数,搜索符合要求的书本,默认按照 id 排序
const getbooks = async (pageNum, pageSize) => {
    const books = await BookModel.findAll({
        attributes: { exclude: ['note', 'type'] },
        // 跳过
        offset: (pageNum - 1) * pageSize,
        // 截取
        limit: pageSize,
    })
    return books
}
// 再调用函数外面使用 toJSON() 也是可以的
getbooks(3, 4).then(data => { data.length ? data.map(book => console.log(book.toJSON())) : console.log('No data') })
  • 关联问题
// 使用 id join,成功!
UserModel.hasOne(BookModel,{foreignKey:'id'}) // 添加 UserModel 的外键为 id
BookModel.belongsTo(UserModel,{foreignKey:'id'}) // 添加 BookModel 的外键为 id
const getbooks = async () => {
    const books = await BookModel.findAll({
        include:{
            model:UserModel
        }
    })
    return JSON.parse(JSON.stringify(books))
}

getbooks().then(data => console.log(data))

// 使用 author 和 name join,失败!
UserModel.hasOne(BookModel,{foreignKey:'name'})
BookModel.belongsTo(UserModel,{foreignKey:'author'})
const getbooks = async () => {
    const books = await BookModel.findAll({
        include:{
            model:UserModel
        }
    })
    return JSON.parse(JSON.stringify(books))
}

getbooks().then(data => console.log(data))

上面的关联问题中,两表之间的 id 关联成功,两表之间的 author 和 name 关联失败,明明关联的数据值是相等的,为什么一个成功,一个失败呢?
其实只需要把 UsersModel 的主键改成 name 就可以关联成功,这有必要回忆一下主键和外键

  • 主键:主键表示唯一标识一条记录,不能有重复的,不允许为空
  • 外键:外键用于与另一张表的关联。是能确定另一张表记录的字段,用于保持数据的一致性
    也就是说 B 表的主键 b,就可以是 A 表的外键 b,A 表内的外键 b 在 A 表内可以有重复,但是在 B 表内必须是唯一值
posted @ 2023-05-18 10:28  超重了  阅读(502)  评论(0编辑  收藏  举报