在egg中使用sequelize进行分表读写

1,922 阅读5分钟

关于egg和sequelize

封装框架会解决很多基础问题,同时也会带来很多应用问题

对于前端来说,开发后端服务,最熟悉不过就是 express 和 koa 了,而 egg 是基于 koa 进行封装的应用层框架,做了很多基础功能的封装,开箱即用,十分方便,也提供了插件机制进行扩展。

而 sequelize,是一个 node.js 的 orm 工具,看下 v7 的文档,支持 Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift 和 Snowflake’s Data Cloud,对于我们常用的关系型数据库 MySQL 的话,使用这个工具,也会免去很多基础 sq l语句的写法。

当然了,现在我们用 node.js 的时候,一般会选用 MongoDB 这个 NoSQL 数据库,对应就是 mongoose 这个object modeling。

而在 egg 中,也都封装了对应的插件来进行支持,针对本文说的 sequelize ,egg 自家封装了 egg-sequelize 这个插件,如果单想用 mysql ,也可以用 egg-mysql 插件,应用层的框架都喜欢这样,怕你集成不上其他框架,都帮你包一层。

在egg中使用sequelize定义表

一个model一个表

在默认路径 app/model 文件夹下,定义各种表文件,一个表一个文件,就像下面这个 user 表定义文件:

在进行表操作也很简单,因为插件会挂载到 app.model 和 ctx.model 上,所以可以用 app.model.User 或 ctx.model.User 来调用model的方法。

想办法如何进行分表

单表数据量大,考虑到按天进行分表

第一个想法,使用万能的 sql 原生语句,sequelize 的 api 是完全支持的,但是想到 orm 本身的映射关系,考虑的方向,就改为建立对应的 model 了。

但是因为在 egg 中,定义 model 就是在 app/model 下加文件,心想不会是每次新建一个表都动态创建一个文件进去吧,感觉还是不靠谱。

没办法,只有看下源码看怎样解决。

先看看egg-sequelize

看下egg中定义是怎样的机制加载sequelize的

很容易找到 egg-sequelize 包下的 lib/loader.js,就一个文件,一百多行代码,可以完整看下去。

从下面两行代码可以知道,引入的 sequelize 会挂载在 app.Sequelize 下。

那 sequelize 的实例,会挂载在哪里呢?继续往下看,可以看到这段代码:

这里的代码命名其实会有点误导的,因为看这个代码之前,会有个先入为主的想法:通过 app.model.User 可以操作对应的表,那么会想,必然是先给 app 定义 model 对象,然后挂载不同的 model 实例上去。

而上面这段代码,如果忽略了 let model = app 这行代码,会以为后面挂载 model 变量的就是 app.model 上,但是实际上是挂载 app 上。

然后回头看下 delegate[len - 1] 这个东西的引用,其实就是一个字符串“model”,这个才是 app.model 的这个model。

再从 Object.defineProperty 的那行代码可以看出,sequelize 实例就是定义 app.model 上了,那使用 sequelize 实例的方法就会很清晰了。

最后再看看app是如何加载model文件的:

这里模糊的主要是 egg 的一个 loader 插件机制提供的 loadToApp 方法,可以再进入 egg-core/lib/loader/egg_loader.js 的 378 行看下,这里不展开,主要就是引入对应路径的文件,会提供一些配置的方法来筛选和设置文件名首字母大写来挂载在 model ,使得可以通过 app.model.User 之类来访问 model 实例。

还有可以看到 initializer 中的判断,如果文件引入的 factory 是一个函数,就会调用 factory(app, sequelize)。

有点hack的方法来定义表

model只要一个,但是表名可以按天来命名

既然 app/model 已经有定义 model 了,那如果我想定义相同的表,就不用新弄一个文件了,直接复用这个 model,但是就需要换其他 model 的名称去 define。

因为知道前面调用这个文件的时候,会传参 app 和 sequelize,所以这里可以多配置一个参数 modelName 来可选配置表名,就像下面这样:

那如何读表呢

sequelize工具已经帮你想好了

看看 sequelize 的官网文档,可以知道:

sequelize.models.User 就能访问访问到 define 的 User 了,注意这里的 User 不是 app.model 外面文件名的User,而是 define 的 modelName。

配合前面看到插件的代码,知道 app.model 的 value 就是 sequelize 实例,所以在 egg 中就可以通过app.model.models[modelName]来访问表了

一个方法实现表创建和引用

简单易用,搞定model

因为插件机制, app.model.User 访问的都是已经存在的文件表,如果按天分表的话,就必须是动态创建和访问,所以可以在 app/extend/application.js 中进行扩展方法,例如这样:

这里为什么要 await this.model.sync({ force: false }) ,就是映射关系同步进去,就会创建表,看下官方文档:

然后在使用的地方,就使用诸如这样方法来获取 model 和使用 model 的 api 了:

但是实际运行时候还有一个问题,就是 models 上的实例是先建立映射关系,再同步到数据库的,所以 this.model.models[modelName] 存在并不代表可以立即操作 model ,如果要实时立即创建然后立即写入就会有异步问题,解决方法也很简单,不再展开。