Mybatis-Plus
在设计之初是为了扩展而不是替代Mybatis
,所以对于连表查询官方并没有给出解决方法,还是依托Mybatis通过XML配置文件中写SQL语句的方式。但是在多数据源适配上,还是想要消除掉XML以屏蔽不同数据库类型的查询(新增加一个数据库,不需要新增加一个XML配置)。
最后采用第三方开源工具Mybatis-Plus-Join
实现连表查询,开源地址:https://github.com/yulichang/mybatis-plus-join,支持一对一、一对多的操作。
引入依赖
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.2.4</version>
<exclusions>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
</exclusions>
</dependency>
之前已经分享过关于Mybatis-Plus代码分层和改造,所以修改XwMapper
继承MPJBaseMapper
,整体对于原由的结构没有影响。Mybatis-Plus-Join
提供MPJLambdaWrapper
和MPJQueryWrapper
实现连表查询。MPJLambdaWrapper
支持Lambda表达式查询。
使用示例:
List<UserDTO> list = baseMapper.selectJoinList(UserDTO.class,
new MPJLambdaWrapper<UserDO>()
.selectAll(UserDO.class)
.select(UserAddressDO::getTel)
.selectAs(UserAddressDO::getAddress, UserDTO::getUserAddress)
.select(AreaDO::getProvince, AreaDO::getCity)
.leftJoin(UserAddressDO.class, UserAddressDO::getUserId, UserDO::getId)
.leftJoin(AreaDO.class, AreaDO::getId, UserAddressDO::getAreaId)
.eq(UserDO::getId, 1)
.like(UserAddressDO::getTel, "1")
.gt(UserDO::getId, 5));
对应的SQL语句:
SELECT t.id, t.name, t.sex, t.head_img, t1.tel, t1.address AS userAddress, t2.province, t2.city
FROM user t
LEFT JOIN user_address t1 ON t1.user_id = t.id
LEFT JOIN area t2 ON t2.id = t1.area_id
WHERE ( t.id = ? AND t1.tel LIKE ? AND t.id > ?)
对应字段说明:
字段 | 含义 |
---|---|
UserDTO | 查询返回的结果,对应ResultType |
UserDO | 查询对应的Entity |
selectAll | 查询指定实体类的全部字段 |
select | 查询指定实体的指定字段 |
leftJoin | 第一个参数参与连表的实体类class; 第二个参数连表的ON字段,这个属性必须是第一个参数实体类的属性; 第三个参数: 参与连表的ON的另一个实体类属性 |
默认主表别名是t,其他的表别名以先后调用的顺序使用t1、t2、3;同时也支持分页查询。
IPage<UserDTO> list = baseMapper.selectJoinPage(new Page<>(2, 10), UserDTO.class,
new MPJLambdaWrapper<UserDO>()
.selectAll(UserDO.class)
.select(UserAddressDO::getTel)
.selectAs(UserAddressDO::getAddress, UserDTO::getUserAddress)
.select(AreaDO::getProvince, AreaDO::getCity)
.leftJoin(UserAddressDO.class, UserAddressDO::getUserId, UserDO::getId)
.leftJoin(AreaDO.class, AreaDO::getId, UserAddressDO::getAreaId)
.eq(UserDO::getId, 1)
.like(UserAddressDO::getTel, "1")
.gt(UserDO::getId, 5));
对应的SQL语句:
SELECT t.id, t.name, t.sex, t.head_img, t1.tel, t1.address AS userAddress, t2.province, t2.city
FROM user t
LEFT JOIN user_address t1 ON t1.user_id = t.id
LEFT JOIN area t2 ON t2.id = t1.area_id
WHERE ( t.id = ? AND t1.tel LIKE ? AND t.id > ?)
LIMIT 2,10
条件查询,可以查询主表以及关联表的所有字段,调用Mybatis-Plus原生的方法,不会有SQL注入风险,但是上面的字段适合查询结果映射成单一属性的对象,当对象中包含对象或者对象中包含集合的时候,上面的方式就不适合了。
Mybatis-Plus-Join提供了@EntityMapping
和 @FieldMapping
注解用来解决非单一属性对象连表查询的处理。通过在Entity中配置好对应的映射关系,再通过baseMapper调用Deep结尾的方法即可处理,例如:查询集合使用baseMapper#selectListDeep、分页查询baseMapper#selectPageDeep。
举个栗子:
@TableName("user")
public class UserDO {
@TableId
private Integer id;
private Integer pid;
/**
* 查询上级 一对一
*/
@TableField(exist = false)
@EntityMapping(thisField = "pid", joinField = "id")
private UserDO pUser;
/**
* 查询下级 一对多
*/
@TableField(exist = false)
@EntityMapping(thisField = "id", joinField = "pid")
private List<UserDO> childUser;
/**
* 带条件的查询下级 一对多
*/
@TableField(exist = false)
@EntityMapping(thisField = "id", joinField = "pid",
condition = {
@MPJMappingCondition(column = "sex", value = "0"),//sex = '0' 默认条件是等于
@MPJMappingCondition(column = "name", value = "张三", keyWord = SqlKeyword.LIKE)//name like '%a%'
},
apply = @MPJMappingApply(value = "id between 1 and 20"))//拼接sql 同 wrapper.apply()
private List<UserDO> childUserCondition;
/**
* 查询地址 (一对多)
*/
@TableField(exist = false)
@EntityMapping(thisField = "id", joinField = "userId")
private List<UserAddressDO> addressList;
/**
* 绑定字段 (一对多)
*/
@TableField(exist = false)
@FieldMapping(tag = UserDO.class, thisField = "id", joinField = "pid", select = "id")
private List<Integer> childIds;
}
使用示例:
@Test
void test1() {
UserDO deep = userMapper.selectByIdDeep(2);
System.out.println(deep);
}
@Test
void test2() {
List<UserDO> list = userMapper.selectListDeep(Wrappers.emptyWrapper());
list.forEach(System.out::println);
}
@Test
void test3() {
Page<UserDO> page = userMapper.selectPageDeep(new Page<>(2, 2), Wrappers.emptyWrapper());
page.getRecords().forEach(System.out::println);
}
当查询映射的是实体,则使用@EntityMapping
,而单一字段结果映射则使用 @FieldMapping
,使用文档也给出了详细的说明,具体可以查看:MAPPING.md
Mybatis-Plus-Join
连表查询对于单一属性对象的连表查询能很好的支持,查看源码是通过Mybatis-Plus
,抽离成模板,通过自定义SQL注入器的方式实现。而对于非单一属性对象的查询是提供了两个注解,但底层是分成多条SQL语句采用IN查询得到结果后再聚合。
所以原先一条语句被分成了两条甚至多条语句去处理,中间多了网络开销。在1000w数据量下,对比过使用Mybatis-Plus-Join
注解的方式和使用XML的方式,Mybatis-Plus-Join
注解的方式在查询效率上会慢一倍,所以对于QPS要求比较高的系统来说,对于非单一属性对象的查询使用注解的这种方式并非是最优的。