MyBatis-Flex多表关联查询指南分享
作者:友莘居士
MyBatis-Flex 提供了强大且灵活的多表关联查询支持,其核心是通过 QueryWrapper 和 Relations 注解来实现的。与 MyBatis-Plus 等框架的关联查询不同,MyBatis-Flex 的关联查询是在不涉及 JOIN 的 SQL 的情况下,通过多次查询来实现的,这种方式在某些场景下(如分页、大数据量)反而有更好的性能表现。
MyBatis-Flex 的多表关联主要分为两种类型:
- 基于
QueryWrapper的关联查询(动态、手动) - 基于
@Relation注解的关联查询(静态、自动)
1. 基于QueryWrapper的关联查询 (Manual)
这种方式非常灵活,你可以通过 QueryWrapper 动态地指定关联关系和查询条件。它主要通过 select(...) 和 leftJoin/innerJoin 等方法来实现,但其底层机制依然是分开查询再组合。
核心语法示例:
假设我们有 Account(账户)表和 Book(书籍)表,一个账户对应多本书。
// 1. 查询账户及其书籍(1对多)
QueryWrapper query = QueryWrapper.create()
.select() // 查询主表(Account)的所有字段
.from(Account.class)
// 使用 leftJoin 或 innerJoin 定义关联关系(注意:这只是定义关系,不生成SQL JOIN)
.leftJoin(Book.class).on(Account::getId).eq(Book::getAccountId))
// 添加查询条件
.where(Account::getUserName).like("张三")
.and(Book::getPublishDate).ge(LocalDate.now().minusYears(1));
// 执行查询
// 注意:这里返回的仍然是 List<Account>,但MyBatis-Flex会通过额外查询自动填充其Book列表
List<Account> accounts = accountMapper.selectListByQuery(query);
关键点:
leftJoin(Book.class)和on(...)方法定义了Account和Book之间的关联关系。
MyBatis-Flex 看到这个定义后,会执行以下步骤:
- 首先查询满足条件的
Account列表。 - 然后根据
on条件中定义的关联关系(Account.id = Book.account_id),再发起一次查询,获取所有相关的Book。 - 最后,自动将
Book列表设置到对应Account对象的books属性中(需要你有这个属性)。
2. 基于@Relation注解的关联查询 (Auto)
这种方式是声明式的。你需要在实体类的字段上使用 @Relation 注解来定义关联关系,之后在查询时,MyBatis-Flex 会自动帮你完成数据的填充。这是最常用和推荐的方式。
@Relation 注解支持多种关联类型:
| 关联类型 | 注解值 | 说明 |
|---|---|---|
| 一对一 | RelationType.OneToOne | 例如:一个员工对应一个工位 |
| 多对一 | RelationType.ManyToOne | 例如:多本书属于同一个作者 |
| 一对多 | RelationType.OneToMany | 例如:一个作者拥有多本书 |
| 多对多 | RelationType.ManyToMany | 例如:一个学生选修多门课程,一门课程有多个学生 |
语法与示例
a. 一对多 (OneToMany)
在 Account 实体类中,定义一个 List<Book> 字段,并加上注解。
public class Account {
private Long id;
private String userName;
// 关键:使用 @Relation 注解定义一对多关系
@Relation(
value = RelationType.OneToMany, // 关联类型
targetField = "accountId" // 目标表(Book)中的关联字段
)
private List<Book> books;
// ... getters and setters
}
public class Book {
private Long id;
private String title;
private Long accountId; // 这个字段与 Account.id 关联
// ... getters and setters
}
查询时,直接使用普通查询即可,关联数据会自动填充:
// 无需在 QueryWrapper 中写 join,直接查
QueryWrapper query = QueryWrapper.create()
.where(Account::getUserName).eq("张三");
List<Account> accounts = accountMapper.selectListByQuery(query);
// 此时,每个 accounts 中的 Account 对象,其 books 属性已被自动填充
b. 多对一 (ManyToOne)
在 Book 实体类中,定义一个 Account 字段。
public class Book {
private Long id;
private String title;
private Long accountId;
// 关键:使用 @Relation 注解定义多对一关系
@Relation(
value = RelationType.ManyToOne, // 关联类型
targetField = "accountId", // 当前表(Book)中的关联字段
// 指定目标表的关联字段,默认为 id,可省略
targetFieldBind = "id"
)
private Account account;
// ... getters and setters
}
查询书籍时,作者信息会自动填充:
List<Book> books = bookMapper.selectAll(); // 每个 Book 对象的 account 属性已被自动填充
c. 多对多 (ManyToMany)
多对多需要一个中间表。例如 Student, Course, StudentCourse。
public class Student {
private Long id;
private String name;
@Relation(
value = RelationType.ManyToMany,
// 中间表实体类
targetTable = "tb_student_course",
// 当前对象在中间表中的字段
selfField = "id",
// 当前对象在中间表中的字段(selfField)对应的中间表字段
selfTargetField = "student_id",
// 目标对象在中间表中的字段(targetField)对应的中间表字段
targetTargetField = "course_id",
// 目标表实体类
targetTableField = "tb_course",
// 目标对象的关联字段(通常是id)
targetField = "id"
)
private List<Course> courses;
}
3. 关联查询与条件
无论是哪种方式,你都可以轻松地添加关联表的查询条件。
使用 QueryWrapper 添加关联表条件:
QueryWrapper query = QueryWrapper.create()
.select()
.from(Account.class)
// 定义关联关系
.leftJoin(Book.class).on(Account::getId).eq(Book::getAccountId))
// 主表条件
.where(Account::getUserName).like("张")
// 关联表条件!!!
.and(Book::getPrice).gt(new BigDecimal("50.00"))
.and(Book::getStatus).eq(1);
List<Account> accounts = accountMapper.selectListByQuery(query);
使用 @Relation 时,通过 extraCondition 添加额外条件:
public class Account {
@Relation(
value = RelationType.OneToMany,
targetField = "accountId",
// 额外条件:只查询状态为已发布的书籍
extraCondition = "status = 1 and price > 50"
)
private List<Book> publishedExpensiveBooks;
}
总结对比
| 特性 | 基于 QueryWrapper | 基于 @Relation 注解 |
|---|---|---|
| 灵活性 | 高,可动态构建复杂关联 | 中,关系在代码中预先定义 |
| 便捷性 | 低,每次查询需手动编写关联 | 高,定义一次,随处使用 |
| 代码侵入性 | 无,不修改实体类 | 有,需要在实体类中添加注解和字段 |
| 适用场景 | 动态、复杂的关联查询 | 固定的、常见的关联关系 |
最佳实践建议:
- 对于业务中固定的关联关系(如查询用户时总要显示他的订单),使用
@Relation注解。 - 对于动态的、一次性的复杂关联查询(如根据多个动态条件关联查询),使用
QueryWrapper手动关联。 - 灵活处理复杂关联使用基于QueryWrapper的关联查询 (Manual),配置即可实现关联查询使用基于 @Relation注解的关联查询 (Auto)
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
