## 需求分析
在系统内需要将某些员工的信息作出个性化处理,且不能修改原表的数据。
例如在 B 表中将 “张三” 划分到产品设计部(在A表中为产品运营),在查询 “张三” 的数据之前,先到 B 表查询,如果存在数据则不再去 A 表查询。
## 思路分析
- A 表为原表,再克隆出相同结构的 B 表 ,在 B 表中作出个性化处理。
- 既然 A、B 表的结构相同,那么可以复用 B 表的 QueryWrapper 条件来实现查询结果的一致性,虽然两个表的字段相同,但在 Java 中是两个独立的 model 对象,不能直接使用 B 表的 QueryWrapper 去查询 A 表的数据。
- 目前的思路是通过 Java 反射的特性将 A 表的 QueryWrapper 对象条件复制到 B 表的 QueryWrapper,再对 B 表进行查询。
## 实现过程
在程序执行的过程中,观察 `QueryWrapper` 对象的属性我们可以发现,在执行一系列 `.eq()` 等条件配置后写入的参数值会体现在 `expression` 属性的中

可以根据一定的规律从 `expression` 属性来拼接出 `where` 后的 sql 条件,但取值的方式需要兼容各种情况的出现,比较繁琐。
由于 QueryWrapper 又继承于 `AbstractWrapper` ,从 AbstractWrapper 提供了一个 getSqlSegment 方法可以得到拼接后的 where 条件。

所以我们调用 queryWrapper.getSqlSegment() 会得到一个拼接后的 sql 子句,如下图

`Mybatis Plus` 将参数具体的值使用 ` #{ew.paramNameValuePairs.XXX} ` 进行转义,再进行最终的 sql 语句拼接时会将其转换为具体的参数值,而参数的值则存放在 `paramNameValuePairs` 这个 `Map` 属性中

到这里我们可以整理下思路:
- 将 A 表 QueryWrapper 对象的 `paramNameValuePairs` 和 `expression` 属性赋给 B 表的 QueryWrapper 。
- 但是由于 `QueryWrapper` 类大部分属性都是私有的,并且只对外开放了 get 方法,所以我们需要通过反射来获取私有属性的值。
#### 0x01:所以我们先定义一个方法,将任意对象的属性设置为特定的值
```java
/**
* @author: Cory Lin
* @description 常用工具类
* @date: 2021/8/18 12:13 下午
*/
public class CommonUtil {
public static void setProperty(Object obj,String propertyName,Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
Class c = obj.getClass();
Field field = null;
try {
field = c.getDeclaredField(propertyName);
}catch (NoSuchFieldException e){
Class superclass = c.getSuperclass();
field = superclass.getDeclaredField(propertyName);
}
field.setAccessible(true);
field.set(obj, value);
}
}
```
这里需要注意的是,通过 getDeclaredField 方法只能获取到当前类的所有属性,并不能获取父类的属性,所以需要通过 `getSuperclass` 先获取到父类的对象,再使用 getDeclaredField 获取父类的属性,再抛出 NoSuchFieldException 异常时则表示在子类中找不到相关属性,再到父类中去找。
#### 0x02:将两个 QueryWrapper 条件值从 source 复制到 target
定义一个 `copyQueryWrapper` 方法
```java
/**
* 复制 QueryWrapper 的条件值
* @param source 源 QueryWrapper
* @param target 目标 QueryWrapper
* @param <T>
* @param <E>
*/
private <T,E> void copyQueryWrapper(QueryWrapper<E> source, QueryWrapper<T> target){
try {
CommonUtil.setProperty(target, "expression", source.getExpression());
CommonUtil.setProperty(target, "paramNameValuePairs", source.getParamNameValuePairs());
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
```
#### 0x03:重写 page 方法
- 先从 B 表中查询,在查询 A 表的条件增加 `notIn` 排除从 B 表中查询到的结果
- 合并 B 表和 A 表的查询结果
```java
@Override
public IPage<TableA> page(IPage<TableA> page, QueryWrapper<TableA> queryWrapper){
//查询自定义表
QueryWrapper<TableB> tableBQueryWrapper = new QueryWrapper<>();
this.copyQueryWrapper(queryWrapper, tableBQueryWrapper);
List<TableB> tableBInfos = tableBService.list(tableBQueryWrapper);
//在查询A表的时排除从B表查询到的数据,并将B表的数据整合到A表的 page 对象中返回
List<String> tableBIds = bonusEmployeeInfos.stream()
.map(TableB::GetUserId).collect(Collectors.toList());
queryWrapper.notIn("user_id",tableBIds)
IPage<TableA> tableAPage = super.page(page, queryWrapper);
//转为 A 表的 model
List<TableA> bToAList = tableBInfos.stream().map(b -> {
TableA a = new TableA();
BeanUtils.copyProperties(b, a);
return a;
}).collect(Collectors.toList());
tableAPage.getRecords().addAll(bToAList);
tableAPage.setTotal(tableAPage.getRecords().size());
return tableAPage;
}
```
#### 执行效果
先执行 B 表的查询,并且复用了 A 表 QueryWrapper 的查询条件

后查询 A 表,并 notIn 排除 B 表的结果

## 总结
- 通过该需求的梳理,巩固了 Java 反射相关的知识点,也开始尝试通过阅读框架的源码去解决需求中的问题
- 当然上述方案可能不是最优的,也可以通过传递查询条件的 DTO 再对 B 表的 QueryWrapper 进行配置,但这显然需要传递更多的参数,且不通用。
通过 Java 反射复用 MyBatis Plus 中 QueryWrapper 的条件值