1.0 前言

大家看我们之前 Mysql 章节,我们和数据库交互的时候,每次都是要写 sql 的,然后处理数据返回的数据一般都是下划线的的(比如 img_url),而一般前端一般用的是驼峰写法(imgUrl),假设数据库的字段非常多,表也有很几个,每次转换起来非常麻烦,而且每次查询都要写 sql 也是很麻烦的,有什么简便的方法吗? 答案是有的,就是 ORM 库。

2.0 什么是 ORM?

ORM:对象关系映射(Object Relational Mapping,简称ORM),目的是操作对象一样操作数据库。因为数据库不是面向对象的,所以需要一个 对象来和数据库一一对应起来,方便操作数据库。

举个例子,我们 Mysql 对用户接口操作有下面几个方法:

  1. async function list(ctx) {
  2. const data = await ctx.mysql.queryPromise('select id,name,email,img_url from user')
  3. ctx.body = {
  4. data: data,
  5. success: true
  6. }
  7. }

这样我们每次都要写 sql ,非常不方面,那现在假设有个对象和数据库字段映射起来,我们转变操作数据库的方式。

  1. // app/model/user.js
  2. 伪代码如下
  3. const User = sequelize.define("User", {
  4. id: {
  5. type: DataTypes.INTEGER(10),
  6. allowNull: false,
  7. autoIncrement: true,
  8. primaryKey: true
  9. },
  10. name: String,
  11. email: String,
  12. imgUrl: String
  13. }, {
  14. tableName: 'user',
  15. });
  16. module.exports = User

接着我们 用户操作改成:

  1. import User from '../model/user'
  2. async function list(ctx) {
  3. const data = await ctx.mysqlORM.User.find({})
  4. // 返回的 data 自动转换成 [{id: 1, name: '', email: '', imgUrl: ''}]
  5. ctx.body = {
  6. data: data,
  7. success: true
  8. }
  9. }

同理,新增,更新,删除操作,我们只要操作 User 对象就好了,自动生成对应的 sql,这样不就方便很多么。

那这个整个逻辑即使 ORM 库要解决的问题,本质就是对数据库做了一层抽象,通过我们容易理解,方便操作的 对象的方式来操作数据,大大减少工作量。

3.0 Sequelize 使用

在 Node.js 我们最常用的 ORM 框架就是 Sequelize 了,Sequelize 文档

3.0.1 安装

  1. npm install --save sequelize
  2. npm install --save mysql2

3.0.2 创建 Sequelize 实例

Sequelize 这个库 包裹了 Mysql ,我还是还是在 /app/db/mysql 中初始化 Sequelize。

  1. const {mySql } = require('../config')
  2. const { Sequelize } = require('sequelize');
  3. const { logger } = require('../log4j/logger')
  4. const { port, host, pass, userName, database, dialect = 'mysql' } = mySql
  5. let connection
  6. async function initDB() {
  7. try {
  8. // 初始化 sequelize
  9. const sequelize = new Sequelize(database, userName, pass, {
  10. host: host,
  11. port: port,
  12. dialect: dialect
  13. });
  14. connection = sequelize
  15. await sequelize.authenticate()
  16. } catch (error) {
  17. logger.error(error)
  18. }
  19. }
  20. function getConnection() {
  21. return connection
  22. }
  23. initDB()
  24. module.exports = {
  25. getConnection
  26. }

3.0.3 定于 User 对应的 model

和数据库表映射的我们都放在 app/models 目录下。

  1. app/models/user.js
  2. const { Sequelize, DataTypes, Model } = require('sequelize')
  3. const { getConnection } = require('../db/mysql')
  4. class User extends Model {
  5. }
  6. User.init(
  7. {
  8. id: {
  9. type: DataTypes.INTEGER({ length: 11, unsigned: true }), // 标记类型
  10. autoIncrement: true, // id 是否自动增长
  11. primaryKey: true, // 是否是主键
  12. allowNull: false,
  13. comment: 'id', // 描述
  14. },
  15. name: {
  16. type: DataTypes.STRING,
  17. allowNull: false,
  18. comment: '名称',
  19. },
  20. email: {
  21. type: DataTypes.STRING,
  22. allowNull: false,
  23. comment: '邮箱',
  24. },
  25. imgUrl: {
  26. type: DataTypes.STRING,
  27. allowNull: false,
  28. comment: '图片url',
  29. },
  30. }, {
  31. sequelize: getConnection(),
  32. modelName: 'User',
  33. tableName: 'user',
  34. underscored: true, // 转下划线
  35. comment: '用户表',
  36. freezeTableName: true,
  37. timestamps: true,
  38. })
  39. module.exports = User

如上我们就定义了好了一个 User model,接下来我们看看 查询的时候变化。

3.0.4 使用 model

我们看看看之前用户查询改成如下:

  1. const User = require('../models/user')
  2. async function list(ctx) {
  3. const data = await User.findAll()
  4. console.log('查询到:', data)
  5. ctx.body = {
  6. data: data,
  7. success: true
  8. }
  9. }
  10. async function detail(ctx) {
  11. const id = ctx.params.id
  12. const data = await User.findByPk(id)
  13. ctx.body = {
  14. data: data[0],
  15. success: true
  16. }
  17. }
  18. async function add(ctx) {
  19. const { path } = ctx.request.files.file
  20. const { name, email } = ctx.request.body // 获取 request body 字段
  21. const imgUrl = path.split("/static")[1]
  22. const data = await User.create({ name, email, imgUrl })
  23. ctx.body = {
  24. success: true,
  25. }
  26. }
  27. async function remove(params) {
  28. const id = ctx.params.id
  29. const data = await User.destroy(id)
  30. ctx.body = {
  31. data: data[0],
  32. success: true
  33. }
  34. }
  35. module.exports = {
  36. detail,
  37. list,
  38. add,
  39. remove
  40. }

我们把项目看起来,看看查询接口,控制台打印如下:
image.png
可以看到,Sequelize 会自动组装 sql ,并且返回的数据都已经自动将下划线转换成的驼峰字段了。大大节省了人力。

3.0.5 绑定到 Koa 的 ctx 对象上去

因为 models 目录下面都是我们需要的 model,我们可以获取到 这些文件,用一个对象存储起来,然后绑定到 ctx 对象上去。

  1. // app/models/index.js
  2. const fs = require('fs')
  3. const path = require('path')
  4. const { getConnection } = require('../db/mysql')
  5. const User = require('./user')
  6. let modelObj = {}
  7. fs.readdirSync(__dirname).filter(function(file) {
  8. console.log(file)
  9. return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js');
  10. })
  11. .forEach(function(file) {
  12. const model = require(path.join(__dirname, file))
  13. modelObj[model.name] = model;
  14. });
  15. Object.keys(modelObj).forEach(function(modelName) {
  16. if (modelObj[modelName].associate) {
  17. modelObj[modelName].associate(modelObj)
  18. }
  19. });
  20. modelObj.sequelize = getConnection()
  21. module.exports = modelObj // 这样 modelObj = {User}
  22. // app.js
  23. const model = require('./app/models/index')
  24. app.use(async (ctx, next) => {
  25. ctx.model = model
  26. await next()
  27. })

如上,我们就将所有的 model 都绑定到 ctx 对象上去了,这样我们 接口可以改造如下:

  1. // app/controller/user.js
  2. async function list(ctx) {
  3. const data = await ctx.model.User.findAll()
  4. console.log('查询到:', data)
  5. ctx.body = {
  6. data: data,
  7. success: true
  8. }
  9. }

这样就不需要 加载对应的 js 了,这种使用方式也挺方便的。

4.0 小结

这节我们学习了 ORM 的概念,Sequelize 的使用,在平常开发过程中我们都会使用到 ORM 框架,大家可以对着 Demo 源码看看,Demo地址