# mybatis-plus-relations
**Repository Path**: zhuyunlong2018/mybatis-plus-relations
## Basic Information
- **Project Name**: mybatis-plus-relations
- **Description**: mybatis-plus的关联查询,使用注解方式操作
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 0
- **Created**: 2022-03-21
- **Last Updated**: 2024-12-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
### mybatis-plus-relations
### mybatis-plus 关联查询
#### 简介
通过在entity或者vo的属性注解,可以关联查询并自动组装数据,不需要写任何xml和sql语句,生成的sql语句不使用join,而是使用索引in查询,java的stream进行组装,解决mybatis多表关联时可能存在的n+1问题
使用mybatis-plus的接口,可以和现有项目无侵入整合
#### 源码
[zhuyunlong2018/mybatis-plus-relations: mybatis-plus的关联模型查询插件 (github.com)](https://github.com/zhuyunlong2018/mybatis-plus-relations)
[mybatis-plus-relations: mybatis-plus的关联查询,使用注解方式操作 (gitee.com)](https://gitee.com/zhuyunlong2018/mybatis-plus-relations)
### 使用方法
添加maven依赖
```xml
io.gitee.zhuyunlong2018
mybatis-plus-relations-core
1.0.4
```
#### 1. 添加扫描注解包
给SpringBoot入口程序添加@RelationScan注解即可,包名可以实现和@MapperScan一样的通配符操作,*代表一级通配符,**代表可以多级通配符,用于扫描含有关联查询注解的entity或者vo等
```java
@SpringBootApplication
@MapperScan(basePackages = "com.gitee.zhuyunlong2018.mybatisplusrelations.**.mapper")
@RelationScan({
"com.gitee.zhuyunlong2018.mybatisplusrelations.*.vo",
"com.gitee.zhuyunlong2018.mybatisplusrelations.entity"
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
#### 2. 给关联属性添加注解
```java
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@ToString(callSuper = true)
public class DeptVo extends Dept {
// 通过注解绑定关联,localProperty为当前entity的关联属性,foreignProperty为被关联进来的entity的关联属性
@BindMany(localProperty = "deptId", foreignProperty = "deptId")
private List users;
public DeptVo(Dept dept) {
super(dept);
}
}
```
#### 注解说明
##### 注解类别
- @BindOne 绑定一对一关系
- @BindMany 绑定一对多关系
- @ManyBindMany 绑定多对多关系
##### 以上三个注解都有以下属性
- localProperty 主表字段的entity属性
- foreignProperty 副表字段的entity属性
- applySql 中间追加的sql语句,一般用于追加where,调用方法为LambdaQueryWrapper的apply方法
- lastSql 末尾追加的sql语句,调用LambdaQueryWrapper的last方法
##### 其中,@ManyBindMany多出以下几个属性
- linkModel 中间表模型类
- linkLocalProperty 中间表链接主表的字段的entity属性,如何属性名和localProperty,可以不设置
- linkForeignProperty 中间表链接副表的字段的entity属性,如何属性名和foreignProperty,可以不设置
- linkApplySql 中间表的中间追加的sql语句,同applySql,不过是用于中间表过滤
- linkLastSql 中间表末尾追加sql语句,同lastSql,作用于中间表过滤
- iterateLinkMethod 主表entity可以设置的迭代器方法,接收中间表过滤的List
###### 注意,被关联模型(entity)必须是mybatis-plus的Model的子孙类
### 查询注入
#### 一对一查询bindOne(一个用户对应一个部门)
先在UserVO添加@BindOne注解绑定关联方式
```java
@Data
@NoArgsConstructor
public class UserVO extends User {
@BindOne(localProperty = "deptId", foreignProperty = "id")
private Dept dept;
}
```
service中进行绑定
```java
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService {
/**
* 查询单个用户信息
*/
@Override
public UserVO getOneUser(Integer userId) {
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getId, userId);
UserVO userVo = EntityUtils.toObj(getOne(wrapper), UserVO::new);
// 此处进行绑定操作
Relations.with(userVo).bindOne(UserVO::getDept).end();
return userVo;
}
/**
* 批量查询用户信息
*/
@Override
public List getUserByList() {
// 先查询用户信息(表现形式为列表)
List user = list(Wrappers.emptyWrapper());
List userVos = user.stream().map(UserVO::new).collect(toList());
// 此处进行绑定操作
Relations.with(userVos).bindOne(UserVO::getDept).end();
return userVos;
}
}
```
#### 一对多查询bindMany(其中一个部门有多个用户)
先在模型进行绑定@BindMany注解
```java
@Data
public class DeptVO extends Dept {
@BindMany(localProperty = "id", foreignProperty = "deptId")
private List users;
public DeptVO(Dept dept) {
super(dept);
}
}
```
service中进行绑定
```java
/**
* @author explore
* @since 2021/05/24 11:09
**/
@Service
public class DeptServiceImpl extends ServiceImpl implements IDeptService {
/**
* 查询单个部门
*/
@Override
public DeptVO getOneDept(Integer deptId) {
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(Dept.class).eq(Dept::getId, deptId);
DeptVO deptVo = EntityUtils.toObj(getOne(wrapper), DeptVO::new);
// 此处进行绑定操作
Relations.with(deptVo).bindMany(DeptVO::getUsers).end();
return deptVo;
}
/**
* 查询多个部门
*/
@Override
public List getDeptByList() {
List deptVos = EntityUtils.toList(list(Wrappers.emptyWrapper()), DeptVO::new);
// 此处进行绑定操作
Relations.with(deptVos).bindMany(DeptVO::getUsers).end();
return deptVos;
}
}
```
#### 多对多查询ManyBindMany (每个用户拥有多项技能)
先在模型进行绑定@ManyBindMany注解
```
@Data
@NoArgsConstructor
public class UserVO extends User {
@ManyBindMany(
localProperty = "id",
foreignProperty = "id",
linkModel = UserSkillRelation.class,
linkLocalProperty = "userId",
linkForeignProperty = "skillId",
iterateLinkMethod = "setUserSkillScope"
)
private List skills;
/**
* 中间表迭代器 relations的size总是保持和skills的size一样
* @param relations
*/
public void setUserSkillScope(List relations) {
for (int i = 0; i < relations.size(); i++) {
this.skills.get(i).setScore(relations.get(i).getScore());
}
}
}
```
service中进行绑定
```java
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService {
/**
* 查询单个用户信息
*/
@Override
public UserVO getOneUser(Integer userId) {
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getId, userId);
UserVO userVo = EntityUtils.toObj(getOne(wrapper), UserVO::new);
// 此处进行绑定操作
Relations.with(userVo).manyBindMany(UserVO::getSkills).end();
return userVo;
}
/**
* 批量查询用户信息
*/
@Override
public List getUserByList() {
// 先查询用户信息(表现形式为列表)
List user = list(Wrappers.emptyWrapper());
List userVos = user.stream().map(UserVO::new).collect(toList());
// 此处进行绑定操作
Relations.with(userVos).manyBindMany(UserVO::getSkills).end();
return userVos;
}
}
```
##### 多对多绑定特有属性ManyBindMany
@ManyBindMany可以实现多对多绑定,并且实现了IManyBindHandler接口的linkQuery方法,可以对中间表的query进行过滤查询
用户和技能表示多对多关联,在manyBindMany后,可以进行linkQuery,此方法传递的是中间表的LambdaQueryWrapper,可以对中间表进行过滤检索
```java
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService {
@Override
public List getUserByList() {
List user = list(Wrappers.emptyWrapper());
List userVos = user.stream().map(UserVO::new).collect(toList());
// 此步骤可以有多个
Relations.with(userVos)
.manyBindMany(UserVO::getSkills)
.linkQuery((LambdaQueryWrapper wrapper) -> {
wrapper.gt(UserSkillRelation::getScore, 90);
})
.end();
return userVos;
}
}
```
##### iterateLinkMethod迭代器
@ManyBindMany注解有一个iterateLinkMethod迭代器属性,可以传入主模型的一个方法,对中间表进行迭代操作,详情可以查阅上面的模型代码,让模型可以获取到中间表的数据进行一些操作
#### 关联表追加query
@BindOne、@BindMany、@ManyBindMany三个注解除了在注解时可以追加applySql和lastSql外,还可以在service中进行绑定,如下
```java
@Service
public class DeptServiceImpl extends ServiceImpl implements IDeptService {
@Override
public List getDeptByList() {
// 按条件查询部门信息
List deptVos = EntityUtils.toList(list(Wrappers.emptyWrapper()), DeptVO::new);
// query方法可以过滤副表user的ID为1
Relations.with(deptVos)
.bindMany(DeptVO::getUsers)
.query(wrapper -> wrapper.eq(User::getUserId, 1))
.end();
return deptVos;
}
}
```
#### 深度绑定
IBinder提供一个deepBinder的方法,可以将副表的绑定器继续绑定新的关联属性,实现递归绑定操作
模型关系,一个用户有多项技能关联表,每个技能关联表UserSkillVO又关联了一个技能属性Skill
```java
@Data
@NoArgsConstructor
public class UserVO extends User {
@BindMany(localProperty = "id", foreignProperty = "userId")
private List userSkills;
}
```
```java
@Data
public class UserSkillVO extends UserSkillRelation {
@BindOne(localProperty = "skillId", foreignProperty = "id")
private Skill skill;
}
```
具体业务代码,用户模型先bindMany检索出所有的关联数据,再通过deepWith获得副表的Binder,追加绑定副表的关系
```java
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService {
@Override
public UserVO getOneUser(Integer userId) {
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getId, userId);
UserVO userVo = EntityUtils.toObj(getOne(wrapper), UserVO::new);
Relations.with(userVo)
.bindMany(UserVO::getUserSkills)
.deepWith(binder -> {
binder.bindOne(UserSkillVO::getSkill)
.end();
})
.end();
return userVo;
}
}
```
### 具体实现
具体包含以下几个步骤:
1. 解析@RelationScan的包路劲下的所有类,并扫描到含有@BindOne、@BindMany、@ManyBindMany的注解信息
2. 将注解信息放入缓存器RelationCache中缓存起来
3. 当业务层调用Relations.with方法时候,返回一个绑定器IBinder
4. IBinder绑定器根据注入数据的类型和属性返回一个具体的关联处理器IHandler
5. 关联处理器IHander进行副表或中间表的queryWrapper构建和数据查询,并组装好返回业务层
6. 如果需要绑定多个关联属性,可以继续调用IBinder的其他bind方法构建新的属性绑定器
### 常见问题
- 出现java.lang.ClassCastException,可能是项目使用了spring-boot-devtools,可以在src/main/resources/META-INF/spring-devtools.
properties添加如下,或者移除spring-boot-devtools
```shell
restart.include.relations=/io.gitee.zhuyunlong2018.*.jar
```
- 出现java.lang.NullPointerException
1. 可能被关联模型entity或者vo没有无惨构造函数,可提添加@NoArgsConstructor注解或无参构造函数
2. 注解参数localProperty填写错误
- 出现java.lang.NoSuchMethodException,注解参数foreignProperty填写错误
### 参考资料
- 扫描包路劲注解使用 mprelation:https://github.com/dreamyoung/mprelation.git
- 测试用数据结构直接使用 mybatisplus-joinquery:https://gitee.com/java-spring-demo/mybatisplus-joinquery.git
- 部分代码参考:https://gitee.com/xiaokedamowang/bilibili
#### deploy
mvn clean deploy -pl mybatis-plus-relations-core -am
如果终端无法弹出密码输入框,出现`gpg: signing failed: Inappropriate ioctl for device`错误,运行如下命令
```shell
export GPG_TTY=$(tty)
```