阅读Sequelize.js (一)

1,148 阅读3分钟

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的实例。

  1. 把sequelize实例挂在到 constructor
this.sequelize = options.sequelize;
  1. 判断modelManager里面是否有当前modal 有的话就删除 走了一个hook beforeDefine(modelManager 会在后期的文章中详细讲解)
if (this.sequelize.isDefined(this.name)) {
    this.sequelize.modelManager.removeModel(
        this.sequelize.modelManager.getModel(this.name)
    );
}
  1. 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;
}
  1. 把 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);
  1. _setupHooks(hooks对象) 注册options里面的hooks的方法
this._setupHooks(options.hooks);

//在文件最下面 导出Model前已经在Hooks挂载在Model上了
//所以可以使用Hooks里面的_setupHooks
Hooks.applyTo(Model, true);
module.exports = Model;
  1. 判断了一下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}`);
  }
});
  1. 遍历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;
});
  1. 处理了options里面的索引(indexes) 挂载了constructor._indexes
const tableName = this.getTableName();
this._indexes = this.options.indexes
  .map(index => Utils.nameIndex(this._conformIndex(index), tableName));
  1. 处理了配置中的

    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);
}
  1. 增加一些个默认的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();
  1. 把当前的这个model 加入到modelManager里面
this.sequelize.modelManager.addModel(this);
  1. 跑了一个hook afterDefine
this.sequelize.runHooks('afterDefine', this);