一、背景
不知道各位在开发中是否遇到了这样的问题,就是一般在做一些查询功能的时候,特别是一些繁琐的信息采集类或者erp类系统,前段要传一大批查询条件,我们需要在代码里一遍遍的判空,然后在讲条件进去组合进去查询。
假设我们有以下实体类,前端将给我们传过来这个类,然后我们需要按照这个类的字段去数据库里查询相关数据。
import com.baomidou.mybatisplus.annotation.*;
import com.linzi.pitpat.base.utils.BeanUtil;
import lombok.Data;
import com.lz.mybatis.plugin.annotations.AS;
import java.math.BigDecimal;
import java.util.Date;import java.util.Date;
@Data
public class Movement implements java.io.Serializable {
private Integer id;
//动作名称
private String movementName;
//动作英文名称
private String movementEnName;
//动作难度
private String movementLevel;
//适用性别
private Integer applicableGender;
//动作类型
private Integer movementType;
//锻炼部位
private String exercisePart;
//锻炼肌肉
private String exerciseMuscle;
//所需器械
private String requireMachine;
//预计消耗热量
private Integer heatConsumption;
//动作视频
private String video;
//视频时长
private String videoDuration;
//封面
private String cover;
//动作介绍
private String movementIntroduction;
//备注描述
private String remark;
//
private Integer isDelete;
//
private Date gmtModified;
//
private Date gmtCreate;
}
LambdaQueryWrapper<Movement> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(po.getMovementName()), Movement::getMovementName, po.getMovementName())
.like(StringUtils.isNotBlank(po.getMovementEnName()), Movement::getMovementEnName, po.getMovementEnName())
.eq(StringUtils.isNotBlank(po.getMovementLevel()), Movement::getMovementLevel, po.getMovementLevel())
.eq(po.getApplicableGender() != null, Movement::getApplicableGender, po.getApplicableGender())
.eq(po.getMovementType() != null, Movement::getMovementType, po.getMovementType())
.ge(po.getExercisePart() != null, Movement::getExercisePart, po.getExercisePart())
.ge(po.getExerciseMuscle() != null, Movement::getExerciseMuscle, po.getExerciseMuscle())
.ge(po.getRequireMachine() != null, Movement::getRequireMachine, po.getRequireMachine())
.ge(po.getVideo() != null, Movement::getVideo, po.getVideo())
.ge(po.getCover() != null, Movement::getCover, po.getCover())
.like(po.getMovementIntroduction() != null, Movement::getMovementIntroduction, po.getMovementIntroduction())
.like(po.getRemark() != null, Movement::getRemark, po.getRemark())
.le(po.getGmtCreate() != null, Movement::getGmtCreate, po.getGmtCreate())
.orderByDesc(Movement::getGmtCreate);
Page<Movement> page = new Page<>(po.getPageNum(), po.getPageSize());
movementService.page(page, wrapper);
当做一些查询时,我们要写这种大量像抹布一样的代码,就姑且称之为抹布代码吧。其实仔细分析这些代码和我们要的业务需求,不难发现其实我们要的很简单,就是根据一些指定的字段按照指定的比较或者排序方式向数据库查询而已,而且大多数场景下这种查询是单表的。
当我们抽象出我们需要的东西后,事情就变得简单的。实体类我们有,那能不能有一种快捷的方式去指定查询实体类上每个字段的比较方式呢,是等值比较,还是模糊查询,或者说是大于小于,还有就是跟表里的那个字段比较,是否一定要比较呢?
很自然的,我们会想到用注解去实现这一idea,我们可以去写一个自定义注解,将其放在我们实体类的字段上,并表明这个字段需要跟表中的那个字段对应以及他的比较方式。
二、实现
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target( ElementType.FIELD)
public @interface Compare {
String columnName() default "default";
String compareType();
}
首先,我们拥有了自己的一个标记注解,接下来的工作就是让这个注解生效了。由于我们项目中使用的orm工具是mybatis-plus,所以下面我给一个基于mybatis-plus实现。
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.linzi.pitpat.data.annotation.Compare;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
public class MpUtil {
public static <type> QueryWrapper<type> generateWrapper(Type type, Object source){
return generateWrapper(type, source, null);
}
public static <type> QueryWrapper<type> generateWrapper(Type type, Object source, List<String> ignoreFields) {
QueryWrapper<type> wrapper = new QueryWrapper<>();
List<signNode> nodes = getSignNodes(source);
if (CollectionUtils.isNotEmpty(nodes)) {
start(wrapper, nodes, ignoreFields);
}
return wrapper;
}
//启动组装wrapper
private static <T> void start(QueryWrapper<T> wrapper, List<signNode> nodes, List<String> ignoreFields) {
for (signNode node : nodes) {
String column = node.getColumn();
Object value = node.getValue();
String symbol = node.getSymbol();
//存在忽略集
if (CollectionUtils.isNotEmpty(ignoreFields)) {
//当前是否是忽略字段
boolean isIgnoreField = ignoreFields.contains(column) || ignoreFields.contains(StrUtil.toCamelCase(column));
if (isIgnoreField)
continue;
}
try {
Method method = wrapper.getClass().getSuperclass().getDeclaredMethod(symbol, boolean.class, Object.class, Object.class);
method.setAccessible(true);
method.invoke(wrapper, true, column, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static List<signNode> getSignNodes(Object source) {
ArrayList<signNode> list = new ArrayList<>();
Class<?> cls = source.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// String不应该!=null做判空
Object value = null;
try {
value = field.get(source);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Compare compare = field.getAnnotation(Compare.class);
if (notNull(value) && compare != null) {
String columnName = compare.columnName();
String compareType = compare.compareType();
if ("default".equals(columnName)) {
columnName = StrUtil.toUnderlineCase(field.getName());
}
signNode node = new signNode();
node.setColumn(columnName);
node.setValue(value);
node.setSymbol(compareType);
list.add(node);
}
}
return list;
}
private static boolean notNull(Object value) {
return value instanceof String ? StringUtils.isNotBlank((CharSequence) value) : value != null;
}
原理也很简单,我们首先通过字段上的注解拿到我们要去比较的字段,将其包装为node,然后看他是否为空,注意,在这里各个字段判空的条件不同,比如String类型用的是StringUtils.isNotBlank(),这个由开发者自己把握。然后我们解析获取这个字段要去表中对应着那个字段,根据注解上的columnName获取,默认情况下代表驼峰转下划线。接着,我们再拿到这个字段和表中字段的比较类型,是等于呢或者是大于小于。下一步我们在判断是否有忽略字段,因为在一些情况下,我们不希望某些字段进入筛选条件中,我们只需排除他即可。
最后就easy了,我们只要薅一层mybatis-plus的羊毛即可,利用反射的方式,将相关参数天好,返回一个可以被执行的QueryWrapper。至此,我们轻轻松松拿到了我们之前写了半天抹布代码的QueryWrapper,剩下的直接用或者进一步操作就看具体的需求咯。
最爽的是这种薅mybatis-plus羊毛方式的做法扩展性极强,但凡它支持的比较符号我基本都能支持,以后哪怕他怎么更新也是万变不离其宗,任尔东西南北风,我自微风清清。
下面给一个使用实例。
@Data
public class MovementPo extends BasePagePo {
//动作名称
@Compare(compareType = "like")
private String movementName;
//动作英文名称
private String movementEnName;
//动作难度
private String movementLevel;
//适用性别 性别,1:男,2:女,0:通用
@Compare(compareType = "eq")
private Integer applicableGender;
//动作类型 0:无氧,1:有氧
private Integer movementType;
//起始时间
private Date startTime;
//结束时间
@Compare(compareType = "le",columnName = "gmt_create")
private Date endTime;
}
@Test
public void test() throws Exception {
//构造条件
MovementPo po = new MovementPo();
po.setMovementName("高抬腿");
po.setApplicableGender(0);
po.setEndTime(new Date());
//忽略比较的字段,可不传
ArrayList<String> ignores = new ArrayList<>();
ignores.add("applicable_gender");
//生成wrapper
QueryWrapper<Movement> wrapper = MpUtil.generateWrapper(Movement.class, po, ignores);
List<Movement> list = movementService.list(wrapper);
list.forEach(System.out::println);
}
Movement{,id=62,movementName=高抬腿,movementLevel=3,applicableGender=1,movementType=1,exercisePart=[10,9,8],exerciseMuscle=[[1,12],[2,15],[2,16],[2,17],[3,18],[3,19],[3,20],[4,21],[4,22],[5,23],[5,24],[5,25],[5,42],[6,26],[6,27],[6,28],[6,29],[6,30]],requireMachine=[30,29,28],heatConsumption=12,video=12,videoDuration=12,cover=34,movementIntroduction=<h1>21</h1>,remark=null,isDelete=0,gmtModified=Thu Jun 29 09:44:34 CST 2023,gmtCreate=Sat Jun 17 16:52:55 CST 2023}
Movement{,id=63,movementName=高抬腿,movementLevel=3,applicableGender=1,movementType=1,exercisePart=[10,9,8],exerciseMuscle=[[1,12],[2,15],[2,16],[2,17],[3,18],[3,19],[3,20],[4,21],[4,22],[5,23],[5,24],[5,25],[5,42],[6,26],[6,27],[6,28],[6,29],[6,30]],requireMachine=[30,29,28],heatConsumption=12,video=12,videoDuration=12,cover=34movementIntroduction=<h1>21</h1>,remark=null,isDelete=0,gmtModified=Thu Jun 29 09:44:34 CST 2023,gmtCreate=Sat Jun 17 16:52:55 CST 2023}
有兴趣的老少年们可以自己去体验一下,代码不多,用的很爽。
标签:String,private,实用,plus,mybatis,import,null,po,Movement From: https://www.cnblogs.com/fix200/p/18067188