MyBatis-Flex关联查询
MyBatis-Flex关联查询
在 MyBatis-Flex 中,我们内置了 3 种方案,帮助用户进行关联查询,比如 一对多
、一对一
、多对一
、多对多
等场景,他们分别是:
- 方案1:Relations 注解
- 方案2:Field Query
- 方案3:Join Query
其中理解一下使用注解形式关联查询
方案一:
在 MyBatis-Flex 中,提供了 4 个 Relations 注解,他们分别是:
- RelationOneToOne:用于一对一的场景
- RelationOneToMany:用于一对多的场景
- RelationManyToOne:用于多对一的场景
- RelationManyToMany:用于多对多的场景
一对一 @RelationOneToOne
1、什么是:RelationOneToOne
它是用来告诉 MyBatis-Flex:这两个对象之间是一对一的关系,查询账户的时候,顺便把对应的身份证也查出来。
你可以把「账户」和「身份证」看作现实生活中的「一个人」和「他的身份证」,一个人只能有一个身份证,这就是所谓的 一对一关系。代码如下所示:
Account.java :
public class Account implements Serializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@RelationOneToOne(selfField = "id", targetField = "accountId")
private IDCard idCard;
//getter setter
}
这个意思是:
- 我的
Account
(账户)类里面有一个idCard
(身份证)属性。 - 我要把
id
和IDCard
中的accountId
对上号,才能找到它的身份证。
你可以理解成「Account 的 id 是 1,就去 IDCard 表里找 accountId=1 的那张身份证。」
IDCard.java :
@Table(value = "tb_idcard")
public class IDCard implements Serializable {
private Long accountId;
private String cardNo;
private String content;
//getter setter
}
这个方法跟普通的 selectAll()
不一样,它会自动把关联的 IDCard
一起查出来。
如果你写的是:
accountMapper.selectAll(); // 这个不会查身份证,idCard 是 null
那就查不到身份证信息。
2、 查询的 SQL 是啥样的?
它其实会执行两条 SQL:
-- 查账户
SELECT id, user_name FROM tb_account;
-- 再查身份证
SELECT account_id, card_no, content FROM tb_idcard WHERE account_id IN (1,2,3,4,5);
也就是说,它帮你查了两次,然后把数据合并起来了。对应的结果如下:
[
Account{id=1, userName='孙悟空', idCard=IDCard{accountId=1, cardNo='0001', content='内容1'}},
Account{id=2, userName='猪八戒', idCard=IDCard{accountId=2, cardNo='0002', content='内容2'}},
...
]
补充说明
情况 1:IDCard
是 VO 或 DTO
如果你这个 IDCard
不是数据库实体(也就是没有 @Table
注解),比如只是个数据传输对象,那你得手动告诉它「去哪个表查」。
@RelationOneToOne(targetField = "accountId", targetTable = "tb_idcard")
总结一句话:
用
@RelationOneToOne
可以让你在查主表的时候,自动把关联的子表(比如身份证)一块查出来,少写 SQL,多做事
一对多关系@RelationOneToMany
假设你有一个账户(Account),这个账户买了很多本书(Book)。一个账户对应很多本书,这就是典型的一对多关系。代码如下:
public class Account {
private Long id;
private String userName;
@RelationOneToMany(selfField = "id", targetField = "accountId")
private List<Book> books; // 这个账户拥有的书籍列表
}
selfField = "id"
:账户自己的 idtargetField = "accountId"
:书籍表中表示“属于哪个账户”的字段
就是:我账户的 id = 1,就去 Book 表里查 accountId = 1 的所有书,执行的 SQL:
SELECT id, user_name FROM tb_account;
SELECT id, account_id, title FROM tb_book WHERE account_id IN (1, 2, 3, 4, 5);
它会自动查出每个账户的所有书,帮你组装好 List<Book>
。每个账户对象里,books
属性都是一个 List。查询结果如下:
Account{id=1, userName="孙悟空", books=[
Book{id=1, accountId=1, title="西游记"},
Book{id=2, accountId=1, title="大圣归来"}
]}
进阶用法:
Map 映射想把书存在 Map
里咋办?
复制编辑
@RelationOneToMany(selfField = "id", targetField = "accountId", mapKeyField = "id")
private Map<Long, Book> books;
- 意思是:把每本书的
id
当作 Map 的 key,Book 当作 value 存起来
{
1L -> Book{id=1, accountId=1, title="西游记"},
2L -> Book{id=2, accountId=1, title="大圣归来"}
}
是不是很方便用 key 快速访问某本书?
selfValueSplitBy 分割查询
场景举例:
病人表 Patient
里有个字段 diseaseIds
是个字符串:
比如 "1,2,3"
表示这个人有 1、2、3 号疾病。
我们希望在查出病人的时候,自动把这些疾病的名字查出来,甚至生成 List<String>
或 Map<String, Disease>
。
@RelationOneToMany(
selfField = "diseaseIds", // 是个字符串,像 "1,2,3"
selfValueSplitBy = ",", // 用逗号分割
targetTable = "tb_disease", // 从 tb_disease 表中查
targetField = "diseaseId", // 对应疾病表的主键
valueField = "name" // 我只要疾病名就行
)
private List<String> diseaseNameList;
还可以这样:
@RelationOneToMany(
selfField = "diseaseIds",
selfValueSplitBy = ",",
targetField = "diseaseId",
mapKeyField = "diseaseId"
)
private Map<String, Disease> diseaseMap;
执行的 SQL
-- 查病人
SELECT patient_id, name, disease_ids, tag_ids FROM tb_patient;
-- 再查疾病信息
SELECT disease_id, name FROM tb_disease WHERE disease_id IN ('1','2','3');
查询结果
{
"patientId": 4,
"name": "赵六",
"diseaseIds": "1,2,3",
"diseaseNameList": [
"心脑血管疾病",
"消化系统疾病",
"神经系统疾病"
],
"diseaseMap": {
"1": {"diseaseId": "1", "name": "心脑血管疾病"},
"2": {"diseaseId": "2", "name": "消化系统疾病"},
"3": {"diseaseId": "3", "name": "神经系统疾病"}
}
}
是不是很神奇?你只存了个 "1,2,3"
的字符串,它就帮你拆开、查表、拼好返回。总结一句话:
@RelationOneToMany
:一对多,查主表时自动加载子表列表(List/Map 都行);mapKeyField
:可以让子表结果以 Map 的形式返回;selfValueSplitBy
:把主表字段按逗号、斜杠等切割成多个值,查子表用;valueField
:只要子表中的某个字段,直接返回成 List,比如 List。
多对一 @RelationManyToOne
场景举例
- 一个账户(Account)可以拥有很多本书(Book)——这是“一对多”;
- 从书籍(Book)的角度来看,每本书只属于一个账户——这就是“多对一”。
所以现在我们站在 Book(多) 的角度,去查它所属的 Account(一),就是“多对一”。
账户类(Account)——还是很简单的主表:
public class Account implements Serializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
// getter / setter
}
书籍类(Book)——配置多对一关系:
@Table(value = "tb_book")
public class Book implements Serializable {
@Id(keyType = KeyType.Auto)
private Long id;
private Long accountId; // 外键,关联账户
private String title;
@RelationManyToOne(selfField = "accountId", targetField = "id")
private Account account; // 多对一:一本书关联一个账户
// getter / setter
}
注解说明
selfField = "accountId"
:表示书籍里这个字段是“关联账户的 ID”;targetField = "id"
:表示账户类的主键;- 因为账户类的主键是
id
,所以可以简写为:
@RelationManyToOne(selfField = "accountId")
查询方式
List<Book> books = bookMapper.selectAllWithRelations();
注意: selectAllWithRelations()
必须使用这个方法,MyBatis-Flex 才会自动加载关联关系。
SQL 实际执行
-- 查书本
SELECT id, account_id, title FROM tb_book;
-- 查账户
SELECT id, user_name FROM tb_account WHERE id IN (1, 2, 3);
系统会自动分析你有哪些 accountId
,一次性查出相关账户信息,组装进去。
查询结果展示
Book{id=1, title="Java 编程", accountId=1, account={
id=1, userName="孙悟空"
}}
Book{id=2, title="Spring Boot", accountId=2, account={
id=2, userName="猪八戒"
}}
你只写了一个 accountId
,系统就帮你查到了完整的 Account
对象。
总结一句话:
@RelationManyToOne
是从“子表”反向查“主表”的配置方式,常见于像“书籍 -> 账户”,“订单 -> 用户”,“评论 -> 帖子”这样的场景。
多对多 @RelationManyToMany
场景举例
我们来看一个经典的业务场景:
一个账户(Account)可以拥有多个角色(Role),比如“管理员”、“用户”、“VIP”;
一个角色(Role)也可以被多个账户拥有,比如“管理员”可能对应了孙悟空、猪八戒两个账户。
这个就是一个标准的 多对多关系。
多对多实现过程?
多对多在数据库中,不能直接一张表对另一张表,所以需要用一张 中间表 来“桥接”两边的关系。
中间表叫做:tb_role_mapping
,结构如下:
CREATE TABLE tb_role_mapping (
account_id INTEGER,
role_id INTEGER
);
作用:
记录“哪个账户”关联了“哪个角色”。
比如:
account_id | role_id |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
这表示:
- 账户1有角色1和2
- 账户2有角色1
实体类写法
Account.java
public class Account implements Serializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@RelationManyToMany(
joinTable = "tb_role_mapping", // 中间表
joinSelfColumn = "account_id", // 中间表中对 account 的字段
joinTargetColumn = "role_id" // 中间表中对 role 的字段
)
private List<Role> roles; // 多个角色
// getter / setter
}
Role.java
@Table(value = "tb_role")
public class Role implements Serializable {
private Long id;
private String name;
// getter / setter
}
注解说明
属性 | 说明 |
---|---|
joinTable | 中间表的名字(桥梁) |
joinSelfColumn | 中间表里对应当前类(Account)的字段 |
joinTargetColumn | 中间表里对应目标类(Role)的字段 |
selfField 和 targetField | 当前类 和 目标类 的主键字段名,如果默认是 id ,可以省略 |
所以只写:
@RelationManyToMany(
joinTable = "tb_role_mapping",
joinSelfColumn = "account_id",
joinTargetColumn = "role_id"
)
就够了,MyBatis-Flex 会自动去找 id
字段。
查询示例
List<Account> accounts = accountMapper.selectAllWithRelations();
执行的 SQL 类似于:
-- 查账户
SELECT id, user_name FROM tb_account;
-- 查中间表
SELECT account_id, role_id FROM tb_role_mapping WHERE account_id IN (1, 2, 3);
-- 查角色表
SELECT id, name FROM tb_role WHERE id IN (1, 2, 3);
查询结果
[
Account{
id=1,
userName='孙悟空',
roles=[
Role{id=1, name='管理员'},
Role{id=2, name='VIP'}
]
},
Account{
id=2,
userName='猪八戒',
roles=[
Role{id=1, name='管理员'}
]
}
]
MyBatis-Flex 会自动:
- 查出账户;
- 用中间表找到账户对应的角色ID;
- 再查出对应的角色列表,自动装配进来。
总结一句话:
@RelationManyToMany
就是用来处理“多对多”的关系,通过一个中间表桥接两个实体,MyBatis-Flex 自动帮你搞定 SQL 和关系映射。