属于

声明属于关系

声明是属于关系的方法是 belongsTo

  1. Book.belongsTo(User);

Book 是主体发起模型,而 User 是目标模型。这样声明会在 Book 上面增加 setUser / createUser / User 俩个方法与一个属性,通过 options 里面的 as: 'Some',来让名称变成 setSome / createUser / Some。且在 Book 表里面会自动增加一个 UserId 的字段来记录 User 的 id 值,通过设置 options 里面的 foreignKey: 'user_id',来修改自动增加字段的名称。

倘若你十分讨厌驼峰原则,喜欢下划线分割的配置,并且期望所有表都这样,可以在初始化数据库连接的时候配置 define.underscored,它会默成为你所有 define 的 默认 options。

  1. const sequelize = new Sequelize('nodelover', 'root', '', {
  2. host: 'localhost',
  3. dialect: 'mysql',
  4. define: {
  5. underscored: true,
  6. } ,
  7. pool: {
  8. max: 5,
  9. min: 0,
  10. idle: 10000
  11. },
  12. });

小栗子

接下来,同样通过一个例子来说明属于关系。就着之前的 User 与 Book 模型定义做一些修改。

接口是支持继承的,没必要把 UserAttributes 里面的属性再写一次,直接继承一下就好了。并且为加强一下记忆,再次阐述一下属性接口与实例接口的区别attr 里面声明的是 create 创建的时候需要传递的属性,而 instance 接口里面的都是 sequelize 动态给类添加的方法与属性。

1. 新建 user.ts

  1. import Sequelize from 'sequelize';
  2. export interface UserAttributes {
  3. email: string;
  4. name: string;
  5. }
  6. export interface UserInstance extends Sequelize.Instance<UserAttributes>, UserAttributes {
  7. id: number;
  8. createdAt: Date;
  9. updatedAt: Date;
  10. say(): void;
  11. }
  12. export default function UserDefine(sequelize: Sequelize.Sequelize, dataTypes: Sequelize.DataTypes): Sequelize.Model<UserInstance, UserAttributes> {
  13. const S = dataTypes;
  14. const User = sequelize.define<UserInstance, UserAttributes>
  15. ('User', {
  16. email: S.STRING,
  17. name: S.STRING
  18. });
  19. (User as any).prototype.say = function(this: UserInstance) {
  20. console.log('name ' + this.name);
  21. };
  22. (User as any).associate = function(models){
  23. }
  24. return User;
  25. }

在模型里面,可以在静态 associate 方法来定义模型之间的关系,这是官方所推荐的,假如使用官方提供的 migrate 工具会自动执行该方法。

prototype 上面加了一个 say 实例方法,所以在 instace 接口里面声明一下 say 方法。

2.book.ts

  1. import Sequelize from 'sequelize';
  2. import {UserInstance, UserAttributes} from './User'
  3. export interface BookAttributes {
  4. status: "inSale" | "noSale";
  5. description: string;
  6. title: string;
  7. author: string;
  8. }
  9. export interface BookInstance extends Sequelize.Instance<BookAttributes>, BookAttributes{
  10. id: number;
  11. createdAt: Date;
  12. updatedAt: Date;
  13. setUser: Sequelize.BelongsToSetAssociationMixin<UserInstance, number>;
  14. createUser: Sequelize.BelongsToCreateAssociationMixin<UserAttributes>;
  15. User: Sequelize.BelongsToGetAssociationMixin<UserInstance>
  16. }
  17. export default function BookDefine(sequelize: Sequelize.Sequelize, dataTypes: Sequelize.DataTypes): Sequelize.Model<BookInstance, BookAttributes> {
  18. const S = dataTypes;
  19. const Book = sequelize.define<BookInstance, BookAttributes>('Book', {
  20. id: {
  21. type: S.INTEGER,
  22. autoIncrement: true,
  23. primaryKey: true,
  24. unique: true
  25. },
  26. description: S.TEXT,
  27. status: {
  28. type: S.ENUM,
  29. values: ['inSale', 'noSale'],
  30. validate: {
  31. isIn: {
  32. args: [['inSale', 'noSale']],
  33. msg: "status field must be inSale or noSale"
  34. }
  35. }
  36. },
  37. title:{
  38. type: S.STRING,
  39. allowNull: false,
  40. get(this: BookInstance) {
  41. return this.getDataValue('author') + ' - ' + this.getDataValue('title');
  42. }
  43. },
  44. author: {
  45. type: S.STRING,
  46. allowNull: false,
  47. set(val: string){
  48. this.setDataValue('author', val.toLowerCase());
  49. }
  50. }},{
  51. comment: "图书表", // 表注释
  52. indexes: [ // 表索引
  53. {
  54. fields: ['id']
  55. }
  56. ],
  57. classMethods: {
  58. // associate: function(models){} 第一种
  59. }
  60. });
  61. // 第二种
  62. (Book as any).associate = function(this: typeof Book, models: any){
  63. this.belongsTo(models.User);
  64. }
  65. return Book;
  66. }

特别注意 第一种写法在 v4 版本将会被移除。具体可以查看官方的如何升级到 v4版本?文档。

在 define 方法的 options 参数里是有 classMethods 配置项的,可以指定模型的静态方法,所以可以有俩种写法。这里用的是直接写,也就是第二种写法。在 associate 静态方法内, this 指的就是 Book,而传递的 models 里面有所有 define 过的模型,这里定义的关系就是, Book 模型属于 User,所以会在 Book 实例上面添加 setUser / createUser / User

sequelize 动态添加了这些方法,但是 typescript 是不知道的,所以需要自己去声明,所以就有了。

  1. setUser: Sequelize.BelongsToSetAssociationMixin<UserInstance, number>;
  2. createUser: Sequelize.BelongsToCreateAssociationMixin<UserAttributes>;
  3. User: Sequelize.BelongsToGetAssociationMixin<UserInstance>

这些 Mixin 是由定义库提供的一些接口。

3.index.ts

  1. import Sequelize from 'sequelize';
  2. const sequelize = new Sequelize('nodelover', 'root', '', {
  3. host: 'localhost',
  4. dialect: 'mysql',
  5. pool: {
  6. max: 5,
  7. min: 0,
  8. idle: 10000
  9. },
  10. });
  11. import UserDefined , { UserAttributes, UserInstance } from './user';
  12. import BookDefined, {BookAttributes, BookInstance} from './book';
  13. async function main() {
  14. try {
  15. const User = sequelize.import<UserInstance, UserAttributes>('./user');
  16. const Book = sequelize.import<BookInstance, BookAttributes>('./book');
  17. Object.keys(sequelize.models).forEach(modelName => {
  18. const model = (sequelize.models[modelName] as any)
  19. if('associate' in model){
  20. model.associate(sequelize.models)
  21. }
  22. })
  23. await sequelize.sync();
  24. let book = await Book.create({author:'alice', description: 'typescript hand book', status: 'inSale',title:'ts leaning'});
  25. let user = await User.create({email:'belovedyogurt@gmail.com',name: 'yugo'})
  26. book.setUser(user);
  27. await book.save();
  28. }catch(e){
  29. // console.log(e)
  30. if (e instanceof Sequelize.ValidationError) {
  31. console.log(e.message);
  32. }
  33. }
  34. process.exit(0)

sequelize.models 对象上面缓存了所有定义过的模型,通过以下几行代码,实现了自动调用 associate 方法,与同步数据库表。

  1. Object.keys(sequelize.models).forEach(modelName => {
  2. const model = (sequelize.models[modelName] as any)
  3. if('associate' in model){
  4. model.associate(sequelize.models)
  5. }
  6. })
  7. await sequelize.sync();

运行以上代码,在数据库里面可以看到2张表都已经有了数据。

·>_<·! Nice Work!