使用Mybatisplus时-动态构建QueryWrapper:自定义注解+反射

如果您在项目使用过mybatisplus,想必有过这样的麻烦,mybatisplus中条件是通过QueryWrapper进行封装,根据不同的业务来确定使用哪种操作符,比如eq,ge,lt等等。比如我们具有相近业务的条件使用同一个条件对象,这样一来就会产生一种非常臃肿的代码结构,并且每增加一个条件,都会牵扯QueryWrapper构造条件的改动,比如下面这样的情形:

只要改动PlFinanceCondition这个条件属性,这个条件的封装就会随之更新,耦合强亦不优雅。为了解决这个问题,我的优化方案为:自定义注解+反射来动态构建QueryWrapper

1、自定义注解

 
  1. @Retention(RetentionPolicy.RUNTIME)

  2. @Target(ElementType.FIELD)

  3. public @interface Wrapper {

  4. /**

  5. * 条件的关键字

  6. */

  7. String keyword() default "eq";

  8. /**

  9. * 数据表列

  10. */

  11. String field() default "";

  12. }

说明:注解条件对象,标明其属性使用哪种操作符(默认是eq)和属性对应的数据表的列名

2、Between操作左右边界对象

 
  1. @Data

  2. public class Between<T> {

  3. private T bLeft;

  4. private T bRight;

  5.  
  6. public Between(T bLeft, T bRight) {

  7. this.bLeft = bLeft;

  8. this.bRight = bRight;

  9. }

  10. }

3、条件对象中使用该注解

 
  1. @Data

  2. @Builder

  3. public class PlFinanceCondition {

  4.  
  5. ********省略部分**********

  6.  
  7. /**

  8. * 分校编码

  9. * */

  10. @Wrapper(field = "area_code")

  11. private String areaCode;

  12. /**

  13. * 支付方式列表

  14. * */

  15. @Wrapper(keyword = "in",field = "pay_type")

  16. private List<Integer> payTypes;

  17. /**

  18. * 支付方式

  19. * */

  20. @Wrapper(field = "pay_type")

  21. private Integer payType;

  22. /**

  23. * 交易开始时间

  24. * */

  25. @Wrapper(keyword = "ge",field = "trade_time")

  26. private Date tradeTimeStart;

  27. /**

  28. * 交易结束时间

  29. * */

  30. @Wrapper(keyword = "lt",field = "trade_time")

  31. private Date tradeTimeEnd;

  32. /**

  33. * 交易时间 between操作

  34. * */

  35. @Wrapper(keyword = "between",field = "trade_time")

  36. private Between<Date> tradeTimeBetween;

  37. /**

  38. * id,trade_time正序

  39. * */

  40. @Wrapper(keyword = "orderByDesc",field = "id,trade_time")

  41. private Boolean orderByIdAndTradeTimeDesc;

  42. }

说明:在每个属性上添加注解,标识属性使用哪种操作和对应数据表列名称

4、通过反射调用不同的操作方法

QueryWrapper的父类AbstractWrapper实现了4个接口,分别为Compare、Nested、Join和Func,其中我们常用的操作方法(比如eq、ge、gt、lt、in等)都在Compare和Func中,所以我这里就处理这两种接口的方法

4.1、通过枚举类抽象一个接口,两种类型Compare、Func

public enum WrapperInterfaceEnum {
Compare(Wrapper.KeywordsEnum.Compare.getKeyword()){
@Override
public <T> void doHandle(QueryWrapper<T> queryWrapper,Wrapper wrapper,Object entityValue) throws Exception {
Class compareClass = WrapperInterfaceEnum.getClass(this.name(),queryWrapper);
if(Wrapper.KeywordsEnum.Compare_Ext.getKeyword().contains(wrapper.keyword())){
//例:default Children between(R column, Object val1, Object val2)
Method m = compareClass.getDeclaredMethod(wrapper.keyword(),Object.class,Object.class,Object.class);
Between between = (Between)entityValue;
m.invoke(queryWrapper,wrapper.field(),between.getBLeft(),between.getBRight());
}else{
//例:default Children eq(R column, Object val)
Method m = compareClass.getDeclaredMethod(wrapper.keyword(),Object.class,Object.class);
m.invoke(queryWrapper,wrapper.field(),entityValue);
}
}
},
Func(Wrapper.KeywordsEnum.Func.getKeyword()){
@Override
public <T> void doHandle(QueryWrapper<T> queryWrapper,Wrapper wrapper,Object entityValue) throws Exception {
****省略*****
}
}
};
private List<String> keyword;

****省略*****

private static <T> Class getClass(String enumName,QueryWrapper<T> queryWrapper) throws ClassNotFoundException {
Class[] classes = queryWrapper.getClass().getSuperclass().getInterfaces();
Class compareClass = null;
for(Class c : classes){
if(enumName.equals(c.getSimpleName())){
compareClass = Class.forName(c.getName());
break;
}
}
return compareClass;
}

/**
* 每个枚举类型不同的实现逻辑
@param queryWrapper 封装查询条件
@param wrapper 注解条件对象的注解类
@param entityValue 条件中对应值
* */
public abstract <T> void doHandle(QueryWrapper<T> queryWrapper,Wrapper wrapper,Object entityValue) throws Exception;
}

说明:该枚举类定义了一个抽象方法doHandle,每个枚举类型通过不同实现来调用不同的AbstractWrapper实现接口的操作方法。该枚举有两种类型,分别对应Compare和Func接口,每个类型都对应有一个操作符列表

Compare(Lists.newArrayList("eq","ge","gt","le","lt","like","notLike","likeLeft","likeRight","between"))

Func(Lists.newArrayList("in","notIn","groupBy","orderByAsc","orderByDesc"))

这里有个关键点,通过当前实例获取其父类实现的接口类型Class[] classes = queryWrapper.getClass().getSuperclass().getInterfaces();

4.2、封装工具类,来使用该枚举类,构造QueryWrapper查询条件

 
  1. public class QueryWrapperUtil {

  2. public static <T> void buildQueryWrapper(PlFinanceCondition condition,QueryWrapper<T> queryWrapper){

  3. Field[] fields = condition.getClass().getDeclaredFields();

  4. for(Field field : fields){

  5. field.setAccessible(true);

  6. Wrapper wrapper = field.getAnnotation(Wrapper.class);

  7. if(Objects.nonNull(wrapper)){

  8. try {

  9. Object fieldValue = field.get(condition);

  10. if(Objects.nonNull(fieldValue)){

  11. String keyword = wrapper.keyword();

  12. WrapperInterfaceEnum.getByKeyword(keyword).doHandle(queryWrapper,wrapper,fieldValue);

  13. }

  14. } catch (Exception e) {

  15. e.printStackTrace();

  16. }

  17. }

  18. }

  19. }

  20. }

说明:根据操作符来确定使用枚举类型,调用对应的doHandle

WrapperInterfaceEnum.getByKeyword(keyword).doHandle(queryWrapper,wrapper,fieldValue)

5、使用方式,通过工具类调用对应的方法

 
  1. @DataSource(DataSourceTypes.SLAVE_PL_FINANCE)

  2. public List<PlFinance> selectByCondition(PlFinanceCondition condition){

  3. QueryWrapper<PlFinance> queryWrapper = new QueryWrapper<>();

  4. QueryWrapperUtil.buildQueryWrapper(condition,queryWrapper);

  5. Page<PlFinance> page = new Page<>(1, DBConstant.MAX_NUMBER_OF_PIECES);

  6. IPage<PlFinance> ppFinanceIPage = plFinanceMapper.selectPage(page,queryWrapper);

  7. return Lists.newArrayList();

  8. }

6、单元测试,验证结果

 
  1. @Test

  2. public void testWrapper(){

  3. PlFinanceCondition condition = PlFinanceCondition.builder()

  4. .areaCode("1111")//eq

  5. .payTypes(Lists.newArrayList(103))//in

  6. //.tradeTimeStart(new DateTime("2020-01-01").toDate())//ge

  7. .tradeTimeBetween(new Between<>(new DateTime("2020-01-01").toDate(),new DateTime("2020-06-01").toDate()))//between

  8. .orderByIdAndTradeTimeDesc(true)//orderByDesc

  9. .build();

  10. plFinanceService.selectByCondition(condition);

  11. }

操作符和设置的值对应并生效

总结:通过优化,在使用上我只需关注条件对象本身,而无需关心条件封装的过程,解开条件和封装过程的耦合,降低了因改变条件而没有改变封装过程带来错误的风险。现在要修改条件,只需要在条件对象中增减属性,修改对应注解的操作符即可,nice!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值