tk.mybatis多层括号嵌套SQL查询
tk.mybatis多层括号嵌套SQL查询
- 现状
- 解决思路
- 具体实现
- 1、扩展`tk.mybatis.mapper.entity.Example`实现父子节点
- 2、将父子层次结构转为单层循环接口
- 定义枚举`XyzSqlNodeType `
- 定义`SQL`节点类
- 层接结构转换为水平结构
- 在`XyzExample`中增加`SQL`节点
- 判断结构转换是否正确,输出SQL语句
- 单元测试一下
- 3、将`nodes`在XML文件中体现
- 新建一个Mapper
- 实际使用
- 定义实际Mapper,扩展自`BaseXyzExampleMapper`
这里考虑的是单表复杂逻辑查询,多层括号嵌套
现状
tk.mybatis.mapper.provider.ExampleProvider#selectByExample
这里是selectByExample底层SQL操作的方法
public String selectByExample(MappedStatement ms) {Class<?> entityClass = getEntityClass(ms);//将返回值修改为实体类型setResultType(ms, entityClass);StringBuilder sql = new StringBuilder("SELECT ");if (isCheckExampleEntityClass()) {sql.append(SqlHelper.exampleCheck(entityClass));}sql.append("<if test=\"distinct\">distinct</if>");//支持查询指定列sql.append(SqlHelper.exampleSelectColumns(entityClass));sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));sql.append(SqlHelper.exampleWhereClause());sql.append(SqlHelper.exampleOrderBy(entityClass));sql.append(SqlHelper.exampleForUpdate());return sql.toString();}
tk.mybatis.mapper.mapperhelper.SqlHelper#exampleWhereClause
这里是where条件拼接内容
public static String exampleWhereClause() {return "<if test=\"_parameter != null\">" +"<where>\n" +" ${@tk.mybatis.mapper.util.OGNL@andNotLogicDelete(_parameter)}" +" <trim prefix=\"(\" prefixOverrides=\"and |or \" suffix=\")\">\n" +" <foreach collection=\"oredCriteria\" item=\"criteria\">\n" +" <if test=\"criteria.valid\">\n" +" ${@tk.mybatis.mapper.util.OGNL@andOr(criteria)}" +" <trim prefix=\"(\" prefixOverrides=\"and |or \" suffix=\")\">\n" +" <foreach collection=\"criteria.criteria\" item=\"criterion\">\n" +" <choose>\n" +" <when test=\"criterion.noValue\">\n" +" ${@tk.mybatis.mapper.util.OGNL@andOr(criterion)} ${criterion.condition}\n" +" </when>\n" +" <when test=\"criterion.singleValue\">\n" +" ${@tk.mybatis.mapper.util.OGNL@andOr(criterion)} ${criterion.condition} #{criterion.value}\n" +" </when>\n" +" <when test=\"criterion.betweenValue\">\n" +" ${@tk.mybatis.mapper.util.OGNL@andOr(criterion)} ${criterion.condition} #{criterion.value} and #{criterion.secondValue}\n" +" </when>\n" +" <when test=\"criterion.listValue\">\n" +" ${@tk.mybatis.mapper.util.OGNL@andOr(criterion)} ${criterion.condition}\n" +" <foreach close=\")\" collection=\"criterion.value\" item=\"listItem\" open=\"(\" separator=\",\">\n" +" #{listItem}\n" +" </foreach>\n" +" </when>\n" +" </choose>\n" +" </foreach>\n" +" </trim>\n" +" </if>\n" +" </foreach>\n" +" </trim>\n" +"</where>" +"</if>";}
总结: 从源代码查看,复杂括号逻辑的SQL是不支持的;而且在xml文件中通过include
来实现嵌套逻辑会导致死递归
解决思路
1、一层循环,把每一个括号、AND、OR、条件作为一个节点循环输出
2、扩展tk.mybatis
,能用的轮子继续用
- 输入
(type = '4')
AND ((((status = '1'and user_id = 1)))OR (((status = '2'and user_id = 2)))
)
- 输出
List<String> list = Arrays.asList("(", "type='4'", ")", " AND ");# xml中循环
<foreach item="item" collection="items" index="index"><choose><when test="左括号">(</when><when test="右括号">)</when><when test="AND符号"> AND </when><when test="OR符号"> OR </when><when test="条件">${name} = #{value}</when></choose><otherwise></otherwise>
<foreach>
具体实现
1、扩展tk.mybatis.mapper.entity.Example
实现父子节点
这里自定了层级结构的
groupCriteriaList
字段,放弃原Example
中的oredCriteria
字段
package org.x.y.z.ext;import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.EntityTable;
import tk.mybatis.mapper.mapperhelper.EntityHelper;import java.util.*;
import java.util.stream.Collectors;public class XyzExample<T> extends tk.mybatis.mapper.entity.Example {public XyzExample(Class<T> entityClass) {super(entityClass);this.tableName = getEntityTable(entityClass);}/*** 表名*/protected String tableName;public String getTableName() {return tableName;}/*** 层级SQL*/protected List<XyzExample.GroupCriteria> groupCriteriaList = new ArrayList<XyzExample.GroupCriteria>();protected String getEntityTable(Class<?> entityClass) {EntityTable entityTable = EntityHelper.getEntityTable(entityClass);return entityTable.getName();}public List<String> getAllColumns() {Set<EntityColumn> columnSet = EntityHelper.getColumns(entityClass);if (CollectionUtils.isEmpty(columnSet)) {return new ArrayList<String>();}return columnSet.stream().filter(Objects::nonNull).map(EntityColumn::getColumn).collect(Collectors.toList());}public GroupCriteria createGroupCriteria(boolean isAnd) {GroupCriteria groupCriteria = new GroupCriteria();groupCriteria.setAndOr(isAnd ? " AND " : " OR ");groupCriteriaList.add(groupCriteria);return groupCriteria;}protected Criteria createCriteriaInner(boolean isAnd) {Criteria criteria = createCriteriaInternal();criteria.setAndOr(isAnd ? " AND " : " OR ");return criteria;}@Datapublic class GroupCriteria {private String andOr;private List<Criteria> criteriaList = new ArrayList<Criteria>();private List<GroupCriteria> subGroupCriteriaList = new ArrayList<GroupCriteria>();public GroupCriteria createSubGroupCriteria(boolean isAnd) {GroupCriteria groupCriteria = new GroupCriteria();groupCriteria.setAndOr(isAnd ? " AND " : " OR ");subGroupCriteriaList.add(groupCriteria);return groupCriteria;}public Criteria createCriteria(boolean isAnd) {Criteria criteria = createCriteriaInner(isAnd);criteriaList.add(criteria);return criteria;}}
}
实际使用:
XyzExample<OrderDO> example = new XyzExample<OrderDO>(OrderDO.class);
example.orderBy(OrderDO.Fields.id).desc();XyzExample.GroupCriteria group1 = example.createGroupCriteria(true);
group1.createCriteria(true).andEqualTo(OrderDO.Fields.type, 1);XyzExample.GroupCriteria group11 = group1.createSubGroupCriteria(true);
group11.createCriteria(true).andEqualTo(OrderDO.Fields.status, 1).andEqualTo(OrderDO.Fields.userId, 1);XyzExample.GroupCriteria group12 = group1.createSubGroupCriteria(false);
group12.createCriteria(true).andEqualTo(OrderDO.Fields.status, 2).andEqualTo(OrderDO.Fields.userId, 2);XyzExample.GroupCriteria group2 = example.createGroupCriteria(false);
group2.createCriteria(true).andEqualTo(OrderDO.Fields.type, 2);XyzExample.GroupCriteria group21 = group2.createSubGroupCriteria(true);
group21.createCriteria(true).andEqualTo(OrderDO.Fields.status, 3).andEqualTo(OrderDO.Fields.userId, 3);XyzExample.GroupCriteria group22 = group2.createSubGroupCriteria(false);
group22.createCriteria(true).andEqualTo(OrderDO.Fields.status, 4).andEqualTo(OrderDO.Fields.userId, 4);
List<OrderDO> list = orderMapper.selectByExampleExtend(example);
2、将父子层次结构转为单层循环接口
定义枚举XyzSqlNodeType
public enum XyzSqlNodeType {/*** 左括号: (*/LEFT_PARENTHESE,/*** 右括号: )*/RIGHT_PARENTHESE,/*** AND、OR*/ANDOR,/*** 条件: a = 1*/CONDITION
}
定义SQL
节点类
@Accessors(chain = true)
@Data
public class XyzSqlNode {/*** 节点类型*/private XyzSqlNodeType type;/*** "AND", "OR"(用于连接符)*/private String andOr;/*** 字段值1*/private Object value;/*** 字段值2*/private Object secondValue;/*** 例如: a =*/private String condition;/*** 例如: a IS NULL*/private boolean noValue;/*** 例如: a = 1*/private boolean singleValue;/*** 例如: a between 1 and 100*/private boolean betweenValue;/*** 例如: a IN(1,2,3)*/private boolean listValue;
}
层接结构转换为水平结构
public class XyzSqlUtil {public static List<XyzSqlNode> getSqlNodeListByGroup(List<XyzExample.GroupCriteria> groupCriteriaList) {List<XyzSqlNode> list = new ArrayList<XyzSqlNode>();if (CollectionUtils.isEmpty(groupCriteriaList)) {return list;}int i = 0;for (XyzExample.GroupCriteria it : groupCriteriaList) {if (Objects.isNull(it)) {continue;}if (i > 0) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.ANDOR).setAndOr(it.getAndOr()));}List<XyzSqlNode> subNodeList = getSqlNodeListByGroup(it);if (CollectionUtils.isNotEmpty(subNodeList)) {list.addAll(subNodeList);i++;}}return list;}private static List<XyzSqlNode> getSqlNodeListByGroup(XyzExample.GroupCriteria groupCriteria) {List<XyzSqlNode> list = new ArrayList<XyzSqlNode>();if (Objects.isNull(groupCriteria)) {return list;}List<XyzSqlNode> nodeList = getSqlNodeList(groupCriteria.getCriteriaList());if (CollectionUtils.isNotEmpty(groupCriteria.getSubGroupCriteriaList())) {// 递归List<XyzSqlNode> subList = getSqlNodeListByGroup(groupCriteria.getSubGroupCriteriaList());if (CollectionUtils.isNotEmpty(nodeList)) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));list.addAll(nodeList);if (CollectionUtils.isNotEmpty(subList)) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.ANDOR).setAndOr(groupCriteria.getAndOr()));list.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));list.addAll(subList);list.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));}list.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));} else {if (CollectionUtils.isNotEmpty(subList)) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));list.addAll(subList);list.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));}}} else {if (CollectionUtils.isNotEmpty(nodeList)) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));list.addAll(nodeList);list.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));}}return list;}private static List<XyzSqlNode> getSqlNodeList(List<Example.Criteria> criteriaList) {ArrayList<XyzSqlNode> list = new ArrayList<XyzSqlNode>();if (CollectionUtils.isEmpty(criteriaList)) {return list;}list.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));int i = 0;for (Example.Criteria it : criteriaList) {if (Objects.isNull(it)) {continue;}if (i > 0) {list.add(new XyzSqlNode().setType(XyzSqlNodeType.ANDOR).setAndOr(it.getAndOr()));}List<XyzSqlNode> subNodeList = getSqlNodeList(it);if (CollectionUtils.isNotEmpty(subNodeList)) {list.addAll(subNodeList);i++;}}if (CollectionUtils.size(list) == 1) {list.clear();} else {list.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));}return list;}private static List<XyzSqlNode> getSqlNodeList(Example.Criteria criteria) {ArrayList<XyzSqlNode> nodeList = new ArrayList<XyzSqlNode>();if (Objects.isNull(criteria) || CollectionUtils.isEmpty(criteria.getCriteria())) {return nodeList;}nodeList.add(new XyzSqlNode().setType(XyzSqlNodeType.LEFT_PARENTHESE));int i = 0;for (Example.Criterion it : criteria.getCriteria()) {if (Objects.isNull(it)) {continue;}if (i > 0) {nodeList.add(new XyzSqlNode().setType(XyzSqlNodeType.ANDOR).setAndOr(" " + it.getAndOr() + " "));}XyzSqlNode subXyzSqlNode = new XyzSqlNode();subXyzSqlNode.setType(XyzSqlNodeType.CONDITION);subXyzSqlNode.setCondition(it.getCondition());subXyzSqlNode.setValue(it.getValue());subXyzSqlNode.setSecondValue(it.getSecondValue());subXyzSqlNode.setNoValue(it.isNoValue());subXyzSqlNode.setSingleValue(it.isSingleValue());subXyzSqlNode.setBetweenValue(it.isBetweenValue());subXyzSqlNode.setListValue(it.isListValue());nodeList.add(subXyzSqlNode);i++;}nodeList.add(new XyzSqlNode().setType(XyzSqlNodeType.RIGHT_PARENTHESE));return nodeList;}
}
在XyzExample
中增加SQL
节点
public class XyzExample<T> extends tk.mybatis.mapper.entity.Example {// 其他代码protected List<XyzSqlNode> nodes = new ArrayList<XyzSqlNode>();public List<XyzSqlNode> getNodes() {if (CollectionUtils.isNotEmpty(nodes)) {return nodes;}nodes = XyzSqlUtil.getSqlNodeListByGroup(groupCriteriaList);return nodes;}
}
判断结构转换是否正确,输出SQL语句
public class XyzSqlUtil {public static String parseWhere(List<XyzSqlNode> nodes) {if (CollectionUtils.isEmpty(nodes)) {return null;}StringBuilder builder = new StringBuilder();for (XyzSqlNode node : nodes) {switch (node.getType()) {case LEFT_PARENTHESE:builder.append("(");break;case RIGHT_PARENTHESE:builder.append(")");break;case ANDOR:builder.append(node.getAndOr());break;case CONDITION:if (node.isNoValue()) {builder.append(node.getCondition());} else if (node.isSingleValue()) {if (node.getValue() instanceof Number) {builder.append(node.getCondition()).append(node.getValue());} else {builder.append(node.getCondition()).append("'").append(node.getValue()).append("'");}} else if (node.isBetweenValue()) {builder.append(node.getCondition()).append(" AND ");if (node.getValue() instanceof Number) {builder.append(node.getValue());} else {builder.append("'").append(node.getValue()).append("'");}} else if (node.isListValue()) {builder.append(node.getCondition()).append(" (");Collection<Object> coll = (Collection<Object>) (node.getValue());int i = 0;for (Object o : coll) {if (Objects.isNull(o)) {continue;}if (i > 0) {builder.append(",");}if (o instanceof Number) {builder.append(o);} else {builder.append("'").append(o).append("'");}i++;}builder.append(")");}break;default:break;}}return builder.toString();}
}
单元测试一下
@Test
public void test1() {EntityHelper.initEntityNameMap(OrderDO.class, new Config());XyzExample<OrderDO> example = new XyzExample<OrderDO>(OrderDO.class);example.orderBy(OrderDO.Fields.id).desc();XyzExample.GroupCriteria group1 = example.createGroupCriteria(true);group1.createCriteria(true).andEqualTo(OrderDO.Fields.type, 1);XyzExample.GroupCriteria group11 = group1.createSubGroupCriteria(true);group11.createCriteria(true).andEqualTo(OrderDO.Fields.status, 1).andEqualTo(OrderDO.Fields.userId, 1);XyzExample.GroupCriteria group12 = group1.createSubGroupCriteria(false);group12.createCriteria(true).andEqualTo(OrderDO.Fields.status, 2).andEqualTo(OrderDO.Fields.userId, 2);XyzExample.GroupCriteria group2 = example.createGroupCriteria(false);group2.createCriteria(true).andEqualTo(OrderDO.Fields.type, 2);XyzExample.GroupCriteria group21 = group2.createSubGroupCriteria(true);group21.createCriteria(true).andEqualTo(OrderDO.Fields.status, 3).andEqualTo(OrderDO.Fields.userId, 3);XyzExample.GroupCriteria group22 = group2.createSubGroupCriteria(false);group22.createCriteria(true).andEqualTo(OrderDO.Fields.status, 4).andEqualTo(OrderDO.Fields.userId, 4);System.out.println(XyzSqlUtil.parseWhere(example.getNodes()));
}
3、将nodes
在XML文件中体现
新建一个Mapper
public interface BaseXyzExampleMapper {// 内部什么都不需要
}
对应XML文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.x.y.z.ext.BaseXyzExampleMapper"><sql id="_selectByExampleExtend">SELECT<if test="param.distinct">distinct</if><include refid="org.x.y.z.ext.BaseXyzExampleMapper._getExampleExtendColumn"/>FROM ${param.tableName}<include refid="org.x.y.z.ext.BaseXyzExampleMapper._getExampleExtendWhere"/><if test="param.orderByClause != null">ORDER BY ${param.orderByClause}</if></sql><sql id="_getExampleExtendColumn"><choose><when test="param.selectColumns != null and !param.selectColumns.isEmpty()"><foreach collection="param.selectColumns" item="column" open="" close="" separator=",">${column}</foreach></when><otherwise><foreach collection="param.getAllColumns()" item="column" open="" close="" separator=",">${column}</foreach></otherwise></choose></sql><sql id="_getExampleExtendWhere"><if test="param.nodes != null and !param.nodes.isEmpty()"><where><trim prefixOverrides="AND |OR "><foreach item="node" collection="param.nodes" index="index"><choose><!-- 处理开括号 --><when test="node.type !=null and node.type == @org.x.y.z.ext.XyzSqlNodeType@LEFT_PARENTHESE">(</when><!-- 处理闭括号 --><when test="node.type !=null and node.type == @org.x.y.z.ext.XyzSqlNodeType@RIGHT_PARENTHESE">)</when><!-- 处理 AND OR --><when test="node.type !=null and node.type == @org.x.y.z.ext.XyzSqlNodeType@ANDOR">${index > 0 ? node.andOr : ''}</when><!-- 处理条件 --><when test="node.type !=null and node.type == @org.x.y.z.ext.XyzSqlNodeType@CONDITION"><choose><when test="node.noValue">${node.condition}</when><when test="node.singleValue">${node.condition} #{node.value}</when><when test="node.betweenValue">${node.condition} #{node.value} AND #{node.secondValue}</when><when test="node.listValue">${node.condition}<foreach collection="node.value" item="listItem" open="(" close=")" separator=",">#{listItem}</foreach></when></choose></when><otherwise></otherwise></choose></foreach></trim></where></if></sql>
</mapper>
实际使用
定义实际Mapper,扩展自BaseXyzExampleMapper
@org.apache.ibatis.annotations.Mapper
public interface OrderMapper extends tk.mybatis.mapper.common.Mapper<OrderDO>, InsertListMapper<OrderDO>, BaseXyzExampleMapper {// 定义查询方法List<OrderDO> selectByExampleExtend(@Param("param") XyzExample<OrderDO> param);
}
对应XML:
<select id="selectByExampleExtend" resultType="org.x.y.z.entity.OrderDO"><include refid="org.x.y.z.ext.BaseXyzExampleMapper._selectByExampleExtend"/></select>