Sequelize.js 介绍
Sequelize是用于Postgres,MySQL,MariaDB,SQLite和Microsoft SQL Server的基于承诺的Node.js ORM。它具有可靠的事务支持,关系,急切和延迟加载,读取复制等功能。
本文采用的是MySQL作为数据库, 由于javascript弱类型语言,我们在阅读js源码时可能会比较吃力,我这边推荐一种方法,将我们的代码跑起来然后在边阅读的时候边在框架中答应输出,可以很好的帮助我们的学习和阅读。
本章核心问题
阅读学习sequelize 核心代码 Model 的静态方法init做了什么事情
准备工作
安装sequelize mysql2
yarn add mysql2 sequelize
git clone https://github.com/sequelize/sequelize.git // 下载sequelize的源码 放在lib文件夹下 以便我们进行打印输出
Schema代码如下
const {
Model,
DataTypes,
Sequelize
} = require('./lib/sequelize');
const sequelize = new Sequelize('db', 'root', null, {
host: 'localhost', // 数据库地址
dialect: 'mysql', // 指定连接的数据库类型
pool: {
max: 5, // 连接池中最大连接数量
min: 0, // 连接池中最小连接数量
idle: 10000 // 如果一个线程 10 秒钟内没有被使用过的话,那么就释放线程
}
});
class User extends Model {
get fullName() {
return this.username + ' ' + this.birthday;
}
set fullName(value) {
const names = value.split(' ');
this.setDataValue('firstname', names.slice(0, -1).join(' '));
this.setDataValue('lastname', names.slice(-1).join(' '));
}
}
User.init({
userName: {
type: DataTypes.STRING,
field: "username"
},
birthday: DataTypes.DATE,
get test() {
return 'test'
}
}, {
sequelize,
indexes: [{
unique: true,
fields: ['username', 'email']
}],
validate: {
bothCoordsOrNone() {
if ((this.username === null) !== (this.birthday === null)) {
throw new Error('Require either both latitude and longitude or neither')
}
}
},
setterMethods: {
fullName(value) {
const names = value.split(' ');
this.setDataValue('firstname', names.slice(0, -1).join(' '));
this.setDataValue('lastname', names.slice(-1).join(' '));
}
},
modelName: 'user'
});
废话不多说 直接开始吧
在上面的代码中,我们主要是使用了Model的一个init方法,来定义了Schema 打开这个sequelize 的model文件 我们找到init方法。
- init(attributes,options)
if (!options.sequelize) {
throw new Error('No Sequelize instance passed');
}
可以看到 init中传入了两个参数,第一个schema就是我们定义的User的Schema,第二个参数options中我们传入了sequelize的实例。
- 把sequelize实例挂在到 constructor
this.sequelize = options.sequelize;
- 判断modelManager里面是否有当前modal 有的话就删除 走了一个hook beforeDefine(modelManager 会在后期的文章中详细讲解)
if (this.sequelize.isDefined(this.name)) {
this.sequelize.modelManager.removeModel(
this.sequelize.modelManager.getModel(this.name)
);
}
- model名字 如果没有用modelName 就用class的名字 用了Object defineProperty 修改了class 名字
if (!this.options.tableName) {
this.tableName = this.options.freezeTableName
? this.name
: Utils.underscoredIf(Utils.pluralize(this.name), this.underscored);
} else {
this.tableName = this.options.tableName;
}
- 把 options 挂载在了constructor 上对象options 更改了默认配置
this.options = Object.assign({
timestamps: true,
validate: {},
freezeTableName: false,
underscored: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: null,
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: {},
indexes: []
}, options);
- _setupHooks(hooks对象) 注册options里面的hooks的方法
this._setupHooks(options.hooks);
//在文件最下面 导出Model前已经在Hooks挂载在Model上了
//所以可以使用Hooks里面的_setupHooks
Hooks.applyTo(Model, true);
module.exports = Model;
- 判断了一下schema内部的字段不能和validate里面的方法名相同(Object.prototype.hasOwnProperty)
_.each(options.validate, (validator, validatorType) => {
if (Object.prototype.hasOwnProperty.call(attributes, validatorType)) {
throw new Error(`
A model validator function must not have the same name as a field. Model: ${this.name}, field/validation name: ${validatorType}`
);
}
if (typeof validator !== 'function') {
throw new Error(`Members of the validate option must be functions. Model: ${this.name}, error with validate member ${validatorType}`);
}
});
- 遍历schema字段 使用sequelize.normalizeAttribute 转换了一下字段 挂载在constructor.rawAttributes
this.rawAttributes = _.mapValues(attributes, (attribute, name) => {
attribute = this.sequelize.normalizeAttribute(attribute);
if (attribute.type === undefined) {
throw new Error(`Unrecognized datatype for attribute "${this.name}.${name}"`);
}
if (attribute.allowNull !== false && _.get(attribute, 'validate.notNull')) {
throw new Error(`Invalid definition for "${this.name}.${name}", "notNull" validator is only allowed with "allowNull:false"`);
}
if (_.get(attribute, 'references.model.prototype') instanceof Model) {
attribute.references.model = attribute.references.model.getTableName();
}
return attribute;
});
- 处理了options里面的索引(indexes) 挂载了constructor._indexes
const tableName = this.getTableName();
this._indexes = this.options.indexes
.map(index => Utils.nameIndex(this._conformIndex(index), tableName));
-
处理了配置中的
primaryKeys _readOnlyAttributes _timestampAttributes _timestampAttributes - createdAt updatedAt deletedAt _readOnlyAttributes(Set) - createdAt updatedAt deletedAt version _versionAttribute - version _hasReadOnlyAttributes = _readOnlyAttributes.size
this.primaryKeys = {};
this._readOnlyAttributes = new Set();
this._timestampAttributes = {};
// setup names of timestamp attributes
if (this.options.timestamps) {
if (this.options.createdAt !== false) {
this._timestampAttributes.createdAt = this.options.createdAt || 'createdAt';
this._readOnlyAttributes.add(this._timestampAttributes.createdAt);
}
if (this.options.updatedAt !== false) {
this._timestampAttributes.updatedAt = this.options.updatedAt || 'updatedAt';
this._readOnlyAttributes.add(this._timestampAttributes.updatedAt);
}
if (this.options.paranoid && this.options.deletedAt !== false) {
this._timestampAttributes.deletedAt = this.options.deletedAt || 'deletedAt';
this._readOnlyAttributes.add(this._timestampAttributes.deletedAt);
}
}
// setup name for version attribute
if (this.options.version) {
this._versionAttribute = typeof this.options.version === 'string' ? this.options.version : 'version';
this._readOnlyAttributes.add(this._versionAttribute);
}
- 增加一些个默认的attributes
// 把createdAt updatedAt deletedAt id加入到rawAttributes里面
this._addDefaultAttributes();
//将get set 挂在到 prototype 上的 _customGetters _customSetters
- 将options里面的getterMethods 和 getterMethods 还有所有字段创建get set 都封装在 attributeManipulation
- 将rawAttributes 字段重新组装 用到了sequelize.normalizeDataType
- this.fieldAttributeMap 为需要修改的 不一样的字段 {username:"userName"} username数据库的字段 => userName 展示的字段
- 判断了字段名不能让你重写 model上的方法 把constructor 上的 rawAttributes 挂载了一份到 prototype上
this.refreshAttributes();
//挂载了一个自增的属性名字 autoIncrementAttribute
this._findAutoIncrementAttribute();
- 把当前的这个model 加入到modelManager里面
this.sequelize.modelManager.addModel(this);
- 跑了一个hook afterDefine
this.sequelize.runHooks('afterDefine', this);