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

本文介绍了一种使用自定义注解和反射技术优化MyBatisPlus条件封装的方法,通过解耦条件对象与封装过程,提高代码的可维护性和扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

1、自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Wrapper {
    /**
     * 条件的关键字
     */
    String keyword() default "eq";
    /**
     * 数据表列
     */
    String field() default "";
}

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

2、Between操作左右边界对象

@Data
public class Between<T> {
    private T bLeft;
    private T bRight;

    public Between(T bLeft, T bRight) {
        this.bLeft = bLeft;
        this.bRight = bRight;
    }
}

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

@Data
@Builder
public class PlFinanceCondition {

    ********省略部分**********

    /**
     * 分校编码
     * */
    @Wrapper(field = "area_code")
    private String areaCode;
    /**
     * 支付方式列表
     * */
    @Wrapper(keyword = "in",field = "pay_type")
    private List<Integer> payTypes;
    /**
     * 支付方式
     * */
    @Wrapper(field = "pay_type")
    private Integer payType;
    /**
     * 交易开始时间
     * */
    @Wrapper(keyword = "ge",field = "trade_time")
    private Date tradeTimeStart;
    /**
     * 交易结束时间
     * */
    @Wrapper(keyword = "lt",field = "trade_time")
    private Date tradeTimeEnd;
    /**
     * 交易时间 between操作
     * */
    @Wrapper(keyword = "between",field = "trade_time")
    private Between<Date> tradeTimeBetween;
    /**
     * id,trade_time正序
     * */
    @Wrapper(keyword = "orderByDesc",field = "id,trade_time")
    private Boolean orderByIdAndTradeTimeDesc;
}

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

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查询条件

public class QueryWrapperUtil {
    public static  <T> void buildQueryWrapper(PlFinanceCondition condition,QueryWrapper<T> queryWrapper){
        Field[] fields = condition.getClass().getDeclaredFields();
        for(Field field : fields){
            field.setAccessible(true);
            Wrapper wrapper = field.getAnnotation(Wrapper.class);
            if(Objects.nonNull(wrapper)){
                try {
                    Object fieldValue = field.get(condition);
                    if(Objects.nonNull(fieldValue)){
                        String keyword = wrapper.keyword();
                        WrapperInterfaceEnum.getByKeyword(keyword).doHandle(queryWrapper,wrapper,fieldValue);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

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

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

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

@DataSource(DataSourceTypes.SLAVE_PL_FINANCE)
public List<PlFinance> selectByCondition(PlFinanceCondition condition){
    QueryWrapper<PlFinance> queryWrapper = new QueryWrapper<>();
    QueryWrapperUtil.buildQueryWrapper(condition,queryWrapper);
    Page<PlFinance> page = new Page<>(1, DBConstant.MAX_NUMBER_OF_PIECES);
    IPage<PlFinance> ppFinanceIPage =  plFinanceMapper.selectPage(page,queryWrapper);
    return Lists.newArrayList();
}

6、单元测试,验证结果

@Test
public void testWrapper(){
    PlFinanceCondition condition = PlFinanceCondition.builder()
            .areaCode("1111")//eq
            .payTypes(Lists.newArrayList(103))//in
            //.tradeTimeStart(new DateTime("2020-01-01").toDate())//ge
            .tradeTimeBetween(new Between<>(new DateTime("2020-01-01").toDate(),new DateTime("2020-06-01").toDate()))//between
            .orderByIdAndTradeTimeDesc(true)//orderByDesc
            .build();
    plFinanceService.selectByCondition(condition);
}

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

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值