需求分析
在系统内需要将某些员工的信息作出个性化处理,且不能修改原表的数据。
例如在 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:所以我们先定义一个方法,将任意对象的属性设置为特定的值
/**
* @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 方法
/**
* 复制 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 表的查询结果
@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对象,以实现不同表间条件值的共享。在不修改原表数据的情况下,针对特定需求,如员工信息个性化处理,通过反射获取并复制QueryWrapper的属性,从而实现查询的一致性。文中详细讲解了实现过程,包括获取私有属性值,条件值复制以及查询逻辑的调整。
1378

被折叠的 条评论
为什么被折叠?



