动态join支持通过中间表的关联

This commit is contained in:
mazhicheng 2020-06-03 20:38:24 +08:00
parent a980b290bb
commit 7327a356fe
25 changed files with 746 additions and 339 deletions

View File

@ -127,8 +127,10 @@ public class QueryBuilder {
return new QueryWrapper<>();
}
QueryWrapper wrapper;
// 只解析有值的
fields = fieldValuesMap.keySet();
// 是否有join联表查询
boolean hasJoinTable = ParserCache.hasJoinTable(dto, fieldValuesMap.keySet());
boolean hasJoinTable = ParserCache.hasJoinTable(dto, fields);
if(hasJoinTable){
wrapper = new DynamicJoinQueryWrapper<>(dto.getClass(), fields);
}
@ -337,7 +339,7 @@ public class QueryBuilder {
try {
value = field.get(dto);
} catch (IllegalAccessException e) {
log.error("通过反射获取属性值出错:" + e);
log.error("通过反射获取属性值出错:{}", e.getMessage());
}
// 忽略逻辑删除字段
if(Cons.FieldName.deleted.name().equals(field.getName())

View File

@ -15,7 +15,6 @@
*/
package com.diboot.core.binding.binder;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.S;

View File

@ -15,7 +15,6 @@
*/
package com.diboot.core.binding.binder;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.util.*;
import org.slf4j.Logger;

View File

@ -16,11 +16,16 @@
package com.diboot.core.binding.binder;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.util.*;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 关联字段绑定

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.diboot.core.binding.parser;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 条件管理器base类
* @author mazc@dibo.ltd
* @version v2.0
* @date 2020/06/02
*/
@Slf4j
public class BaseConditionManager {
/**
* 表达式缓存Map
*/
private static Map<String, List<Expression>> expressionParseResultMap = new ConcurrentHashMap<>();
/**
* 获取解析后的Expression列表
* @param condition
* @return
*/
protected static List<Expression> getExpressionList(String condition){
if(V.isEmpty(condition)){
return null;
}
List<Expression> expressionList = expressionParseResultMap.get(condition);
if(expressionList == null){
ConditionParser visitor = new ConditionParser();
try{
Expression expression = CCJSqlParserUtil.parseCondExpression(condition);
expression.accept(visitor);
expressionList = visitor.getExpressList();
expressionParseResultMap.put(condition, expressionList);
}
catch (Exception e){
log.error("关联条件解析异常", e);
}
}
return expressionList;
}
/**
* 提取中间表表对象名
* @param expressionList
* @return
*/
protected static String extractMiddleTableName(List<Expression> expressionList){
Set<String> tableNameSet = new HashSet<>();
for(Expression operator : expressionList){
if(operator instanceof EqualsTo){
EqualsTo express = (EqualsTo)operator;
// 均为列
if(express.getLeftExpression() instanceof Column && express.getRightExpression() instanceof Column){
// 统计左侧列中出现的表名
String leftColumn = express.getLeftExpression().toString();
collectTableName(tableNameSet, leftColumn);
// 统计右侧列中出现的表名
String rightColumn = express.getRightExpression().toString();
collectTableName(tableNameSet, rightColumn);
}
}
}
if(tableNameSet.isEmpty()){
return null;
}
if(tableNameSet.size() > 1){
log.warn("中间表关联条件暂只支持1张中间表");
}
return tableNameSet.iterator().next();
}
/**
* 统计表名出现的次数
* @param tableNameSet
* @param columnStr
*/
private static void collectTableName(Set<String> tableNameSet, String columnStr) {
if(!columnStr.contains(".")){
return;
}
// 如果是中间表(非this,self标识的当前表)
if(!isCurrentObjColumn(columnStr)){
String tempTableName = S.substringBefore(columnStr, ".");
tableNameSet.add(tempTableName);
}
}
/**
* 是否为VO自身属性以this开头的
* @param expression
* @return
*/
protected static boolean isCurrentObjColumn(String expression){
String tempTableName = S.substringBefore(expression, ".");
// 如果是中间表(非this,self标识的当前表)
return "this".equals(tempTableName) || "self".equals(tempTableName);
}
}

View File

@ -18,18 +18,13 @@ package com.diboot.core.binding.parser;
import com.diboot.core.binding.binder.BaseBinder;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 条件表达式的管理器
@ -37,38 +32,8 @@ import java.util.concurrent.ConcurrentHashMap;
* @version v2.0
* @date 2019/4/1
*/
public class ConditionManager {
private static final Logger log = LoggerFactory.getLogger(ConditionManager.class);
/**
* 表达式缓存Map
*/
private static Map<String, List<Expression>> expressionParseResultMap = new ConcurrentHashMap<>();
/**
* 获取解析后的Expression列表
* @param condition
* @return
*/
public static List<Expression> getExpressionList(String condition){
if(V.isEmpty(condition)){
return null;
}
List<Expression> expressionList = expressionParseResultMap.get(condition);
if(expressionList == null){
ConditionParser visitor = new ConditionParser();
try{
Expression expression = CCJSqlParserUtil.parseCondExpression(condition);
expression.accept(visitor);
expressionList = visitor.getExpressList();
expressionParseResultMap.put(condition, expressionList);
}
catch (Exception e){
log.error("关联条件解析异常", e);
}
}
return expressionList;
}
@Slf4j
public class ConditionManager extends BaseConditionManager{
/**
* 附加条件到binder
@ -241,7 +206,7 @@ public class ConditionManager {
// 绑定左手边连接列
String leftHandColumn = removeLeftAlias(leftColumn);
// this. 开头的vo对象字段
if(isVoColumn(leftColumn)){
if(isCurrentObjColumn(leftColumn)){
// 识别到vo对象的属性 departmentId
annoObjectForeignKey = leftHandColumn;
// 对应中间表的关联字段
@ -259,7 +224,7 @@ public class ConditionManager {
if(leftColumn.startsWith(tableName+".")){
// 绑定右手边连接列
String rightHandColumn = removeLeftAlias(rightColumn);
if(isVoColumn(rightColumn)){
if(isCurrentObjColumn(rightColumn)){
// 识别到vo对象的属性 departmentId
annoObjectForeignKey = rightHandColumn;
// 对应中间表的关联字段
@ -337,61 +302,6 @@ public class ConditionManager {
return additionalExpressions;
}
/**
* 提取中间表表对象名
* @param expressionList
* @return
*/
private static String extractMiddleTableName(List<Expression> expressionList){
Map<String, Integer> tableNameCountMap = new HashMap<>();
for(Expression operator : expressionList){
if(operator instanceof EqualsTo){
EqualsTo express = (EqualsTo)operator;
// 均为列
if(express.getLeftExpression() instanceof Column && express.getRightExpression() instanceof Column){
// 统计左侧列中出现的表名
String leftColumn = express.getLeftExpression().toString();
countTableName(tableNameCountMap, leftColumn);
// 统计右侧列中出现的表名
String rightColumn = express.getRightExpression().toString();
countTableName(tableNameCountMap, rightColumn);
}
}
}
if(tableNameCountMap.isEmpty()){
return null;
}
String tableName = null;
int count = 1;
for(Map.Entry<String, Integer> entry : tableNameCountMap.entrySet()){
if(entry.getValue() > count){
count = entry.getValue();
tableName = entry.getKey();
}
}
return tableName;
}
/**
* 统计表名出现的次数
* @param tableNameCountMap
* @param columnStr
*/
private static void countTableName(Map<String, Integer> tableNameCountMap, String columnStr) {
if(columnStr.contains(".")){
// 如果是中间表(非this,self标识的当前表)
if(!isVoColumn(columnStr)){
String tempTableName = S.substringBefore(columnStr, ".");
Integer count = tableNameCountMap.get(tempTableName);
if(count == null){
count = 0;
}
count++;
tableNameCountMap.put(tempTableName, count);
}
}
}
/**
* 注解列
* @return
@ -403,15 +313,4 @@ public class ConditionManager {
return annoColumn;
}
/**
* 是否为VO自身属性以this开头的
* @param expression
* @return
*/
private static boolean isVoColumn(String expression){
String tempTableName = S.substringBefore(expression, ".");
// 如果是中间表(非this,self标识的当前表)
return "this".equals(tempTableName) || "self".equals(tempTableName);
}
}

View File

@ -15,8 +15,11 @@
*/
package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.V;
@ -101,15 +104,21 @@ public class MiddleTable {
* @return
*/
public Map<String, Object> executeOneToOneQuery(List annoObjectForeignKeyList){
// 提取中间表查询SQL: SELECT id, org_id FROM department WHERE id IN(?)
String sql = toSQL(annoObjectForeignKeyList);
// 执行查询并合并结果
//id
String keyName = getEqualsToAnnoObjectFKColumn(),
//org_id
valueName = getEqualsToRefEntityPkColumn();
Map<String, Object> middleTableResultMap = SqlExecutor.executeQueryAndMergeOneToOneResult(sql, annoObjectForeignKeyList, keyName, valueName);
return middleTableResultMap;
//id //org_id
String keyName = getEqualsToAnnoObjectFKColumn(), valueName = getEqualsToRefEntityPkColumn();
TableLinkage linkage = ParserCache.getTableLinkage(table);
// 有定义mapper首选mapper
if(linkage != null){
List<Map<String, Object>> resultSetMapList = queryByMapper(linkage, annoObjectForeignKeyList);
return SqlExecutor.convertToOneToOneResult(resultSetMapList, keyName, valueName);
}
else{
// 提取中间表查询SQL: SELECT id, org_id FROM department WHERE id IN(?)
String sql = toSQL(annoObjectForeignKeyList);
// 执行查询并合并结果
Map<String, Object> middleTableResultMap = SqlExecutor.executeQueryAndMergeOneToOneResult(sql, annoObjectForeignKeyList, keyName, valueName);
return middleTableResultMap;
}
}
/**
@ -118,15 +127,42 @@ public class MiddleTable {
* @return
*/
public Map<String, List> executeOneToManyQuery(List annoObjectForeignKeyList){
// 提取中间表查询SQL: SELECT user_id, role_id FROM user_role WHERE user_id IN(?)
String sql = toSQL(annoObjectForeignKeyList);
// 执行查询并合并结果
//user_id
String keyName = getEqualsToAnnoObjectFKColumn(),
//role_id
valueName = getEqualsToRefEntityPkColumn();
Map<String, List> middleTableResultMap = SqlExecutor.executeQueryAndMergeOneToManyResult(sql, annoObjectForeignKeyList, keyName, valueName);
return middleTableResultMap;
//user_id //role_id
String keyName = getEqualsToAnnoObjectFKColumn(), valueName = getEqualsToRefEntityPkColumn();
TableLinkage linkage = ParserCache.getTableLinkage(table);
// 有定义mapper首选mapper
if(linkage != null){
List<Map<String, Object>> resultSetMapList = queryByMapper(linkage, annoObjectForeignKeyList);
return SqlExecutor.convertToOneToManyResult(resultSetMapList, keyName, valueName);
}
else{
// 提取中间表查询SQL: SELECT user_id, role_id FROM user_role WHERE user_id IN(?)
String sql = toSQL(annoObjectForeignKeyList);
// 执行查询并合并结果
Map<String, List> middleTableResultMap = SqlExecutor.executeQueryAndMergeOneToManyResult(sql, annoObjectForeignKeyList, keyName, valueName);
return middleTableResultMap;
}
}
/**
* 通过定义的Mapper查询结果
* @param linkage
* @param annoObjectForeignKeyList
* @return
*/
private List<Map<String, Object>> queryByMapper(TableLinkage linkage, List annoObjectForeignKeyList){
BaseMapper mapper = (BaseMapper) ContextHelper.getBean(linkage.getMapperClass());
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.setEntityClass(linkage.getEntityClass());
queryWrapper.select(equalsToAnnoObjectFKColumn, equalsToRefEntityPkColumn);
queryWrapper.in(equalsToAnnoObjectFKColumn, annoObjectForeignKeyList);
if(additionalConditions != null){
for(String condition : additionalConditions){
queryWrapper.apply(condition);
}
}
List<Map<String, Object>> resultSetMapList = mapper.selectMaps(queryWrapper);
return resultSetMapList;
}
/**
@ -134,7 +170,7 @@ public class MiddleTable {
* @param annoObjectForeignKeyList 注解外键值的列表用于拼接SQL查询
* @return
*/
public String toSQL(List annoObjectForeignKeyList){
private String toSQL(List annoObjectForeignKeyList){
if(V.isEmpty(annoObjectForeignKeyList)){
return null;
}

View File

@ -18,12 +18,12 @@ package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.annotation.TableName;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.config.Cons;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.V;
import org.apache.ibatis.jdbc.SQL;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
@ -39,15 +39,16 @@ import java.util.concurrent.ConcurrentHashMap;
* @version 2.0<br>
* @date 2019/04/03 <br>
*/
@Slf4j
public class ParserCache {
/**
* VO类-绑定注解缓存
*/
private static Map<Class, BindAnnotationGroup> allVoBindAnnotationCacheMap = new ConcurrentHashMap<>();
/**
* 中间表是否包含is_deleted列 缓存
* 表及相关信息的缓存
*/
private static Map<String, Boolean> middleTableHasDeletedCacheMap = new ConcurrentHashMap<>();
private static Map<String, TableLinkage> tableToLinkageCacheMap = new ConcurrentHashMap<>();
/**
* entity类-表名的缓存
*/
@ -97,29 +98,54 @@ public class ParserCache {
}
/**
* 是否有is_deleted列
* @return
* 初始化Table的相关对象信息
*/
public static boolean hasDeletedColumn(String middleTable){
if(middleTableHasDeletedCacheMap.containsKey(middleTable)){
return middleTableHasDeletedCacheMap.get(middleTable);
private static void initTableToLinkageCacheMap(){
if(tableToLinkageCacheMap.isEmpty()){
SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class);
Collection<Class<?>> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();
if(V.notEmpty(mappers)){
mappers.forEach(m->{
Type[] types = m.getGenericInterfaces();
try{
if(types != null && types.length > 0 && types[0] != null){
ParameterizedType genericType = (ParameterizedType) types[0];
Type[] superTypes = genericType.getActualTypeArguments();
if(superTypes != null && superTypes.length > 0 && superTypes[0] != null){
String entityClassName = superTypes[0].getTypeName();
if(entityClassName.length() > 1){
Class<?> entityClass = Class.forName(entityClassName);
TableLinkage linkage = new TableLinkage(entityClass, m);
tableToLinkageCacheMap.put(linkage.getTable(), linkage);
}
}
}
}
catch (Exception e){
log.warn("解析mapper异常", e);
}
});
}
}
boolean hasColumn = SqlExecutor.validateQuery(buildCheckDeletedColSql(middleTable));
middleTableHasDeletedCacheMap.put(middleTable, hasColumn);
return hasColumn;
}
/**
* 构建检测是否有删除字段的sql
* @param table
* 是否有is_deleted列
* @return
*/
private static String buildCheckDeletedColSql(String table){
return new SQL(){{
SELECT(Cons.COLUMN_IS_DELETED);
FROM(table);
LIMIT(1);
}}.toString();
public static boolean hasDeletedColumn(String table){
TableLinkage linkage = getTableLinkage(table);
return linkage != null && linkage.isHasDeleted();
}
/**
* 获取table相关信息
* @return
*/
public static TableLinkage getTableLinkage(String table){
initTableToLinkageCacheMap();
TableLinkage linkage = tableToLinkageCacheMap.get(table);
return linkage;
}
/**
@ -150,7 +176,7 @@ public class ParserCache {
* @param <DTO>
* @return
*/
public static <DTO> boolean hasJoinTable(DTO dto, Set<String> fieldNameSet){
public static <DTO> boolean hasJoinTable(DTO dto, Collection<String> fieldNameSet){
List<AnnoJoiner> annoList = getBindQueryAnnos(dto.getClass());
if(V.notEmpty(annoList)){
for(AnnoJoiner anno : annoList){
@ -199,6 +225,7 @@ public class ParserCache {
else{
annoJoiner.setAlias(alias);
}
annoJoiner.parse();
}
annos.add(annoJoiner);
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.config.Cons;
import com.diboot.core.util.BeanUtils;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.lang.reflect.Field;
/**
* table的相关线索信息
* @author mazc
* @version v1.0
* @date 2020/06/02
*/
@Getter @Setter
public class TableLinkage implements Serializable {
private static final long serialVersionUID = 4416187849283913895L;
public TableLinkage(Class<?> entityClass, Class<?> mapperClass){
this.entityClass = entityClass;
this.mapperClass = mapperClass;
this.table = ParserCache.getEntityTableName(entityClass);
// 初始化是否有is_deleted
Field field = BeanUtils.extractField(entityClass, Cons.FieldName.deleted.name());
if(field != null){
TableField tableField = field.getAnnotation(TableField.class);
if(tableField != null && tableField.exist() == true){
this.hasDeleted = true;
}
}
}
private String table;
/**
* 表对应的entity类
*/
private Class<?> entityClass;
/**
* 表对应的mapper类
*/
private Class<?> mapperClass;
/**
* 是否有逻辑删除字段
*/
private boolean hasDeleted = false;
}

View File

@ -67,23 +67,42 @@ public class AnnoJoiner implements Serializable {
private String columnName;
private String join;
private String condition;
private String join;
/**
* 别名
*/
private String alias;
private String condition;
/**
* on条件
*/
private String onSegment;
/**
* 获取On条件
* @return
* 中间表
*/
public String getOnSegment(){
if(V.notEmpty(condition)){
return JoinConditionParser.parseJoinCondition(this.condition, this.alias);
private String middleTable;
/**
* 中间表别名
*/
public String getMiddleTableAlias(){
if(middleTable != null && alias != null){
return alias+"m";
}
return null;
}
/**
* 中间表on
*/
private String middleTableOnSegment;
/**
* 解析
*/
public void parse(){
// 解析查询
JoinConditionManager.parseJoinCondition(this);
}
}

View File

@ -89,6 +89,18 @@ public class DynamicSqlProvider {
StringBuilder sb = new StringBuilder();
for(AnnoJoiner joiner : annoJoinerList){
if(V.notEmpty(joiner.getJoin()) && V.notEmpty(joiner.getOnSegment())){
if(joiner.getMiddleTable() != null){
sb.setLength(0);
sb.append(joiner.getMiddleTable()).append(" ").append(joiner.getMiddleTableAlias()).append(" ON ").append(joiner.getMiddleTableOnSegment());
if(S.containsIgnoreCase(joiner.getMiddleTable(), " "+Cons.COLUMN_IS_DELETED) == false && ParserCache.hasDeletedColumn(joiner.getMiddleTable())){
sb.append(" AND ").append(joiner.getMiddleTableAlias()).append(".").append(Cons.COLUMN_IS_DELETED).append(" = ").append(BaseConfig.getActiveFlagValue());
}
String joinSegment = sb.toString();
if(!tempSet.contains(joinSegment)){
LEFT_OUTER_JOIN(joinSegment);
tempSet.add(joinSegment);
}
}
sb.setLength(0);
sb.append(joiner.getJoin()).append(" ").append(joiner.getAlias()).append(" ON ").append(joiner.getOnSegment());
if(S.containsIgnoreCase(joiner.getOnSegment(), " "+Cons.COLUMN_IS_DELETED) == false && ParserCache.hasDeletedColumn(joiner.getJoin())){

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.diboot.core.binding.query.dynamic;
import com.diboot.core.binding.parser.BaseConditionManager;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.Status;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import java.util.ArrayList;
import java.util.List;
/**
* Join条件表达式的管理器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/4/1
*/
@Slf4j
public class JoinConditionManager extends BaseConditionManager {
/**
* 解析condition条件
* @param joiner
* @throws Exception
*/
public static void parseJoinCondition(AnnoJoiner joiner) {
List<Expression> expressionList = getExpressionList(joiner.getCondition());
if(V.isEmpty(expressionList)){
log.warn("无法解析注解条件: {} ", joiner.getCondition());
throw new BusinessException(Status.FAIL_VALIDATION);
}
// 解析中间表关联
String tableName = extractMiddleTableName(expressionList);
if(tableName != null){
joiner.setMiddleTable(tableName);
}
// 解析join
parseJoinOn(joiner, expressionList);
}
/**
* 解析直接关联
* @param joiner
* @param expressionList
*/
private static void parseJoinOn(AnnoJoiner joiner, List<Expression> expressionList) {
List<String> segments = new ArrayList<>(), middleTableOnSegments = new ArrayList<>();
// 解析直接关联
for(Expression operator : expressionList){
// 默认当前表条件
List<String> currentSegments = segments;
if(operator instanceof BinaryExpression){
BinaryExpression expression = (BinaryExpression)operator;
String left = formatColumn(expression.getLeftExpression(), joiner);
String right = formatColumn(expression.getRightExpression(), joiner);
// 中间表条件
if(joiner.getMiddleTable() != null &&
(left.startsWith(joiner.getMiddleTableAlias() + ".") || right.startsWith(joiner.getMiddleTableAlias() + "."))){
if(left.startsWith(joiner.getAlias()+".") || right.startsWith(joiner.getAlias()+".")){
}
else{
currentSegments = middleTableOnSegments;
}
}
if(operator instanceof EqualsTo){
currentSegments.add(left + " = " + right);
}
else if(operator instanceof NotEqualsTo){
currentSegments.add(left + " != " + right);
}
else if(operator instanceof GreaterThan){
currentSegments.add(left + " > " + right);
}
else if(operator instanceof GreaterThanEquals){
currentSegments.add(left + " >= " + right);
}
else if(operator instanceof MinorThan){
currentSegments.add(left + " < " + right);
}
else if(operator instanceof MinorThanEquals){
currentSegments.add(left + " <= " + right);
}
else{
log.warn("暂不支持的条件: "+ expression.toString());
}
}
else if(operator instanceof IsNullExpression){
IsNullExpression expression = (IsNullExpression)operator;
String left = formatColumn(expression.getLeftExpression(), joiner);
// 中间表条件
if(joiner.getMiddleTable() != null && left.startsWith(joiner.getMiddleTableAlias() + ".")){
currentSegments = middleTableOnSegments;
}
if(expression.isNot() == false){
currentSegments.add(left + " IS NULL");
}
else{
currentSegments.add(left + " IS NOT NULL");
}
}
else if(operator instanceof InExpression){
InExpression expression = (InExpression)operator;
String left = formatColumn(expression.getLeftExpression(), joiner);
// 中间表条件
if(joiner.getMiddleTable() != null && left.startsWith(joiner.getMiddleTableAlias() + ".")){
currentSegments = middleTableOnSegments;
}
if(expression.isNot() == false){
currentSegments.add(left + " IN " + expression.getRightItemsList().toString());
}
else{
currentSegments.add(left + " NOT IN " + expression.getRightItemsList().toString());
}
}
else if(operator instanceof Between){
Between expression = (Between)operator;
String left = formatColumn(expression.getLeftExpression(), joiner);
// 中间表条件
if(joiner.getMiddleTable() != null && left.startsWith(joiner.getMiddleTableAlias() + ".")){
currentSegments = middleTableOnSegments;
}
if(expression.isNot() == false){
currentSegments.add(left + " BETWEEN " + expression.getBetweenExpressionStart().toString() + " AND " + expression.getBetweenExpressionEnd().toString());
}
else{
currentSegments.add(left + " NOT BETWEEN " + expression.getBetweenExpressionStart().toString() + " AND " + expression.getBetweenExpressionEnd().toString());
}
}
else if(operator instanceof LikeExpression){
LikeExpression expression = (LikeExpression)operator;
String left = formatColumn(expression.getLeftExpression(), joiner);
// 中间表条件
if(joiner.getMiddleTable() != null && left.startsWith(joiner.getMiddleTableAlias() + ".")){
currentSegments = middleTableOnSegments;
}
if(expression.isNot() == false){
currentSegments.add(left + " LIKE " + expression.getStringExpression());
}
else{
currentSegments.add(left + " NOT LIKE " + expression.getStringExpression());
}
}
else{
log.warn("不支持的条件: "+operator.toString());
}
}
if(segments.isEmpty() && middleTableOnSegments.isEmpty()){
return;
}
joiner.setOnSegment(S.join(segments, " AND "));
if(V.notEmpty(middleTableOnSegments)){
joiner.setMiddleTableOnSegment(S.join(middleTableOnSegments, " AND "));
}
}
/**
* 格式化左侧
* @return
*/
private static String formatColumn(Expression expression, AnnoJoiner joiner){
String annoColumn = S.toSnakeCase(expression.toString());
if(expression instanceof Column){
// 其他表列
if(annoColumn.contains(".")){
String tableName = S.substringBefore(annoColumn, ".");
// 当前表替换别名
if(tableName.equals("this")){
annoColumn = "self." + S.substringAfter(annoColumn, "this.");
}
else if(tableName.equals("self")){
}
else if(tableName.equals(joiner.getMiddleTable())){
annoColumn = joiner.getMiddleTableAlias() + "." + S.substringAfter(annoColumn, ".");
}
else{
log.warn("无法识别的条件: {}", annoColumn);
}
}
// 当前表列
else{
annoColumn = joiner.getAlias() + "." + annoColumn;
}
}
return annoColumn;
}
}

View File

@ -1,166 +0,0 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.diboot.core.binding.query.dynamic;
import com.diboot.core.binding.parser.ConditionManager;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Join条件表达式的管理器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/4/1
*/
public class JoinConditionParser {
private static final Logger log = LoggerFactory.getLogger(JoinConditionParser.class);
/**
* 解析condition条件
* @param condition
* @param alias
* @throws Exception
*/
public static String parseJoinCondition(String condition, String alias) {
List<Expression> expressionList = ConditionManager.getExpressionList(condition);
if(V.isEmpty(expressionList)){
log.warn("无法解析注解条件: {} ", condition);
return null;
}
// 解析join
return parseJoinOn(alias, expressionList);
}
/**
* 解析直接关联
* @param alias
* @param expressionList
*/
private static String parseJoinOn(String alias, List<Expression> expressionList) {
List<String> segments = new ArrayList<>();
// 解析直接关联
for(Expression operator : expressionList){
if(operator instanceof BinaryExpression){
BinaryExpression expression = (BinaryExpression)operator;
String left = formatLeft(expression.getLeftExpression().toString());
String right = formatRight(expression.getRightExpression().toString());
if(expression.getRightExpression() instanceof Column){
right = alias + "." + right;
}
else if(expression.getLeftExpression() instanceof Column){
left = alias + "." + left;
}
if(operator instanceof EqualsTo){
segments.add(left + " = " + right);
}
else if(operator instanceof NotEqualsTo){
segments.add(left + " != " + right);
}
else if(operator instanceof GreaterThan){
segments.add(left + " > " + right);
}
else if(operator instanceof GreaterThanEquals){
segments.add(left + " >= " + right);
}
else if(operator instanceof MinorThan){
segments.add(left + " < " + right);
}
else if(operator instanceof MinorThanEquals){
segments.add(left + " <= " + right);
}
else{
log.warn("暂不支持的条件: "+ expression.toString());
}
}
else if(operator instanceof IsNullExpression){
IsNullExpression expression = (IsNullExpression)operator;
String left = formatLeft(expression.getLeftExpression().toString());
if(expression.isNot() == false){
segments.add(left + " IS NULL");
}
else{
segments.add(left + " IS NOT NULL");
}
}
else if(operator instanceof InExpression){
InExpression expression = (InExpression)operator;
String left = formatLeft(expression.getLeftExpression().toString());
if(expression.isNot() == false){
segments.add(left + " IN " + expression.getRightItemsList().toString());
}
else{
segments.add(left + " NOT IN " + expression.getRightItemsList().toString());
}
}
else if(operator instanceof Between){
Between expression = (Between)operator;
String left = formatLeft(expression.getLeftExpression().toString());
if(expression.isNot() == false){
segments.add(left + " BETWEEN " + expression.getBetweenExpressionStart().toString() + " AND " + expression.getBetweenExpressionEnd().toString());
}
else{
segments.add(left + " NOT BETWEEN " + expression.getBetweenExpressionStart().toString() + " AND " + expression.getBetweenExpressionEnd().toString());
}
}
else if(operator instanceof LikeExpression){
LikeExpression expression = (LikeExpression)operator;
String left = formatLeft(expression.getLeftExpression().toString());
if(expression.isNot() == false){
segments.add(left + " LIKE " + expression.getStringExpression());
}
else{
segments.add(left + " NOT LIKE " + expression.getStringExpression());
}
}
else{
log.warn("不支持的条件: "+operator.toString());
}
}
if(segments.isEmpty()){
return null;
}
return S.join(segments, " AND ");
}
/**
* 注解列
* @return
*/
private static String formatLeft(String annoColumn){
if(annoColumn.contains("this.")){
annoColumn = S.replace(annoColumn, "this.", "self.");
}
return S.toSnakeCase(annoColumn);
}
/**
* 格式化右侧列
* @return
*/
private static String formatRight(String annoColumn){
return S.toSnakeCase(annoColumn);
}
}

View File

@ -16,10 +16,6 @@
package com.diboot.core.util;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.diboot.core.config.Cons;
import com.diboot.core.entity.BaseEntity;
import com.diboot.core.exception.BusinessException;

View File

@ -116,13 +116,12 @@ public class ContextHelper implements ApplicationContextAware {
}
/***
* 获取指定类型的全部实
* 获取指定类型的全部实现类
* @param type
* @param <T>
* @return
*/
public static <T> List<T> getBeans(Class<T> type){
// 获取所有的定时任务实现类
Map<String, T> map = getApplicationContext().getBeansOfType(type);
if(V.isEmpty(map)){
return null;

View File

@ -115,7 +115,6 @@ public class SqlExecutor {
}
}
/**
* 执行1-1关联查询和合并结果并将结果Map的key类型转成String
*
@ -130,6 +129,18 @@ public class SqlExecutor {
} catch (Exception e) {
log.warn("执行查询异常", e);
}
return convertToOneToOneResult(resultSetMapList, keyName, valueName);
}
/**
* 合并为1-1的map结果
* @param resultSetMapList
* @param keyName
* @param valueName
* @param <E>
* @return
*/
public static <E> Map<String, Object> convertToOneToOneResult(List<Map<String, E>> resultSetMapList, String keyName, String valueName) {
// 合并list为map
Map<String, Object> resultMap = new HashMap<>();
if(V.notEmpty(resultSetMapList)){
@ -162,6 +173,18 @@ public class SqlExecutor {
catch (Exception e) {
log.warn("执行查询异常", e);
}
return convertToOneToManyResult(resultSetMapList, keyName, valueName);
}
/**
* 合并为1-n的map结果
* @param resultSetMapList
* @param keyName
* @param valueName
* @param <E>
* @return
*/
public static <E> Map<String, List> convertToOneToManyResult(List<Map<String, E>> resultSetMapList, String keyName, String valueName){
// 合并list为map
Map<String, List> resultMap = new HashMap<>();
if(V.notEmpty(resultSetMapList)){

View File

@ -24,7 +24,8 @@ import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.service.DepartmentService;
import diboot.core.test.binder.service.UserService;
import diboot.core.test.binder.vo.*;
import diboot.core.test.binder.vo.EntityListComplexBinderVO;
import diboot.core.test.binder.vo.EntityListSimpleBinderVO;
import diboot.core.test.config.SpringMvcConfig;
import org.junit.Assert;
import org.junit.Test;

View File

@ -23,7 +23,9 @@ import com.diboot.core.config.Cons;
import com.diboot.core.vo.Pagination;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.dto.DepartmentDTO;
import diboot.core.test.binder.dto.UserDTO;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.service.DepartmentService;
import diboot.core.test.binder.vo.DepartmentVO;
import diboot.core.test.config.SpringMvcConfig;
@ -146,6 +148,29 @@ public class TestJoinQuery {
Assert.assertTrue(list.size() == 1);
}
/**
* 测试有中间表的动态sql join
*/
@Test
public void testDynamicSqlQueryWithMiddleTable() {
// 初始化DTO测试不涉及关联的情况
UserDTO dto = new UserDTO();
dto.setDeptName("研发组");
dto.setDeptId(10002L);
// builder直接查询不分页 3条结果
List<User> builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(User.class);
Assert.assertTrue(builderResultList.size() == 2);
dto.setOrgName("苏州帝博");
builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(User.class);
Assert.assertTrue(builderResultList.size() == 2);
dto.setRoleCode("ADMIN");
builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(User.class);
Assert.assertTrue(builderResultList.size() == 1);
}
@Test
public void test(){
String sql = buildCheckDeletedColSql("test");

View File

@ -26,7 +26,7 @@ import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
* Department DTO
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package diboot.core.test.binder.dto;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.Comparison;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
import lombok.Data;
/**
* User DTO
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
*/
@Data
public class UserDTO extends User {
// 字段关联
@BindQuery(entity= Department.class, field = "name", condition="this.department_id=id") // AND parent_id >= 0
private String deptName;
// 字段关联
@BindQuery(entity= Department.class, field = "id", condition="this.department_id=id") // AND parent_id >= 0
private Long deptId;
// 通过中间表关联Entity
@BindQuery(comparison = Comparison.CONTAINS, entity = Organization.class, field = "name",
condition = "this.department_id=department.id AND department.org_id=id AND parent_id=0")
private String orgName;
// LEFT JOIN department r2m ON self.department_id = r2m.id
// LEFT JOIN organization r1 ON r2m.org_id=r2.id
@BindQuery(entity = Role.class, field = "code", condition = "this.id=user_role.user_id AND user_role.role_id=id")
private String roleCode;
// LEFT JOIN user_role r3m ON self.id = r3m.user_id
// LEFT JOIN role r3 ON r3m.role_id = r3.id
}

View File

@ -24,7 +24,7 @@ import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
* Department
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package diboot.core.test.binder.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.entity.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 用户角色
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/30
*/
@Getter
@Setter
@Accessors(chain = true)
public class UserRole extends BaseEntity {
private static final long serialVersionUID = 3030761344045195972L;
@TableField
private Long userId;
@TableField
private Long roleId;
@TableField(exist = false)
private boolean deleted;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.UserRole;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户角色Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
*/
@Mapper
public interface UserRoleMapper extends BaseCrudMapper<UserRole> {
}

View File

@ -24,7 +24,7 @@ import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
* Department VO
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27

View File

@ -40,7 +40,7 @@ public class EntityListComplexBinderVO extends User {
private String userType = "OrgUser";
// 支持通过中间表的多-多Entity实体关联
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id AND user_role.user_id>1")
private List<Role> roleList;
// 支持通过中间表的多-多Entity的单个属性集