关联查询包含一对多和多对一的映射关系,一对多的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 // 同上
}, {........其他元素省略
如果查询出来的结果集要在前端展现,并且前端组件使用的是element-ui的可展开行的table组件,对于正常属性这么显示:
{
{ props.row.name }}
而对于像cla.rank这种嵌套属性就需要把它当成一个整体:
{
{ scope.row['cla.rank'] }}
{
{ props.row['cla.rank'] }}
不能通过scope.row.cla.rank一直点出来 ,这是错误写法