美文网首页Node.js
Sequelize入门

Sequelize入门

作者: 忍不住的k | 来源:发表于2020-10-15 15:53 被阅读0次

    资源:

    Sequelize 中文文档
    sequelize API
    sequelize 小技巧
    sequelize 菜鸟教程

    核心概念:

    连接数据库:

    const { Sequelize } = require('sequelize');
    
    // 方法 1: 传递一个连接 URI
    const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
    const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例
    
    // 方法 2: 分别传递参数 (sqlite)
    const sequelize = new Sequelize({
      dialect: 'sqlite',
      storage: 'path/to/database.sqlite'
    });
    
    // 方法 2: 分别传递参数 (其它数据库)
    const sequelize = new Sequelize('database', 'username', 'password', {
      host: 'localhost',
      dialect: /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
    });
    

    模型定义:

    sequelize.define('model_name',{filed:value})

    创建表:

    首先定义模型: model
    然后同步:model.sync()
    创建表会自动创建主键,默认为 id

    增删改查:

    • 新增数据:model.create 相当于 build save两步合并;
    • 批量新增:model.bulkCreate([model,...],{...}) ;
      但是默认不会运行验证器,需要手动开启
     User.bulkCreate([
      { username: 'foo' },
      { username: 'bar', admin: true }
    ], { validate: true,//手动开启验证器
    fields: ['username']//限制字段 
    });
    // 因为限制了字段只存username,foo 和 bar 都不会是管理员.
    
    • 更新 model.update
      相当于 set, save两步合并,通常就直接修改实例属性,然后save()更新;
    • 部分更新:
      通过传递一个列名数组,可以定义在调用 save 时应该保存哪些属性
      save({fields:[ 'name',... ]}) 只更新数组里面的字段
    • 删除 model.destroy
    • 重载实例:model.reload
    • 查询
      include参数 对应sql的 join连接操作
      findAll 查找所有的
      findByPk 根据主键查找
      findOne 找到第一个实例
      findOrCreate 查找到或创建实例
      findAndCountAll 分页查找
    查询的选项参数
    Model.findAll({
    //查询指定字段
      attributes: ['foo', 'bar',
      [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']//函数聚合
    ]  ,
     where: {//对应where子句,过滤
        authorId: 2,
       authorId: {
          [Sequelize.Op.eq]: 2  //操作符运算
        }
      },
    order:[], //排序
    group:'name',//分组
    limit:10,//限制
    offset:1//页
    });
    

    实用方法:

    count,max, min 和 sum

    原始查询:

    const { QueryTypes } = require('sequelize');
    const users = await sequelize.query("SELECT * FROM `users`");//参数是sql语句
    

    偏执表:

    Sequelize 支持 paranoid 表的概念
    这意味着删除记录时不会真的删除,而是给字段deletedAt值设置为时间戳
    删除的时候默认是软删除,而不是硬删除

    class Post extends Model {}
    Post.init({ /* 这是属性 */ }, {
      sequelize,
      paranoid: true,// 传递该参数,创建偏执表
      // 如果要为 deletedAt 列指定自定义名称
      deletedAt: 'destroyTime'
    });
    

    强制删除:

    await Post.destroy({
      where: {
        id: 1
      },
      force: true //硬删除
    });
    

    软删除的实例,恢复:

    post.restore();
    
    Post.restore({
      where: {
        likes: {
          [Op.gt]: 100
        }
      }
    });
    

    查询包含软删除的记录:

    await Post.findAll({
      where: { foo: 'bar' },
      paranoid: false
    });
    

    关联类型:

    对应 sql语句的 foreign key 进行表关联
    HasOne BelongsTo HasMany BelongsToMany

    const A = sequelize.define('A', /* ... */);
    const B = sequelize.define('B', /* ... */);
    
    A.hasOne(B); // A 有一个 B ,外键在目标模型(B)中定义
    A.belongsTo(B); // A 属于 B  ,外键在目标模型(A)中定义
    A.hasMany(B); // A 有多个 B 外键在目标模型(B)中定义
    A.belongsToMany(B, { through: 'C' }); // A 属于多个 B , 通过联结表 C
    

    A.belongsToMany(B, { through: 'C' }) 关联意味着将表 C 用作联结表,在 AB 之间存在多对多关系. 具有外键(例如,aIdbId). Sequelize 将自动创建此模型 C(除非已经存在),并在其上定义适当的外键.

    创建标准关系:
    • 创建一个 一对一 关系, hasOnebelongsTo 关联一起使用;
    • 创建一个 一对多 关系, hasMany he belongsTo 关联一起使用;
    • 创建一个 多对多 关系, 两个 belongsToMany 调用一起使用.

    添加到实例的特殊方法:

    创建关联关系后,这些模型的实例会获得特殊方法
    例如:有两个模型 FooBar 拥有关联关系,则根据关联类型拥有以下可用方法;

    Foo.hasOne(Bar) 和 Foo.belongsTo(Bar)#
    • fooInstance.getBar()
    • fooInstance.setBar()
    • fooInstance.createBar()
    Foo.hasMany(Bar) 和 Foo.belongsToMany(Bar, { through: Baz })#
    • fooInstance.getBars()
    • fooInstance.countBars()
    • fooInstance.hasBar()
    • fooInstance.hasBars()
    • fooInstance.setBars()
    • fooInstance.addBar()
    • fooInstance.addBars()
    • fooInstance.removeBar()
    • fooInstance.removeBars()
    • fooInstance.createBar()

    多对多关系:

    代码分析:

    1. 创建表Foo,Bar ,设置为多对多,中间表为Foo_Bar
    2. sequelize.sync();同步到数据库,就是说如果模型对应的表不存在就创建
      插入数据 foo, bar
    3. foo.addBar(bar) ,foo 关联了一个bar,反映到数据库上面,则是中间表Foo_Bar插入一条数据 INSERT INTO Foo_Bar (FooId,BarId) VALUES(1,1)
    4. Foo.findOne({ include: Bar });数据查询,根据模型,查出Foo表的第一条数据,
      并带上关联表数据,字段是Bars(因为是多对多,所以这里是复数形式,每一条bar包含中间表的数据字段是 Foo_Bar
    const Foo = sequelize.define('Foo', { name: DataTypes.TEXT });
    const Bar = sequelize.define('Bar', { name: DataTypes.TEXT });
    Foo.belongsToMany(Bar, { through: 'Foo_Bar' });
    Bar.belongsToMany(Foo, { through: 'Foo_Bar' });
    
    await sequelize.sync();
    const foo = await Foo.create({ name: 'foo' });
    const bar = await Bar.create({ name: 'bar' });
    await foo.addBar(bar);// foo这条数据关联了一条bar,反映到表上则是在中间表Foo_Bar上插入一条数据
    const fetchedFoo =await  Foo.findOne({ include: Bar });
    console.log(JSON.stringify(fetchedFoo, null, 2));
    

    输出:

    {
      "id": 1,
      "name": "foo",
      "Bars": [
        {
          "id": 1,
          "name": "bar",
          "Foo_Bar": {
            "FooId": 1,
            "BarId": 1
          }
        }
      ]
    }
    

    高级关联概念

    预先加载:

    查询方法中使用 include 参数完成预先加载,翻译成sql其实就是 通过join关联子句;

    创建关联:

    可以一次性创建带关联关系的数据

    高级M:N关联:

    超级多对多:

    超级多对多创建出来的表跟多对多一样,没什么区别,区别就是一次使用6个关联,然后就可以进行各种预先加载

    //模型:
    const User = sequelize.define('user', {
      username: DataTypes.STRING,
      points: DataTypes.INTEGER
    }, { timestamps: false });
    
    const Profile = sequelize.define('profile', {
      name: DataTypes.STRING
    }, { timestamps: false });
    //自定义中间表
    const Grant = sequelize.define('grant', {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        allowNull: false
      },
      selfGranted: DataTypes.BOOLEAN
    }, { timestamps: false });
    
    // 超级多对多关系
    User.belongsToMany(Profile, { through: Grant });
    Profile.belongsToMany(User, { through: Grant });
    User.hasMany(Grant);
    Grant.belongsTo(User);
    Profile.hasMany(Grant);
    Grant.belongsTo(Profile);
    

    这样,我们可以进行各种预先加载:

    // 全部可以使用:
    User.findAll({ include: Profile });
    Profile.findAll({ include: User });
    User.findAll({ include: Grant });
    Profile.findAll({ include: Grant });
    Grant.findAll({ include: User });
    Grant.findAll({ include: Profile });
    

    多态关联:

    多态关联,就是说一个联结表的外键关联多个表
    由于外键引用了多个表,无法添加REFERENCES约束,需要禁用约束constraints: false

    一对多的多态关联:

    考虑模型 Image Video Comment ,
    图片,视频都可以有多个评论,
    但是一个评论只能是图片跟视频中的其中一种类型;

    // Helper 方法
    const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`;
    
    class Image extends Model {}
    Image.init({
      title: DataTypes.STRING,
      url: DataTypes.STRING
    }, { sequelize, modelName: 'image' });
    
    class Video extends Model {}
    Video.init({
      title: DataTypes.STRING,
      text: DataTypes.STRING
    }, { sequelize, modelName: 'video' });
    
    class Comment extends Model {
      getCommentable(options) {//获取评论关联类型的那个实例
        if (!this.commentableType) return Promise.resolve(null);
        const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`;
        return this[mixinMethodName](options);
      }
    }
    Comment.init({
      title: DataTypes.STRING,
      commentableId: DataTypes.INTEGER,
      commentableType: DataTypes.STRING
    }, { sequelize, modelName: 'comment' });
    
    Image.hasMany(Comment, {
      foreignKey: 'commentableId',
      constraints: false,
      scope: {//关联作用域  commentableType = 'image'
        commentableType: 'image'
      }
    });
    Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false });
    
    Video.hasMany(Comment, {
      foreignKey: 'commentableId',
      constraints: false,
      scope: {//关联作用域  commentableType = 'video'
        commentableType: 'video'
      }
    });
    Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false });
    
    Comment.addHook("afterFind", findResult => {
      console.log('afterFind,findResult=====',findResult);
      if (!Array.isArray(findResult)) findResult = [findResult];
      for (const instance of findResult) {
        if (instance.commentableType === "image" && instance.image !== undefined) {
          instance.commentable = instance.image;
        } else if (instance.commentableType === "video" && instance.video !== undefined) {
          instance.commentable = instance.video;
        }
        // 防止错误:
         delete instance.image;
         delete instance.dataValues.image;
         delete instance.video;
         delete instance.dataValues.video;
      }
    });
    
    多对多多态关联:
    class Tag extends Model {
      getTaggables(options) {
        const images = await this.getImages(options);
        const videos = await this.getVideos(options);
        // 在单个 taggables 数组中合并 images 和 videos
        return images.concat(videos);
      }
    }
    Tag.init({
      name: DataTypes.STRING
    }, { sequelize, modelName: 'tag' });
    
    // 在这里,我们明确定义联结模型
    class Tag_Taggable extends Model {}
    Tag_Taggable.init({
      tagId: {
        type: DataTypes.INTEGER,
        unique: 'tt_unique_constraint'
      },
      taggableId: {
        type: DataTypes.INTEGER,
        unique: 'tt_unique_constraint',
        references: null
      },
      taggableType: {
        type: DataTypes.STRING,
        unique: 'tt_unique_constraint'
      }
    }, { sequelize, modelName: 'tag_taggable' });
    
    Image.belongsToMany(Tag, {
      through: {
        model: Tag_Taggable,
        unique: false,
        scope: {//注意这里的作用域用于关联模型,因为这scope参数在through下
          taggableType: 'image'
        }
      },
      foreignKey: 'taggableId',
      constraints: false
    });
    Tag.belongsToMany(Image, {
      through: {
        model: Tag_Taggable,
        unique: false
      },
      foreignKey: 'tagId',
      constraints: false
    });
    
    Video.belongsToMany(Tag, {
      through: {
        model: Tag_Taggable,
        unique: false,
        scope: {//注意这里的作用域用于关联模型,因为这scope参数在through下
          taggableType: 'video'
        }
      },
      foreignKey: 'taggableId',
      constraints: false
    });
    Tag.belongsToMany(Video, {
      through: {
        model: Tag_Taggable,
        unique: false
      },
      foreignKey: 'tagId',
      constraints: false
    });
    
    在目标模型上应用作用域

    我们还可以在目标模型上应用关联作用域. 我们甚至可以同时进行,以下实例:

    Image.belongsToMany(Tag, {
      through: {
        model: Tag_Taggable,
        unique: false,
        scope: {
          taggableType: 'image'
        }
      },
      scope: {
        status: 'pending'
      },
      as: 'pendingTags',
      foreignKey: 'taggableId',
      constraints: false
    });
    

    其他主题

    事务:

    Sequelize 支持两种使用事务的方式:

    1. 非托管事务: 提交和回滚事务应由用户手动完成(通过调用适当的 Sequelize 方法).
    2. 托管事务: 如果引发任何错误,Sequelize 将自动回滚事务,否则将提交事务. 另外,如果启用了CLS(连续本地存储),则事务回调中的所有查询将自动接收事务对象.
    非托管事务:
    // 首先,我们开始一个事务并将其保存到变量中
    const t = await sequelize.transaction();
    
    try {
    
      // 然后,我们进行一些调用以将此事务作为参数传递:
    
      const user = await User.create({
        firstName: 'Bart',
        lastName: 'Simpson'
      }, { transaction: t });
    
      await user.addSibling({
        firstName: 'Lisa',
        lastName: 'Simpson'
      }, { transaction: t });
    
      // 如果执行到此行,且没有引发任何错误.
      // 我们手动提交事务.
      await t.commit();
    
    } catch (error) {
    
      // 如果执行到达此行,则抛出错误.
      // 我们回滚事务.
      await t.rollback();
    
    }
    
    托管事务:
    try {
    
      const result = await sequelize.transaction(async (t) => {
    
        const user = await User.create({
          firstName: 'Abraham',
          lastName: 'Lincoln'
        }, { transaction: t });
    
        await user.setShooter({
          firstName: 'John',
          lastName: 'Boothe'
        }, { transaction: t });
    
        return user;
    
      });
    
      // 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果
      // `result` 就是从事务回调中返回的结果(在这种情况下为 `user`)
    
    } catch (error) {
    
      // 如果执行到此,则发生错误.
      // 该事务已由 Sequelize 自动回滚!
    
    }
    

    作用域:

    不同于关联作用域, 作用域定义在模型中,帮助重用代码
    作用域在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 默认作用域除外,该作用域只能是一个对象

    class Project extends Model {}
    Project.init({
      // 属性
    }, {
      defaultScope: {//默认作用域
        where: {
          active: true
        }
      },
      scopes: {
        deleted: {
          where: {
            deleted: true
          }
        },
        activeUsers: {
          include: [
            { model: User, where: { active: true } }
          ]
        },
        random() {
          return {
            where: {
              someNumber: Math.random()
            }
          }
        }
    },
        sequelize,
        modelName: 'project'
      
    });
    await Project.scope('deleted').findAll(); //用法就是调用scope方法传入字符串,返回一个查询对象
    SELECT * FROM projects WHERE deleted = true // sql
        await Project.scope('random', { method: ['accessLevel', 19] }).findAll();
    SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19// sql
    

    相关文章

      网友评论

        本文标题:Sequelize入门

        本文链接:https://www.haomeiwen.com/subject/kzhppktx.html