Closed abel533 closed 1 year ago
之前做 or(...) 的时候,尝试了下,本来想使用 <sql>
和 <include>
两个标签使用递归的思路,支持任意层次的 or,但是发现 mybatis 读取解析 xml 的方法和我想象的并不一样,他会在初始化的时候直接尝试 include sql片段,而不是根据条件分支走到实际的调用后再include,因此递归的思路最终会导致栈溢出,所以结论就是 单靠 sql script 无法做到任意层次的 and
和 or
的拼接(后续或许可以尝试根据oredCriteria 实际的值,程序动态的生成 sql script 来解决无法任意层次 or 甚至是 and 的问题?)
io.mybatis.mapper.example.ExampleProvider#EXAMPLE_WHERE_CLAUSE
<where>
<foreach collection="oredCriteria" item="criteria" separator=" OR ">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="AND" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
AND ${criterion.condition}
</when>
<when test="criterion.singleValue">
AND ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
AND ${criterion.condition} #{criterion.value} AND #{criterion.secondValue}
</when>
<when test="criterion.listValue">
AND ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" separator="," close=")">
#{listItem}
</foreach>
</when>
<when test="criterion.orValue">
<!-- !!正因为这里是手拼的,所以无法支持嵌套多层次,所以在OrCriteria类中将andOr系列方法标记为了废弃,且直接抛出异常 -->
<foreach collection="criterion.value" item="criteria" separator=" OR " open = " AND (" close = ")">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="AND" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
AND ${criterion.condition}
</when>
<when test="criterion.singleValue">
AND ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
AND ${criterion.condition} #{criterion.value} AND #{criterion.secondValue}
</when>
<when test="criterion.listValue">
AND ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" separator="," close=")">
#{listItem}
</foreach>
</when>
<!-- 这里不再继续深层次的 <when test="criterion.listValue"> 了,因为常规的深层次的SQL也可以想办法改写平铺开来 -->
</choose>
</foreach>
</trim>
</if>
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
当前Example 的结构为
Example
│
│ //当调用Example.or() 或者 wrapper.or()时会创建一个新的Criteria并追加到当前oredCriteria中
│ // oredCriteria 中的每个 Criteria 使用 OR 去拼接
└─ List<Criteria<T>> oredCriteria:
│
│ //当调用 Criteria 中的每个方法,如 .andEqualTo 等都会往当前 Criteria中的criteria 集合中添加一个Criterion
│ // criteria 中的每个 Criterion 使用 AND 去拼接
└─ List<Criterion> criteria
而当前的 wrapper.or(Criteria... criterias)
(以及 Criteria 中的 andOr )的实现 其实是将当前调用这个方法的 全部入参 条件 criterias
作为了一个特殊的值添加到了 当前 Criteria 中,作为了一个单独的Criterion
, 这样就保证了
[其他Criterion] AND [其他Criterion] AND [OR条件片段的Criterion]
将这个映射带入到上面的 动态SQL的XML 最后我们自己的 [OR条件片段的Criterion]
就会命中到上面sql script中的下面一段
<when test="criterion.orValue"><!-- ... --></when>
因为 Criteria
中的每一个 Criterion
条件 本身就是 AND 去拼接,如果直接复用当前对象
优点:复用当前对象,实现简单,支持任意层级的 .and(xxx) 嵌套 以及下一个单层级的 .or(xxx) 条件
缺点:中间任意一个有 .or()
的调用都会不符合预期
/**
* 数组多个条件直接使用 and 拼接
*
* @param andParts 条件块
*/
@SafeVarargs
public final ExampleWrapper<T, I> and(Consumer<ExampleWrapper<T,I>>... andParts) {
if (andParts != null && andParts.length > 0) {
and(Arrays.asList(andParts));
}
return this;
}
/**
* 数组多个条件直接使用 and 拼接
* 因为默认就是用and拼接的,所以直接用当前 wrapper 对象就可以
*
* @param andParts 条件块
*/
public final ExampleWrapper<T, I> and(List<Consumer<ExampleWrapper<T,I>>> andParts) {
if (andParts != null && andParts.size() > 0) {
andParts.forEach(p->p.accept(this));
}
return this;
}
不包含 .or()
测试是OK的:
但是,当包含有 or()
的情况下的话,则会不符合当前的预期
为了应对方案1中的 .or()
这个问题,将 and条件组 与最外层的其他条件进行隔离,使用 OrCriteria
类屏蔽掉 .or()
方法
//ExampleWrapper 新增方法
public final ExampleWrapper<T, I> and(Function<Example.OrCriteria<T>, Example.OrCriteria<T>>... orParts) {
if (orParts != null && orParts.length > 0) {
this.current.andAnd(Arrays.stream(orParts).map(orPart -> orPart.apply(example.orPart())).collect(Collectors.toList()));
}
return this;
}
//Example.Criteria 新增方法
public Criteria<T> andAnd(List<OrCriteria<T>> orCriteriaList) {
criteria.add(new Criterion(null, orCriteriaList,null,true));
return (Criteria<T>) this;
}
//Criterion 新增 andValue 并新增构造
private boolean andValue;//新增一个标识 标记当前的 value 是否为 and 条件组
protected Criterion(String condition, Object value, String typeHandler, Boolean andValue) {
super();
this.condition = condition;
this.value = value;
if (value instanceof Collection<?>) {
if (condition != null) {
this.listValue = true;
} else {
this.orValue = true;
}
} else {
this.singleValue = true;
}
if (andValue != null) {
this.andValue = andValue;
this.orValue = !this.andValue;
}
}
然后动态SQL中可能又得再来一个
<when test="criterion.andValue"><!-- ... --></when>
优点:没有优点(或者说:阻止用户继续深层次的 .or()
调用,给用于以约束,防止出现不符合预期的情况)
缺点:代码(sql script那块)比较冗余,不支持下一层级的 .or(...)和 .and(...) 嵌套
方案2 依旧不支持在里面嵌套更深的层次,感觉有点得不偿失,除非能根据 oredCriteria 实际的值 动态的生成 sql script,支持到无限极的深度嵌套才会有意义
个人感觉实现 .and(xxx)
方法的性价比不高 ,毕竟本来最外层默认就是 and 拼接的
@abel533 看看 讨论下,是否还有必要需要继续实现?需要的话 可以讨论一下用何种方案会更合理,确定好方案之后我可以提个pr
另外 经过群里面的沟通,应该是该用户被文档的错误给误导了,使用方式上存在错误,https://github.com/mybatis-mapper/mapper-docs/pull/1 ,感觉 多条件的 .and(xxx)
应该是个伪需求
分析的非常好了,我个人感觉是没必要实现...复杂点的逻辑直接写xml更合适。
@ydq QQ群里提的需求,方便加群看看吗?
大佬 请问是否支持
将图中or改为and的写法
想要上图效果 使用or
sql是
我把这里的or 改为and就满足我的需求了
个人感觉and的场景应该是多些的 大佬下个版本or 能否加个这样的需求