node的ORM使用-Sequelize

本文档详细介绍了如何使用 Sequelize,一个基于 Promise 的 Node.js ORM(对象关系映射),适用于 MySQL、Postgres、MariaDB、SQLite 和 Microsoft SQL Server。内容包括安装、连接数据库、创建模型、增删改查操作、事务处理、表关联、数据验证、时间处理、访问器和虚拟字段,以及日志记录等核心功能。

一、官方地址(中文版):

https://github.com/demopark/sequelize-docs-Zh-CN

二、新建工程

  • ① 新建文件夹,打开终端,输入 npm init -y
  • ② 在目录下,新建 index.js 文件

三、安装

3.1 安装数据库驱动

npm i mysql2 

3.2 安装 Sequelize

npm i sequelize

四、使用

4.1 代码

  • ①、在根目录下新建 models 文件夹,新建 db.js ,代码如下:
const { Sequelize } = require('sequelize');

module.exports = new Sequelize('数据库表名', '数据库用户名', '数据库密码',  {
    host: 'localhost', 	// 本地服务,也可以线上服务
    dialect: 'mysql'	// 数据库类型
});
  • ②、在根目录下的 index.js 中书写代码:
const sequelize = require('./models/db');

// 测试连接
(async function () {
    try {
        await sequelize.authenticate();
        console.log('Connection has been established successfully.');
    } catch (error) {
        console.error('Unable to connect to the database:', error);
    }
})();

4.2 测试连接

  • 在本地的MySQL中新建一张表(对应上面配置的’数据库表名’)。
  • 运行 node index.js ,成功的话,控制台会显示 'Connection has been established successfully.'

在这里插入图片描述

五、模型

模型相当于数据库中的一张表的概念

  • ①、例如数据库有一张 管理员 的表,那么在 models文件夹 下新建 Admin.js 文件:
const { DataTypes } = require('sequelize');
const sequelize = require('./db');

const Admin = sequelize.define('Admin', {
    // 第二个参数,定义模型属性(也就是列,id可以省略)
    loginId: {
        type: DataTypes.STRING, // 该列是 字符串型
        allowNull: false        // 是否可以 null 值
    },
    loginPwd: {
        type: DataTypes.STRING,
        allowNull: false
    },
    nickName: {
        type: DataTypes.STRING,
        allowNull: false
    }
},{
    // 这是第三个参数,可以不写(默认)
});

// 导出模型,后续可以对Admin进行管理
module.exports = Admin;  // 在数据库里会生成 Admins 的表名,自动推算成复数形式,若自定义参照文档
  • ②、在 index.js 中 同步模型
const Admin = require('./models/Admin');

// 模型同步
(async function () {

    // Admin.sync();   // 如果表不存在,则创建。若存在,则不创建
    // Admin.sync({ force: true });    // 强制创建表,会覆盖之前表

    // 如果表不存在,则创建。若存在,则进行比对,比对有差异进行修改
    Admin.sync({ alter: true });

})();
  • ③、执行成功后,刷新数据库,发现新增一个 admins 表。如下:
    在这里插入图片描述
  • ④、第三个参数
{
    // 这是第三个参数,可以不写(默认)
    createdAt: false, // 不想要 createdAt(也可以将false写成 想要的字符串)

    updatedAt: 'updateTime',  // 想要 updatedAt 名称改成 updateTime

    paranoid: true,  // 该表数据不会被真正删除,会增加一列deleteAt 用来记录删除的时间
    deletedAt: 'deleteTime', // 配合 paranoid 使用,自定义删除列的名称
}
  • ⑤、一次同步所有模型,新建 sync.js 文件, 直接 node sync.js
require('./Admin');
require('./Book');
require('./Class');
require('./Student');

const sequelize = require('./db');

sequelize.sync({ alter: true }).then(() => {
    console.log('模型已全部同步完成')
})
  • ⑥、表外联:对应文档中的关联部分。
    假如 班级 和学生有个映射,在班级表中:
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Student = require('./Student');	// 引入学生表

const Class = sequelize.define('Class',{
    className: {
        type: DataTypes.STRING,
        allowNull: false
    },
    classStart: {
        type: DataTypes.STRING,
        allowNull: false
    }
},{
    createdAt: false,
    updatedAt: false,
    paranoid: true
});

// 表示一个班级拥有多个学生
Class.hasMany(Student);	// 当前表外联 Student 表

module.exports = Class;

六、增删改查

  • ①、根目录下新建 services 文件夹,新建 adminService.js

// 管理员初始化,判断数据库中是否有管理员,如果没有,则自动添加一个默认管理员(或超级管理员)


const Admin = require('../models/Admin');

/**
 * 添加管理员
 * @param obj Object
 * @param operatorId String 操作者的id
 */
exports.addAdmin = async function(obj, operatorId) {
    // 方式一
    // const ins = Admin.build(obj);
    // ins.save().then(() => {
    //     console.log('第一个管理员成功了')
    // })

    // 方式二,相当于上面的 构建完自动运行
    const ins = await Admin.create(obj);
    console.log('admin 添加成功')
    return ins.toJSON();    // 返回ins的平面对象
}

/**
 * 删除管理员
 * @param id
 */
exports.deleteAdmin = async function(id) {
    // 方式一: ①得到实例、②删除实例
    // const ins = await Admin.findByPk(id)
    // await ins.destroy();

    // 方式二:比上面省一条SQL语句
    await Admin.destroy({
        where: {
            id
        }
    })
}

/**
 * 修改管理员
 * @param obj
 */
exports.modifyAdmin = async function(id, obj) {
    // 方式一:
    // const ins = await Admin.findByPk(id);
    // ins.loginPwd = obj.loginPwd;
    // ins.save();

    // 方式二:
    await Admin.update(obj, {
        where: {
            id
        }
    })
}

/**
 * 单个查询 - 登陆查询
 * @param loginId
 * @param loginPwd
 * @returns {Promise<*>}
 */
exports.login = async function(loginId, loginPwd) {
    // const res = await Admin.findOne({
    //     where: {
    //         loginId,
    //         loginPwd
    //     }
    // })
    // // 如果有结果了,且大小写都相同的话,那么返回,并展开
    // if(res && res.loginId === loginId && res.loginPwd === loginPwd) {
    //     return res.toJSON();
    // }
    // return null;

    // 使用了 md5 方式
    loginPwd = md5(loginPwd);
    const res = await Admin.findOne({
        where: {
            loginId,
            loginPwd
        },
        attributes: ['id', 'loginId', 'nickName', 'createdAt'], // 返回指定字段的内容
    })
    // 如果有结果了,且大小写都相同的话,那么返回,并展开
    if(res && res.loginId === loginId) {
        return res.toJSON();
    }
    return null;
}

/**
 * 按主键查询
 * @param id
 * @returns {Promise<void>}
 */
exports.getAdminById = async function(id) {
    const res = await Admin.findByPk(id);
    if(res) {
        return res.toJSON();
    }
    return null;
}

// 另一张表,分页查询示例
/**
 * 查询所有学生 - 分页
 * @returns {Promise<string>}
 */
exports.getStudent = async function(page = 1, limit = 10, sex = -1, name = "") {
    // 方式一
    // const res = await Student.findAll({
    //     offset: (page - 1) * limit,
    //     limit: +limit
    // })
    // const total = await Student.count();
    // const data = JSON.parse(JSON.stringify(res));
    // return {
    //     total,
    //     data
    // }

    // 方式二
    // const { count, rows } = await Student.findAndCountAll({
    //     offset: (page -1) * limit,
    //     limit: +limit,
    // })
    // return {
    //     count,
    //     data: JSON.parse(JSON.stringify(rows))
    // }

    // 如果带条件的话
    const condition = {}
    if(sex != -1) {
        condition.sex = !!sex;  // 强制取反,变成布尔类型,再取反变回原数据(解决了 0, 1 以外的数字)
    }
    // if(name) {      // 精准查询
    //     condition.name = name
    // }
    // 使用 sequelize 的api:Op,进行 模糊查询
    if(name) {
        condition.name = {
            [Op.like]: `%${name}%`
        }
    }
    const { count, rows } = await Student.findAndCountAll({
        where: condition,
        offset: (page - 1) * limit,
        limit,
        attributes: ['id', 'name', 'sex'],   // 查出来的,只包含这些
        include: [Class],       // 查出关系表 对应的内容
    });
    return {
        count,
        data: JSON.parse(JSON.stringify(rows))
    }
}
  • ②、测试,在 index.js
const adminService = require('./services/adminService');

// 增加
// adminService.addAdmin({
//     loginId: 'thirdAdmin',
//     loginPwd: '123123',
//     nickName: '第三个管理员'
// })

// 删除
// adminService.deleteAdmin(2)

// 修改
// adminService.modifyAdmin(1, {
//     loginPwd: '999666'
// })

// 查询所有学生
// studentService.getStudent(1, 5, 1, '秀').then(res => {
//     console.log(res);
// })

七、使用事务

const sequelize = require('../models/db');

// 使用事务后 添加
exports.addAdmin = async function(obj) {
    try {
        const result = await sequelize.transaction(async (t) => {
            obj.loginPwd = md5(obj.loginPwd)
            const ins = await Admin.create(obj, { transaction: t });
            // throw new Error();      // 模拟错误,正常注释掉
            return ins.toJSON();    // 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果,这种情况下,result 中的数据就是 user
        });
    } catch (error) {
        console.log("数据回滚\n" + error);
    }
}

八、表关联

在 models下,新建 relation.js

// 设置外键,表关联

const Class = require('./Class');
const Student = require('./Student');

Class.hasMany(Student);
Student.belongsTo(Class);

index.js 中,引入即可

// 外键,表外联
require('./models/relation');

九、md5加密

安装 npm install md5
在 adminService.js 中,一般也就 登陆密码的地方需要加密

const Admin = require('../models/Admin');
const md5 = require('md5');
/**
 * 添加管理员
 * @param obj Object
 * @param operatorId String 操作者的id
 */
exports.addAdmin = async function(obj, operatorId) {
    // 使用 md5 加密
    obj.loginPwd = md5(obj.loginPwd);
    const ins = await Admin.create(obj);
    return ins.toJSON();
}

/**
 * 删除管理员
 * @param id
 */
exports.deleteAdmin = async function(id) {
    // 方式一: ①得到实例、②删除实例
    // const ins = await Admin.findByPk(id)
    // await ins.destroy();

    // 方式二:比上面省一条SQL语句
    await Admin.destroy({
        where: {
            id
        }
    })
}

/**
 * 修改管理员
 * @param obj
 */
exports.modifyAdmin = async function(id, obj) {
    // 使用 md5 方式
    if (obj.loginPwd) {
        obj.loginPwd = md5(obj.loginPwd)
    }
    await Admin.update(obj, {
        where: {
            id
        }
    })
}
/**
 * 单个查询 - 登陆查询
 * @param loginId
 * @param loginPwd
 * @returns {Promise<*>}
 */
exports.login = async function(loginId, loginPwd) {
    // 使用了 md5 方式
    loginPwd = md5(loginPwd);
    const res = await Admin.findOne({
        where: {
            loginId,
            loginPwd
        }
    })
    // 如果有结果了,且大小写都相同的话,那么返回,并展开
    if(res && res.loginId === loginId && res.loginPwd === loginPwd) {
        return res.toJSON();
    }
    return null;
}

/**
 * 按主键查询
 * @param id
 * @returns {Promise<void>}
 */
exports.getAdminById = async function(id) {
    const res = await Admin.findByPk(id);
    if(res) {
        return res.toJSON();
    }
    return null;
}

十、数据验证和时间处理

  • 验证使用 validate,安装 npm install validate.js
  • 时间使用 moment,安装 npm install moment
  • 给validate继承一个时间处理方法,在service目录下,新建 init.js
const validate = require('validate.js');
const moment = require('moment');

validate.extend(validate.validators.datetime, {
    /**
     * 该函数会自动用于日期格式转换
     * 它会在验证时自动触发,它需要将任何数据转换为时间戳返回
     * 如果无法转换,返回NaN
     * @param {*} value 传入要转换的值
     * @param {*} options 针对某个属性的验证配置
     */
    parse(value, options) {
        let formats = ['YYYY-MM-DD HH:mm:ss', 'YYYY-M-D H:m:s', 'x'];
        if(options.dateOnly) {
            formats = ['YYYY-MM-DD', 'YYYY-M-D', 'x'];
        }
        return +moment.utc(value, formats, true);
    },
    /**
     * 用于显示错误消息时,使用的显示字符串
     * @param {*} value 传入要转换的值
     * @param {*} options 针对某个属性的验证配置
     */
    format(value, options) {
        let format = 'YYYY-MM-DD';
        if(!options.dateOnly) {
            format += 'HH:mm:ss';
        }
        return moment.utc(value).format(format)
    }
})

设置过滤函数,在 util 目录下,新建 propertyHelper.js

/**
 * 传来一个对象,挑选部分属性
 * @param obj
 * @param props
 * @returns {{}|*}
 */
exports.pick = function(obj, ...props) {
    if(!obj || typeof obj !== 'object') {
        return obj
    }
    const newObj = {};
    for (const key in obj) {
        if(props.includes(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

以添加学生为例:

const Student = require('../models/Student');
const { Op } = require('sequelize');
const validate = require('validate.js');
const moment = require('moment');

// 假如传过来的对象中有 deletedAt 字段,我并不想要,所以自定义挑选
const { pick } = require('../util/propertyHelper');

const Class = require('../models/Class');

/**
 * 添加学生
 * @param obj
 * @returns {Promise}
 */
exports.addStudent = async function(obj) {

    // 挑选 过滤
    obj = pick(obj, 'name', 'birthDay', 'sex', 'phone', 'ClassId')

    // 写到下面的classId时,得判断传的班级id存不存在,可以在验证规则里自定义一个方法
    validate.validators.classExist = async function(value) {
        const exist = await Class.findByPk(value);
        if (exist) {
            return;
        }
        return 'is not exist'
    }

    // 使用验证
    const result = validate.async(obj, {
        name: {
            // presence: true
            presence: {
                allowEmpty: false
            }
        },
        birthDay: {
            presence: {
                allowEmpty: false
            },
            datetime: {
                dateOnly: true,
                earliest: +moment.utc().subtract(100, 'y'), // 最早不能超过100年
                latest: +moment.utc().subtract(5, 'y')  // 最晚不能小于5年
            }
        },
        sex: {
            presence: true,
            type: 'boolean'
        },
        phone: {
            presence: {
                allowEmpty: false
            },
            format: /1\d{10}/
        },
        ClassId: {
            presence: true,
            // type: 'integer',    // 比较严格
            numericality: {         // 宽松
                onlyInteger: true,
                strict: false,      // 关闭严格模式
            },
            classExist: true,       // 此处见本方法的第一行,自定义验证规则
        }
    })
    // console.log(result);    // 通过:undefined,不通过则报错
    result.then(res=> {
        console.log('通过了')
        Student.create(obj).then(ins => {
            console.log('开始添加')
            return ins.toJSON();
        });

    }).catch( err => {
        console.log(err)
    })
}

在index.js 中测试

studentService.addStudent({
    name: '大大大',
    birthDay: '2012-04-14',
    sex: true,
    phone: "13913913913",
    ClassId: 2,
    deleteAt: '2022-05-03'
})

十一、访问器和虚拟字段

1)访问器

假如查询学生表,出来的是这样的,标准的时间格式,并不是我想要的
在这里插入图片描述

  • 需要设置访问器,相当于 get/set 方法。
  • 在模型那里,student.js 中birthDay 设置如下:
    birthDay: {
        type: DataTypes.DATE,
        allowNull: false,
        get() {
        	// this指模型本身,通过自带的方法getDataValue获取birthDay,转换成时间戳
            return this.getDataValue('birthDay').getTime();
        }
    },

2)虚拟字段

在模型中没有的字段,想使用的话。例如模型中没有age 字段,那么在模型中如下:

const moment = require("moment");

    age: {
        type: DataTypes.VIRTUAL,
        get() {
            const now = moment.utc();
            const birth = moment.utc(this.birthDay);
            return now.diff(birth, 'y') // 得到两个日期的年份差异
        }
    },

在这里插入图片描述

十二、日志记录

使用第三方库,安装 npm install log4js
index.js 中测试

const log4js = require('log4js');
const logger = log4js.getLogger();
logger.level = 'all'; // 将日志级别调成all(最低),默认off(最高)不显示。具体看官网
logger.info('abc');

在这里插入图片描述

  • 正式书写:在根目录下新建 logger.js
const { resolve } = require('path');
const log4js = require('log4js');
log4js.configure({
    // 出口设置
    appenders: {
        sql: {  // 定义一个sql日志出口
            type: "dateFile",   // file: 文件,dateFile:带日期的文件
            filename: resolve(__dirname, 'logs', 'sql', 'logging.log'),    // 出口的路径和名称
            maxLogSize: 1024 * 1024,   // 配置文件的最大字节数,防止文件过大,字节单位。达到这个长度,则自动新增一个文件
            keepFileExt: true,  // 当备份时,以log为后缀名
            numBackups: 1,      // 只保留1个,之前的不要
            layout: {
                type: "pattern",    // 自定义格式
                pattern: '%d{yyyy-MM-dd hh:mm:ss} %p %c: %m %n',    // 时间 级别 类型 内容 换行
            }
        },
        default: {
            type: 'stdout'
        }
    },
    categories: {
        sql: {
            appenders: ['sql'], // 该分类使用出口sql的配置写入日志
            level: 'all',       // 级别最低
        },
        default: {
            appenders: ['default'],
            level: 'all'
        }
    }
})

// 在程序退出或崩溃时调用下shutdown,防止没有记录完
process.on('exit', () => {
    log4js.shutdown();
})

const sqlLogger = log4js.getLogger('sql');
const defaultLogger = log4js.getLogger('default');

exports.sqlLogger = sqlLogger;
exports.logger = defaultLogger;
  • 应用到数据库中:在 db.js
const { Sequelize } = require('sequelize');
const { sqlLogger } = require('../logger');
module.exports = new Sequelize('test', 'root', '123123',  {
    host: 'localhost',
    dialect: 'mysql',
    logging: sql => {
        sqlLogger.debug(sql);
    }
});
  • 然后在 index.js 中查询数据测试
studentService.getStudent(1, 5).then(res => {
    console.log(res)
})

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值