使用sequelize实现关联查询
关联查询包含一对多和多对一的映射关系,一对多的API为belongsTo,多对一的API为hasMany。使用sequelize进行关联查询时,要根据具体情况选择用哪一个映射关系,因为这其中涉及左右连接、内外连接的性能问题。下面来看一个一对多的例子,当然你可以将这个例子反过来多对一,但是性能会差很多。所以我们要根据数据库表的设计来选择谁关联谁
假设有一个Student表包含id、name、age、address、gender、c_id这几个属性,还有一个Class表包含id、class_id、name、rank这几个属性。student表的c_id对应的是class表的class_id,由于历史原因,student表的c_id并未关联class表的主键id;所以在sequelize中建立映射关系时要特别注意指定class表的关联字段,否则默认关联class表的主键id。
首先定义student和class的model:
export const StudentModel = sequelize.define('student', { id: { type: Sequelize.BIGINT, field: 'id', primaryKey: true, autoIncrement: true }, name: { type: Sequelize.STRING, field: 'name' }, cId: { type: Sequelize.BIGINT, field: 'c_id' }, // 其他属性省略。。。。 }, { timestamps: false, tableName: 'xxx_student', }); export const ClassModel = sequelize.define('class', { id: { type: Sequelize.BIGINT, field: 'id', primaryKey: true, autoIncrement: true }, classId: { type: Sequelize.BIGINT, field: 'class_id' }, // 其他属性省略。。。。 }, { timestamps: false, tableName: 'xxx_class', });
建立映射关系,由于student表的c_id字段值都可以在class表的class_id字段值中找到,所以student对于class是一对多的关系:
// student表与class表根据cId关联查询 StudentModel.belongsTo(ClassModel, {as: 'cla', foreignKey: 'cId', targetKey: 'classId'});
这里需要注意as属性是为class表起别名,如果你的表名太长,这个属性就会非常方便。foreignKey是student表的外键,如果不指定targetKey属性,默认关联的是class表的主键id,但这并不符合我们的需求,所以需要指定targetKey为classId;另外当我们定义了model以后,每个字段在数据库中的样子是以下划线分割命名的,例如c_id,在model中我们使用了驼峰命名法重新对字段进行了命名,那么接下来sequelize所有的API使用都是使用model中的新字段名,他会自动和数据库表字段映射上,所以我们不管是foreignKey还是targetKey的值都是新字段名。
接下来开始真正的关联查询:
// findAndCountAll这个API既查找还统计满足条件的记录数 await StudentModel.findAndCountAll({ where: criteria, // 这里传入的是一个查询对象,因为我的查询条件是动态的,所以前面构建好后才传入,而不是写死 offset: start, // 前端分页组件传来的起始偏移量 limit: Number(pageSize), // 前端分页组件传来的一页显示多少条 include: [{ // include关键字表示关联查询 model: ClassModel, // 指定关联的model as:'cla', // 由于前面建立映射关系时为class表起了别名,那么这里也要与前面保持一致,否则会报错 attributes: [['name','className'], 'rank'], // 这里的attributes属性表示查询class表的name和rank字段,其中对name字段起了别名className }], raw:true // 这个属性表示开启原生查询,原生查询支持的功能更多,自定义更强,注意,开启此项,日期格式化会失效 }).then( result => { total = result.count; // 如果成功,则可以获取到记录条数 list = result.rows; // 如果成功,则可以获取到记录集合 }).catch(err => { getLogger().error("xxx-findAndCountAll occur error:", err); });
关于对class表起的别名cla,你可以在nodejs项目控制台中看到sequelize 转换后的sql语句来验证。上面查询返回的是一个数组,我们来看一下其中一个元素的结构:
{ "code": 200, "data": { "total": 136, "list": [{ "id": 11, "name": "zhangsan", "age": 14, "address": "xxxxxx", "gender": 1, "cId": 3, "cla.className": "86重点班", // 还记得我们之前为class起了别名cla嘛,这里属性前面都带cla. "cla.rank": 1 // 同上 }, {........其他元素省略