在做一个项目的时候,使用mybatis-plus
进行数据库的操作,有个场景需要根据一个条件树生成对应的where
条件,需要根据条件树构造 条件 之间的and
、or
以及 not
逻辑关系,mybatis-plus
提供的方法支持and
、or
。但是not
函数不是 条件前面添加 NOT
关键字。因此需要进行扩展实现此需求。
Wrapper定义:
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
public abstract class Wrapper<T> implements ISqlSegment {
}
mybatis-plus
通过 ISqlSegment
生成各sql
片段,最后组装成一个sql 语句。
Wrapper主要有2个实现,有各自的使用方式。
直接使用SQL 语句片段方式。
下列示例直接编写SQL语句。
//以下代码直接设置select 语句
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.select("count(update_date) as total"
, "min(update_date) as minDate"
, "max(update_date) as maxDate");
if (ObjectUtil.isNotNull(beginDate)) {
queryWrapper.ge("update_date", beginDate);
}
List<Map<String, Object>> info = this.baseMapper.selectMaps(queryWrapper);
//以下代码 设置 过滤条件,直接进行字段编码。
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "张三");
queryWrapper.eq("age", 28);
queryWrapper.eq("last_name", null);
// 这样也可以
// queryWrapper.eq("name", "张三").eq("age", 28).eq("last_name", null);
List<User> users = userMapper.selectList(queryWrapper);
以属性方式操作数据表字段,函数方式构造过滤条件,规避了使用SQL语句方式会出现的很多错误。
下面代码示例直接以属性方式访问字段:
LambdaQueryWrapper<BaseInfo> lambdaQuery = Wrappers.<BaseInfo>query().lambda()
.ge(BaseInfo::getUpdateDate, beginDate)
.lt(BaseInfo::getUpdateDate, endDate);
return this.baseMapper.selectList(lambdaQuery);
and
和 or
@Override
public Children and(boolean condition, Consumer<Children> consumer) {
return and(condition).addNestedCondition(condition, consumer);
}
@Override
public Children or(boolean condition, Consumer<Children> consumer) {
return or(condition).addNestedCondition(condition, consumer);
}
@Override
public Children nested(boolean condition, Consumer<Children> consumer) {
return addNestedCondition(condition, consumer);
}
protected Children addNestedCondition(boolean condition, Consumer<Children> consumer) {
final Children instance = instance();
consumer.accept(instance);
return doIt(condition, LEFT_BRACKET, instance, RIGHT_BRACKET);
}
@Override
public Children or(boolean condition) {
return doIt(condition, OR);
}
protected Children and(boolean condition) {
return doIt(condition, AND);
}
protected Children not(boolean condition) {
return doIt(condition, NOT);
}
public Children notBetween(boolean condition, R column, Object val1, Object val2) {
return not(condition).between(condition, column, val1, val2);
}
protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
if (condition) {
expression.add(sqlSegments);
}
return typedThis;
}
根据源码可以发现 ,and
和 or
方法都会把条件放在子集(即括号内),然后添加 AND
、 OR
字符串。
not()
方法是protected
访问,不能访问。只是在 条件表达式前加上 NOT 字符串,并不会添加上括号()
,导致 不能达到对整个表达式取反操作。对于所有的取反,则会有 notLike
、 notBeTween
、notIn
等方法,内部调用not()
方法。
综上所述,mybatis-plus未提供NOT
逻辑运算符。
思路:
既然最终都是构造SQL语句,则实现一个子类,能够在条件前面加上NOT
,则满足要求。
public class CloudQueryWrapper<T> extends QueryWrapper<T> {
public CloudQueryWrapper() {
super();
}
//增加not()方法。参数为 一个表达式
public QueryWrapper<T> not(Consumer<QueryWrapper<T>> consumer) {
return not(true, consumer);
}
public QueryWrapper<T> not(boolean condition, Consumer<QueryWrapper<T>> consumer) {
final QueryWrapper<T> instance = instance();
consumer.accept(instance);
//必须要这么写,不然会出错
// 第一个参数是:NOT,第二个是左括号,第二个是 条件 ,第三个是右括号
// protected Children doIt(boolean condition, ISqlSegment... sqlSegments)
//参考:protected Children addNestedCondition(boolean condition, Consumer consumer) 的实现
return doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET);
}
}
/**
* 继承 LambdaQueryWrapper
* 为了暴露出 not 方法
*
* @author lihz
* @date 2021/10/13
*/
public class CloudLambdaQueryWrapper<T> extends LambdaQueryWrapper<T> {
public CloudLambdaQueryWrapper() {
super();
}
public LambdaQueryWrapper<T> not(Consumer<LambdaQueryWrapper<T>> consumer) {
return not(true, consumer);
}
public LambdaQueryWrapper<T> not(boolean condition, Consumer<LambdaQueryWrapper<T>> consumer) {
final LambdaQueryWrapper<T> instance = instance();
consumer.accept(instance);
//必须要这么写,不然会出错
return doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET);
}
/**
* 覆盖父类的方法,支持直接设置字段的名称
* @param column
* @param onlyColumn
* @return
*/
@Override
protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
if (column instanceof CloudSFunction) {
CloudSFunction<T> c = (CloudSFunction) column;
return c.apply(null);
}
return super.columnToString(column, onlyColumn);
}
/**
* 返回固定列名的接口
*
* @author lihz
* @date 2021/10/13
*/
public interface CloudSFunction<T> extends SFunction<T, String> {
}
/**
* 不建议直接 new 该实例
*/
CloudLambdaQueryWrapper(T entity, Class<T> entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq,
Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
SharedString lastSql, SharedString sqlComment) {
super.setEntity(entity);
this.paramNameSeq = paramNameSeq;
this.paramNameValuePairs = paramNameValuePairs;
this.expression = mergeSegments;
//this.sqlSelect = sqlSelect;
this.entityClass = entityClass;
this.lastSql = lastSql;
this.sqlComment = sqlComment;
}
/**
* 用于生成嵌套 sql
* 故 sqlSelect 不向下传递,直接从LambdaQueryWrapper拷贝的,用于在内部使用CloudLambdaQueryWrapper 的覆盖的方法
*/
@Override
protected LambdaQueryWrapper<T> instance() {
return new CloudLambdaQueryWrapper<>(entity, entityClass, null, paramNameSeq, paramNameValuePairs,
new MergeSegments(), SharedString.emptyString(), SharedString.emptyString());
}
}
在前面调用 doIt(condition, MybatisExtension.Not.INSTANCE, LEFT_BRACKET, instance, RIGHT_BRACKET)
,没有使用mybatis-plus
的NOT
会出现问题,因此自定义了一个
/**
* Mybatis的扩展类
*
* @author lihz
* @date 2021/10/14
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MybatisExtension {
/**
* 输出NOT,不能使用mybatis 自己的NOT,不然有问题
*/
static class Not implements ISqlSegment {
public static final Not INSTANCE = new Not();
private Not() {
}
/**
* SQL 片段
*/
@Override
public String getSqlSegment() {
return " NOT ";
}
}
}
private void f2(AbstractWrapper wrapper, Consumer<? extends AbstractWrapper> c) {
if (wrapper instanceof CloudLambdaQueryWrapper) {
//使用扩展的 CloudLambdaQueryWrapper
CloudLambdaQueryWrapper wrapper0 = (CloudLambdaQueryWrapper) wrapper;
wrapper0.not(c);
}
}
private <W extends AbstractWrapper> Consumer<W> handleLogicCriteria(LogicCriteria logicCriteria
, Map<RelationalOperatorType, TriConsumer> funcMaps
, BiConsumer<W, Consumer<W>> notFunc) {
return (W wrapper) -> {
switch (logicCriteria.getOperator()) {
case NOT:
if (CollUtil.isEmpty(logicCriteria.getChildren())) {
break;
}
if (logicCriteria.getChildren().size() > 1) {
throw new IllegalArgumentException("NOT 节点只能有一个子节点");
}
Consumer<W> c = createCriteria(logicCriteria.getChildren().get(0), funcMaps, notFunc);
//使用扩展的 CloudLambdaQueryWrapper
notFunc.accept(wrapper, c);
break;
case OR:
// OR,AND 逻辑相似,逻辑写在下面
case AND:
BiConsumer<AbstractWrapper, Consumer<? extends AbstractWrapper>> f = FUNCTION_LOGIC.get(logicCriteria.getOperator());
if (ObjectUtil.isNotNull(f)) {
for (ICriteriaNode node : logicCriteria.getChildren()) {
Consumer<? extends AbstractWrapper> wrapperChild = createCriteria(node, funcMaps, notFunc);
f.accept(wrapper, wrapperChild);
}
}
break;
default:
}
};
}
如果有更好的方式,欢迎指正!