Merge branch 'develop' of https://github.com/dibo-software/diboot-v2 into develop
This commit is contained in:
commit
e1b0e9541b
17
README.md
17
README.md
|
@ -13,20 +13,22 @@
|
|||
> [设计目标](https://segmentfault.com/a/1190000020906742):面向开发人员的低代码开发平台,将重复性的工作自动化,提高质量、效率、可维护性。
|
||||
|
||||
![diboot平台组成结构图](diboot-docs/.vuepress/public/structure.png)
|
||||
diboot v2版本,目前实现: diboot-core全新内核 + diboot-devtools开发助理 + IAM身份认证、file文件处理等基础组件 + diboot-*-admin基础后台。
|
||||
diboot v2版本,目前实现: diboot-core高效内核 + diboot-devtools开发助理 + IAM身份认证、file文件处理等基础组件 + diboot-*-admin基础后台。
|
||||
|
||||
## 一、 diboot-core: 精简优化内核
|
||||
全新精简内核,《高性能MySQL》重构查询方式的最佳实践,主要实现:
|
||||
高效精简内核,重构查询方式(拆解关联查询,程序中Join),简化开发,主要实现:
|
||||
#### 1. 单表CRUD无SQL
|
||||
> 基于Mybatis-Plus实现(Mybatis-Plus具备通用Mapper方案和灵活的查询构造器)
|
||||
#### 2. 关联查询绑定无SQL(注解自动绑定)
|
||||
#### 2. 关联绑定无SQL(注解自动绑定)
|
||||
> 扩展实现了多表关联查询的无SQL方案,只需要一个简单注解@Bind*,就可以实现关联对象(含字段、字段集合、实体、实体集合等)的数据绑定,且实现方案是将关联查询拆解为单表查询,保障最佳性能。
|
||||
#### 3. 数据字典无SQL(注解自动绑定)
|
||||
> 通过@BindDict注解实现数据字典(枚举)的存储值value与显示值name的转换。
|
||||
#### 4. 跨表查询无SQL(QueryWrapper自动构建与查询)
|
||||
> @BindQuery注解绑定字段参数对应的查询条件及关联表,自动将请求参数绑定转换为QueryWrapper,并动态执行单表或Join联表查询。
|
||||
#### 5. 其他常用Service接口、工具类的最佳实践封装
|
||||
> 字符串处理、常用校验、BeanUtils、DateUtils等
|
||||
#### 4. 跨表查询无SQL(自动构建QueryWrapper与查询)
|
||||
> @BindQuery注解绑定字段查询方式及关联表,自动构建QueryWrapper,并动态执行单表或Join联表查询。
|
||||
#### 5. BaseService扩展增强,支持常规的单表及关联开发场景接口
|
||||
> createEntityAndRelatedEntities、getValuesOfField、exists、getKeyValueList、getViewObject*等接口
|
||||
#### 6. 其他常用工具类、状态码、异常处理的最佳实践封装
|
||||
> JsonResult、字符串处理、常用校验、BeanUtils、DateUtils等
|
||||
|
||||
基于diboot-core 2.x版本的CRUD和简单关联的常规功能实现,代码量比传统Mybatis项目减少80%+),且实现更高效更易维护。
|
||||
更多介绍请查看: [diboot-core README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core "注解自动绑定多表关联").
|
||||
|
@ -47,6 +49,7 @@ diboot v2版本,目前实现: diboot-core全新内核 + diboot-devtools开发
|
|||
* RBAC角色权限模型 + JWT的认证授权 实现,支持刷新token
|
||||
* 简化的BindPermission注解,支持兼容shiro的简化权限绑定与自动鉴权
|
||||
* 自动提取需要验证的后端接口, 借助前端功能方便绑定前后端菜单按钮权限
|
||||
* 支持基于注解的数据权限实现
|
||||
* 支持灵活的扩展能力(扩展多种登录方式、灵活替换用户实体类、自定义缓存等)
|
||||
* Starter启动自动安装依赖的数据表
|
||||
* 启用devtools,自动生成初始controller代码到本地
|
||||
|
|
|
@ -82,7 +82,6 @@ public class CoreAutoConfiguration{
|
|||
// 设置fastjson的序列化参数:禁用循环依赖检测,数据兼容浏览器端(避免JS端Long精度丢失问题)
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect,
|
||||
SerializerFeature.BrowserCompatible);
|
||||
fastJsonConfig.setDateFormat(D.FORMAT_DATETIME_Y4MDHM);
|
||||
converter.setFastJsonConfig(fastJsonConfig);
|
||||
|
||||
HttpMessageConverter<?> httpMsgConverter = converter;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## diboot-core: 全新优化内核
|
||||
## diboot-core: 高效精简内核
|
||||
主要实现:
|
||||
1. 单表CRUD和多表关联查询的无SQL化
|
||||
2. Entity/DTO自动转换为QueryWrapper(@BindQuery注解绑定字段参数的查询条件,可自动构建关联查询)
|
||||
|
|
|
@ -39,7 +39,7 @@ import java.util.*;
|
|||
/**
|
||||
* join连接查询绑定器
|
||||
* @author Mazc@dibo.ltd
|
||||
* @version v2.0.5
|
||||
* @version v2.1
|
||||
* @date 2020/04/15
|
||||
*/
|
||||
@Slf4j
|
||||
|
|
|
@ -121,14 +121,19 @@ public class QueryBuilder {
|
|||
* @return
|
||||
*/
|
||||
private static <DTO> QueryWrapper<DTO> dtoToWrapper(DTO dto, Collection<String> fields){
|
||||
QueryWrapper wrapper;
|
||||
// 转换
|
||||
LinkedHashMap<String, Object> fieldValuesMap = extractNotNullValues(dto, fields);
|
||||
if(V.isEmpty(fieldValuesMap)){
|
||||
return new QueryWrapper<>();
|
||||
wrapper = new QueryWrapper<>();
|
||||
// 附加数据访问条件
|
||||
attachDataAccessCondition(wrapper, dto.getClass());
|
||||
return wrapper;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -256,7 +261,7 @@ public class QueryBuilder {
|
|||
dataAccessCheckInstance = ContextHelper.getBean(DataAccessCheckInterface.class);
|
||||
dataAccessCheckInstanceChecked = true;
|
||||
}
|
||||
if(dataAccessCheckInstance != null){
|
||||
if(dataAccessCheckInstance != null && DataAccessAnnoCache.hasDataAccessCheckpoint(dtoClass)){
|
||||
NormalSegmentList segments = queryWrapper.getExpression().getNormal();
|
||||
for(CheckpointType type : CheckpointType.values()){
|
||||
String idCol = DataAccessAnnoCache.getDataPermissionColumn(dtoClass, type);
|
||||
|
@ -337,7 +342,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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 关联字段绑定
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.copy;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 拷贝字段时的非同名字段处理
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.1
|
||||
* @date 2020/06/04
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
@Documented
|
||||
public @interface Accept {
|
||||
/**
|
||||
* 接收来源对象的属性名
|
||||
* @return
|
||||
*/
|
||||
String name();
|
||||
/**
|
||||
* source该字段有值时是否覆盖
|
||||
* @return
|
||||
*/
|
||||
boolean override() default false;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.copy;
|
||||
|
||||
import com.diboot.core.util.BeanUtils;
|
||||
import com.diboot.core.util.V;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Accept注解拷贝器
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v1.0
|
||||
* @date 2020/06/04
|
||||
*/
|
||||
@Slf4j
|
||||
public class AcceptAnnoCopier {
|
||||
/**
|
||||
* 注解缓存
|
||||
*/
|
||||
private static Map<String, List<String[]>> CLASS_ACCEPT_ANNO_CACHE_MAP = new ConcurrentHashMap<>();
|
||||
// 下标
|
||||
private static final int IDX_TARGET_FIELD = 0, IDX_SOURCE_FIELD = 1, IDX_OVERRIDE = 2;
|
||||
|
||||
/**
|
||||
* 基于注解拷贝属性
|
||||
* @param source
|
||||
* @param target
|
||||
*/
|
||||
public static void copyAcceptProperties(Object source, Object target){
|
||||
String key = target.getClass().getName();
|
||||
// 初始化
|
||||
if(!CLASS_ACCEPT_ANNO_CACHE_MAP.containsKey(key)){
|
||||
List<Field> annoFieldList = BeanUtils.extractFields(target.getClass(), Accept.class);
|
||||
if(V.isEmpty(annoFieldList)){
|
||||
CLASS_ACCEPT_ANNO_CACHE_MAP.put(key, Collections.EMPTY_LIST);
|
||||
}
|
||||
else{
|
||||
List<String[]> annoDefList = new ArrayList<>(annoFieldList.size());
|
||||
for(Field fld : annoFieldList){
|
||||
Accept accept = fld.getAnnotation(Accept.class);
|
||||
String[] annoDef = {fld.getName(), accept.name(), accept.override()? "1":"0"};
|
||||
annoDefList.add(annoDef);
|
||||
}
|
||||
CLASS_ACCEPT_ANNO_CACHE_MAP.put(key, annoDefList);
|
||||
}
|
||||
}
|
||||
// 解析copy
|
||||
List<String[]> acceptAnnos = CLASS_ACCEPT_ANNO_CACHE_MAP.get(key);
|
||||
if(V.isEmpty(acceptAnnos)){
|
||||
return;
|
||||
}
|
||||
for(String[] annoDef : acceptAnnos){
|
||||
boolean override = !"0".equals(annoDef[IDX_OVERRIDE]);
|
||||
if(!override){
|
||||
Object targetValue = BeanUtils.getProperty(target, annoDef[IDX_TARGET_FIELD]);
|
||||
if(targetValue != null){
|
||||
log.debug("目标对象{}已有值{},copyAcceptProperties将忽略.", key, targetValue);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Object sourceValue = BeanUtils.getProperty(source, annoDef[IDX_SOURCE_FIELD]);
|
||||
if(sourceValue != null){
|
||||
BeanUtils.setProperty(target, annoDef[IDX_TARGET_FIELD], sourceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,12 +23,11 @@ package com.diboot.core.binding.data;
|
|||
*/
|
||||
public enum CheckpointType {
|
||||
|
||||
USER(0), // 相关用户
|
||||
ORG(1), // 组织
|
||||
USER(0), // 用户范围
|
||||
ORG(1), // 组织范围
|
||||
POSITION(2), // 岗位范围
|
||||
|
||||
OBJ_1(2), // 其他1
|
||||
OBJ_2(3), // 其他2
|
||||
OBJ_3(4); // 其他3
|
||||
EXT_OBJ(3); // 扩展对象范围
|
||||
|
||||
private int index;
|
||||
CheckpointType(int index){
|
||||
|
|
|
@ -39,7 +39,26 @@ public class DataAccessAnnoCache {
|
|||
/**
|
||||
* 注解缓存
|
||||
*/
|
||||
private static Map<String, String> DATA_PERMISSION_ANNO_CACHE = new ConcurrentHashMap<>();
|
||||
private static Map<String, String[]> DATA_PERMISSION_ANNO_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 是否有检查点注解
|
||||
* @param entityDto
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasDataAccessCheckpoint(Class<?> entityDto){
|
||||
initClassCheckpoint(entityDto);
|
||||
String[] columns = DATA_PERMISSION_ANNO_CACHE.get(entityDto.getName());
|
||||
if(V.isEmpty(columns)){
|
||||
return false;
|
||||
}
|
||||
for(String type : columns){
|
||||
if(V.notEmpty(type)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限的用户类型列名
|
||||
|
@ -47,7 +66,21 @@ public class DataAccessAnnoCache {
|
|||
* @return
|
||||
*/
|
||||
public static String getDataPermissionColumn(Class<?> entityDto, CheckpointType type){
|
||||
initClassCheckpoint(entityDto);
|
||||
int typeIndex = type.index();
|
||||
String key = entityDto.getName();
|
||||
String[] columns = DATA_PERMISSION_ANNO_CACHE.get(key);
|
||||
if(columns != null && (columns.length-1) >= typeIndex){
|
||||
return columns[typeIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化entityDto的检查点缓存
|
||||
* @param entityDto
|
||||
*/
|
||||
private static void initClassCheckpoint(Class<?> entityDto){
|
||||
String key = entityDto.getName();
|
||||
if(!DATA_PERMISSION_ANNO_CACHE.containsKey(key)){
|
||||
String[] results = {"", "", "", "", ""};
|
||||
|
@ -61,13 +94,8 @@ public class DataAccessAnnoCache {
|
|||
results[checkpoint.type().index()] = QueryBuilder.getColumnName(fld);
|
||||
}
|
||||
}
|
||||
DATA_PERMISSION_ANNO_CACHE.put(key, S.join(results));
|
||||
DATA_PERMISSION_ANNO_CACHE.put(key, results);
|
||||
}
|
||||
String[] columns = S.split(DATA_PERMISSION_ANNO_CACHE.get(key));
|
||||
if(columns != null && (columns.length-1) >= typeIndex){
|
||||
return columns[typeIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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@dibo.ltd
|
||||
* @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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())){
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.annotation.*;
|
|||
import com.diboot.core.config.Cons;
|
||||
import com.diboot.core.util.BeanUtils;
|
||||
import com.diboot.core.util.ContextHelper;
|
||||
import com.diboot.core.util.D;
|
||||
import com.diboot.core.util.JSON;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -41,13 +42,13 @@ import java.util.Map;
|
|||
public abstract class BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 10203L;
|
||||
|
||||
/***
|
||||
/**
|
||||
* 默认主键字段id,类型为Long型自增,转json时转换为String
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/***
|
||||
/**
|
||||
* 默认逻辑删除标记,is_deleted=0有效
|
||||
*/
|
||||
@TableLogic
|
||||
|
@ -55,9 +56,10 @@ public abstract class BaseEntity implements Serializable {
|
|||
@TableField(Cons.COLUMN_IS_DELETED)
|
||||
private boolean deleted = false;
|
||||
|
||||
/***
|
||||
/**
|
||||
* 默认记录创建时间字段,新建时由数据库赋值
|
||||
*/
|
||||
@JSONField(format = D.FORMAT_DATETIME_Y4MDHMS)
|
||||
@TableField(insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
|
||||
private Date createTime;
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ public interface BaseService<T> {
|
|||
|
||||
/**
|
||||
* 获取符合条件的一个Entity实体
|
||||
* @param queryWrapper 主键
|
||||
* @param queryWrapper
|
||||
* @return entity
|
||||
*/
|
||||
T getSingleEntity(Wrapper queryWrapper);
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
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.binding.copy.AcceptAnnoCopier;
|
||||
import com.diboot.core.config.Cons;
|
||||
import com.diboot.core.entity.BaseEntity;
|
||||
import com.diboot.core.exception.BusinessException;
|
||||
|
@ -75,6 +72,8 @@ public class BeanUtils {
|
|||
public static Object copyProperties(Object source, Object target){
|
||||
// 链式调用无法使用BeanCopier拷贝,换用BeanUtils
|
||||
org.springframework.beans.BeanUtils.copyProperties(source, target);
|
||||
// 处理Accept注解标识的不同字段名拷贝
|
||||
AcceptAnnoCopier.copyAcceptProperties(source, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)){
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -26,7 +26,7 @@ import lombok.Setter;
|
|||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
* Department DTO
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
* @date 2018/12/27
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -24,7 +24,7 @@ import lombok.Setter;
|
|||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
* Department
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
* @date 2018/12/27
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package diboot.core.test.binder.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.diboot.core.binding.copy.Accept;
|
||||
import com.diboot.core.entity.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -38,6 +39,7 @@ public class User extends BaseEntity {
|
|||
@TableField
|
||||
private String username;
|
||||
|
||||
@Accept(name = "itemName")
|
||||
@TableField
|
||||
private String gender;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ import lombok.Setter;
|
|||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
* Department VO
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
* @date 2018/12/27
|
||||
|
@ -44,11 +44,11 @@ public class DepartmentVO {
|
|||
@TableField
|
||||
private Long orgId;
|
||||
|
||||
// 通过中间表关联Entity
|
||||
// 关联Entity
|
||||
@BindEntity(entity = Department.class, condition = "this.parent_id=id") // AND ...
|
||||
private Department department;
|
||||
|
||||
// 通过中间表关联Entity
|
||||
// 关联Entity,赋值给VO
|
||||
@BindEntity(entity = Organization.class, condition = "this.org_id=id") // AND ...
|
||||
private OrganizationVO organizationVO;
|
||||
|
||||
|
|
|
@ -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的单个属性集
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||
import com.diboot.core.entity.Dictionary;
|
||||
import com.diboot.core.util.BeanUtils;
|
||||
import com.diboot.core.vo.DictionaryVO;
|
||||
import diboot.core.test.binder.entity.User;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -57,6 +58,14 @@ public class BeanUtilsTest {
|
|||
Assert.assertTrue(dictionary3.getItemName().equals(itemName));
|
||||
Assert.assertTrue(dictionary3.isEditable() == true);
|
||||
Assert.assertTrue(dictionary3.getCreateTime() != null);
|
||||
|
||||
// Accept注解拷贝
|
||||
User user = new User();
|
||||
BeanUtils.copyProperties(dictionary3, user);
|
||||
Assert.assertTrue(user.getGender().equals(dictionary3.getItemName()));
|
||||
user.setGender("123");
|
||||
BeanUtils.copyProperties(dictionary3, user);
|
||||
Assert.assertTrue(user.getGender().equals("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -51,11 +51,13 @@ public class DTest {
|
|||
JsonResult j2 = new JsonResult(token, "申请token成功");
|
||||
JsonResult j3 = new JsonResult(Status.OK, token);
|
||||
JsonResult j4 = new JsonResult(Status.OK, token, "申请token成功");
|
||||
JsonResult j5 = new JsonResult(Status.OK);
|
||||
JsonResult j5 = JsonResult.OK();
|
||||
JsonResult j6 = JsonResult.FAIL_VALIDATION("xxx验证错误");
|
||||
System.out.println(j1.getData());
|
||||
System.out.println(j2.getData());
|
||||
System.out.println(j3.getData());
|
||||
System.out.println(j4.getData());
|
||||
System.out.println(j5.getData());
|
||||
Assert.assertTrue(j6.getMsg().contains("xxx验证错误"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,14 @@ module.exports = {
|
|||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
['/guide/diboot-core/安装', '安装'],
|
||||
['/guide/diboot-core/设计理念', '设计理念'],
|
||||
['/guide/diboot-core/简介', '简介'],
|
||||
['/guide/diboot-core/实体Entity', 'Entity相关'],
|
||||
['/guide/diboot-core/Service与实现', 'Service相关'],
|
||||
['/guide/diboot-core/Service接口', 'Service相关'],
|
||||
['/guide/diboot-core/Mapper及自定义', 'Mapper相关'],
|
||||
['/guide/diboot-core/Controller接口', 'Controller相关'],
|
||||
['/guide/diboot-core/无SQL关联', '无SQL关联绑定'],
|
||||
['/guide/diboot-core/查询条件DTO', '查询条件DTO'],
|
||||
['/guide/diboot-core/无SQL关联绑定', '无SQL关联绑定'],
|
||||
['/guide/diboot-core/无SQL跨表查询', '无SQL跨表查询'],
|
||||
['/guide/diboot-core/常用工具类', '常用工具类']
|
||||
]
|
||||
}
|
||||
|
@ -94,14 +95,15 @@ module.exports = {
|
|||
]
|
||||
}
|
||||
],
|
||||
'/guide/faq/': [
|
||||
'/guide/notes/': [
|
||||
{
|
||||
title: 'F&Q',
|
||||
title: '版本&FAQ',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
['/guide/faq/devtools', 'devtools开发助理'],
|
||||
['/guide/faq/iam', 'IAM 组件']
|
||||
['/guide/notes/release', 'Release notes']
|
||||
['/guide/notes/faq', 'FAQ'],
|
||||
['/guide/notes/upgrade', '版本升级指南'],
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -111,10 +113,10 @@ module.exports = {
|
|||
}, {
|
||||
text: '基础组件 指南',
|
||||
items: [
|
||||
{ text: 'F&Q', link: '/guide/faq/devtools' },
|
||||
{ text: 'core基础内核', link: '/guide/diboot-core/安装' },
|
||||
{ text: 'IAM身份认证', link: '/guide/diboot-iam/介绍' },
|
||||
{ text: 'File文件组件', link: '/guide/diboot-file/介绍' }
|
||||
{ text: 'File文件组件', link: '/guide/diboot-file/介绍' },
|
||||
{ text: 'F&Q', link: '/guide/faq/devtools' }
|
||||
]
|
||||
}, {
|
||||
text: '前端项目 指南',
|
||||
|
@ -149,11 +151,9 @@ module.exports = {
|
|||
link: 'https://github.com/dibo-software/diboot-v2'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
}/*, {
|
||||
text: '优秀案例',
|
||||
link: '/other/excellentExample'
|
||||
},{
|
||||
text: '1.x旧版', link: 'https://www.diboot.com/diboot-v1/'
|
||||
}]
|
||||
}*/]
|
||||
}
|
||||
}
|
|
@ -6,118 +6,64 @@
|
|||
|
||||
## BaseCrudRestController
|
||||
|
||||
> 增删改查通用controller,以后的controller都可以继承该类,减少代码量。
|
||||
接下来会对BaseCrudRestController中的一些通用方法进行介绍。
|
||||
> 增删改查通用controller, Entity对应的controller都可以继承该类,减少代码量。通用方法:
|
||||
|
||||
* getService 抽象方法
|
||||
```java
|
||||
/**
|
||||
* 获取service实例
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract BaseService getService();
|
||||
```
|
||||
该方法是用来获取当前类中相关业务Service实例。
|
||||
所有继承了BaseCrudRestController的实体类都要实现该方法,如下:
|
||||
* getService 方法实现
|
||||
```java
|
||||
@Autowired
|
||||
private DictionaryService dictionaryService;
|
||||
* protected BaseService getService() 方法
|
||||
该方法是用来获取当前Entity中相关的Service实例。
|
||||
|
||||
@Override
|
||||
protected BaseService getService() {
|
||||
return dictionaryService;
|
||||
}
|
||||
```
|
||||
|
||||
* getEntityList 方法
|
||||
* getViewObject 获取详情页VO
|
||||
```java
|
||||
//方法定义
|
||||
protected JsonResult getEntityList(Wrapper queryWrapper) {...}
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.getEntityList(queryWrapper);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
JsonResult getViewObject(Serializable id, Class<VO> voClass)
|
||||
//子类调用示例
|
||||
JsonResult jsonResult = super.getViewObject(id, UserDetailVO.class);
|
||||
```
|
||||
该方法用于获取数据集合,入参为查询条件(queryWrapper),
|
||||
调用该方法成功后会返回所有符合查询条件的数据集合,该方法无分页功能。
|
||||
该方法用于获取单个对象的详情VO视图对象。
|
||||
|
||||
* getEntityListWithPaging 方法
|
||||
```java
|
||||
//方法定义
|
||||
protected JsonResult getEntityListWithPaging(Wrapper queryWrapper, Pagination pagination) {...}
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.getEntityListWithPaging(queryWrapper,pagination);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
```
|
||||
该方法用于获取数据集合,入参为查询条件(queryWrapper)、分页条件(pagination),
|
||||
调用该方法成功后会返回符合查询条件的当前页数据集合,该方法有分页功能。
|
||||
|
||||
* getVOListWithPaging 方法
|
||||
* getVOListWithPaging 获取列表页VO(带分页)
|
||||
```java
|
||||
//方法定义
|
||||
protected <T> JsonResult getVOListWithPaging(Wrapper queryWrapper, Pagination pagination, Class<T> clazz) {...}
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.getVOListWithPaging(queryWrapper,pagination,Organization.class);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
//调用示例
|
||||
JsonResult jsonResult = super.getVOListWithPaging(queryWrapper, pagination, UserListVO.class);
|
||||
```
|
||||
该方法用来获取数据VO集合,入参为查询条件(queryWrapper)、分页条件(pagination)、类类型(clazz),
|
||||
调用该方法成功后会返回符合查询条件的当前页数据VO集合,该方法有分页功能。
|
||||
列表页查询与分页的url参数示例: /${bindURL}?pageSize=20&pageIndex=1&orderBy=itemValue&type=GENDAR
|
||||
orderBy排序: 格式为“”“”"字段:排序方式",如"id:DESC",多个以,分隔
|
||||
|
||||
* createEntity 方法
|
||||
* getEntityList(queryWrapper),getEntityListWithPaging(queryWrapper, pagination)
|
||||
返回entity对象的集合,供子类需要时调用
|
||||
|
||||
* createEntity 新建保存Entity
|
||||
```java
|
||||
//方法定义
|
||||
protected JsonResult createEntity(BaseEntity entity, BindingResult result) {...}
|
||||
protected JsonResult createEntity(E entity)
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.createEntity(entity,result);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
JsonResult jsonResult = super.createEntity(entity);
|
||||
```
|
||||
该方法用来新建数据,入参为数据实体(entity)、绑定结果(result),调用该方法成功后会在相关表中插入一条数据。
|
||||
|
||||
* updateEntity 方法
|
||||
* updateEntity 根据ID更新Entity
|
||||
```java
|
||||
//方法定义
|
||||
protected JsonResult updateEntity(BaseEntity entity, BindingResult result) {...}
|
||||
protected JsonResult updateEntity(Serializable id, E entity)
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.updateEntity(entity,result);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
JsonResult jsonResult = super.updateEntity(id, entity);
|
||||
```
|
||||
该方法用来更新数据,入参为数据实体(entity)、绑定结果(result),调用该方法成功后会更新相关表中的数据。
|
||||
|
||||
* deleteEntity 方法
|
||||
* deleteEntity 根据ID删除Entity
|
||||
```java
|
||||
//方法定义
|
||||
protected JsonResult deleteEntity(Serializable id) {...}
|
||||
//方法调用示例
|
||||
JsonResult jsonResult = super.deleteEntity(id);
|
||||
System.out.println(jsonResult.getCode()==0);
|
||||
//执行结果
|
||||
===> true
|
||||
```
|
||||
该方法用来删除数据,入参为数据ID(id),调用该方法成功后会删除相关表中的数据。
|
||||
|
||||
* convertToVoAndBindRelations 方法
|
||||
* batchDeleteEntities 批量删除Entities
|
||||
```java
|
||||
//方法定义
|
||||
protected <VO> List<VO> convertToVoAndBindRelations(List entityList, Class<VO> voClass) {...}
|
||||
//方法定义
|
||||
protected JsonResult batchDeleteEntities(Collection<? extends Serializable> ids)
|
||||
//方法调用示例
|
||||
List<OrganizationVO> orgVOList = super.convertToVoAndBindRelations(entityList, OrganizationVO.class);
|
||||
System.out.println(orgVOList.size()>0);
|
||||
//执行结果
|
||||
===> true
|
||||
JsonResult jsonResult = super.batchDeleteEntities(ids);
|
||||
```
|
||||
该方法用来将数据实体集合转化为数据实体VO集合,入参为实体集合(entityList)、类类型(voClass),
|
||||
调用该方法成功后返回数据实体VO集合。
|
||||
|
||||
* beforeCreate 方法
|
||||
```java
|
||||
|
@ -126,7 +72,7 @@ protected String beforeCreate(BaseEntity entity){...}
|
|||
//方法调用示例
|
||||
String str = this.beforeCreate(entity);
|
||||
```
|
||||
该方法用来处理新建数据之前的逻辑,如数据校验等,需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
|
||||
该方法用来处理新建数据之前的检查/预处理逻辑,如数据校验等,供子类重写实现。
|
||||
|
||||
* afterCreated 方法
|
||||
```java
|
||||
|
@ -135,7 +81,8 @@ String str = this.beforeCreate(entity);
|
|||
//方法调用示例
|
||||
String str = this.afterCreated(entity);
|
||||
```
|
||||
该方法用来处理新建数据之后的逻辑,需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
|
||||
该方法用来处理新建数据之后的逻辑,如日志记录等,供子类重写实现。
|
||||
注意:该接口在create entity创建完成之后调用,请勿用于事务性处理。
|
||||
|
||||
* beforeUpdate 方法
|
||||
```java
|
||||
|
@ -144,7 +91,7 @@ protected String beforeUpdate(BaseEntity entity){...}
|
|||
//方法调用示例
|
||||
String str = this.beforeUpdate(entity);
|
||||
```
|
||||
该方法用来处理更新数据之前的逻辑,需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
|
||||
该方法用来处理更新数据之前的逻辑,供子类重写实现。
|
||||
|
||||
* afterUpdated 方法
|
||||
```java
|
||||
|
@ -153,7 +100,8 @@ protected String afterUpdated(BaseEntity entity){...}
|
|||
//方法调用示例
|
||||
String str = this.afterUpdated(entity);
|
||||
```
|
||||
该方法用来处理更新数据之后的逻辑,需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
|
||||
该方法用来处理更新数据之后的逻辑,供子类重写实现。
|
||||
注意:该接口在create entity创建完成之后调用,请勿用于事务性处理。
|
||||
|
||||
* beforeDelete 方法
|
||||
```java
|
||||
|
@ -162,7 +110,7 @@ protected String beforeDelete(BaseEntity entity){...}
|
|||
//方法调用示例
|
||||
String str = this.beforeDelete(entity);
|
||||
```
|
||||
该方法主要用来处理删除数据之前的逻辑,如检验是否具有删除权限等,需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
|
||||
该方法主要用来处理删除数据之前的逻辑,如检验是否具有删除权限等,供子类重写实现。
|
||||
|
||||
## 数据校验
|
||||
|
||||
|
@ -195,3 +143,64 @@ public JsonResult createEntity(@Valid @RequestBody Demo entity, BindingResult re
|
|||
}
|
||||
```
|
||||
|
||||
## 统一异常处理
|
||||
异常处理全局实现类为DefaultExceptionHandler,继承自该类并添加@ControllerAdvice注解即可自动支持:
|
||||
* 兼容JSON请求和Html页面请求的Exception异常处理
|
||||
* Entity绑定校验的统一处理(BindException.class, MethodArgumentNotValidException.class)
|
||||
|
||||
* 示例代码
|
||||
~~~java
|
||||
@ControllerAdvice
|
||||
public class GeneralExceptionHandler extends DefaultExceptionHandler{
|
||||
}
|
||||
~~~
|
||||
|
||||
## JsonResult 格式
|
||||
```json
|
||||
{
|
||||
code: 0,
|
||||
msg: "OK",
|
||||
data: {
|
||||
}
|
||||
}
|
||||
```
|
||||
调用方式
|
||||
```java
|
||||
JsonResult okResult = JsonResult.OK();
|
||||
JsonResult failResult = JsonResult.FAIL_VALIDATION("xxx验证错误");
|
||||
```
|
||||
Status状态码定义:
|
||||
```java
|
||||
//请求处理成功
|
||||
OK(0, "操作成功"),
|
||||
|
||||
// 部分成功(一般用于批量处理场景,只处理筛选后的合法数据)
|
||||
WARN_PARTIAL_SUCCESS(1001, "部分成功"),
|
||||
|
||||
//有潜在的性能问题
|
||||
WARN_PERFORMANCE_ISSUE(1002, "潜在的性能问题"),
|
||||
|
||||
// 传入参数不对
|
||||
FAIL_INVALID_PARAM(4000, "请求参数不匹配"),
|
||||
|
||||
// Token无效或已过期
|
||||
FAIL_INVALID_TOKEN(4001, "Token无效或已过期"),
|
||||
|
||||
// 没有权限执行该操作
|
||||
FAIL_NO_PERMISSION(4003, "无权执行该操作"),
|
||||
|
||||
// 请求资源不存在
|
||||
FAIL_NOT_FOUND(4004, "请求资源不存在"),
|
||||
|
||||
// 数据校验不通过
|
||||
FAIL_VALIDATION(4005, "数据校验不通过"),
|
||||
|
||||
// 操作执行失败
|
||||
FAIL_OPERATION(4006, "操作执行失败"),
|
||||
|
||||
// 后台异常
|
||||
FAIL_EXCEPTION(5000, "系统异常"),
|
||||
|
||||
// 缓存清空
|
||||
MEMORY_EMPTY_LOST(9999, "缓存清空");
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
# Mapper及自定义
|
||||
> 如果您的mapper.xml文件放置于src下的mapper接口同目录下,需要配置编译包含该路径下的xml文件。具体参考如下:
|
||||
|
||||
## Gradle设置
|
||||
> 如果您使用了gradle,并且您的Mapper.xml文件实在java代码目录下,那么除了MapperScan的注解之外,还需要对gradle进行相关设置,使其能够找到相对应的Mapper.xml文件,如:
|
||||
* Gradle设置
|
||||
```groovy
|
||||
sourceSets {
|
||||
main {
|
||||
|
@ -18,9 +18,7 @@ sourceSets {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Maven设置
|
||||
> 如果您使用Maven作为您的构建工具,并且您的Mapper.xml文件是在java代码目录下,那么除了MapperScan的注解之外,还需要再项目中的pom.xml文件中的添加如下配置,使其能够找到您的Mapper.xml文件,如:
|
||||
* Maven设置
|
||||
```xml
|
||||
<build>
|
||||
<resources>
|
||||
|
@ -39,15 +37,11 @@ sourceSets {
|
|||
## Mapper类
|
||||
> Mapper类需要继承diboot-core中的BaseCrudMapper基础类,并传入相对应的实体类,如:
|
||||
```java
|
||||
package com.example.demo.mapper;
|
||||
|
||||
import com.diboot.core.mapper.BaseCrudMapper;
|
||||
import com.example.demo.entity.Demo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DemoMapper extends BaseCrudMapper<Demo> {
|
||||
|
||||
}
|
||||
```
|
||||
* BaseCrudMapper类继承了mybatis-plus提供的BaseMapper类,对于BaseCrudMapper中已有的相关接口可以参考[mybatis-plus关于Mapper类的文档](https://mybatis.plus/guide/crud-interface.html#mapper-crud-%E6%8E%A5%E5%8F%A3)
|
||||
|
@ -64,20 +58,15 @@ public interface DemoMapper extends BaseCrudMapper<Demo> {
|
|||
|
||||
## 自定义Mapper接口
|
||||
|
||||
> 自定义Mapper接口可以使用mybatis增加mapper接口的处理方案,如:
|
||||
> 自定义Mapper接口使用Mybatis处理方式,增加mapper接口的处理方案,如:
|
||||
```java
|
||||
package com.example.demo.mapper;
|
||||
|
||||
import com.diboot.core.mapper.BaseCrudMapper;
|
||||
import com.example.demo.entity.Demo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface DemoMapper extends BaseCrudMapper<Demo> {
|
||||
|
||||
int forceDeleteEntities(@Param("name") String name);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -85,10 +74,10 @@ public interface DemoMapper extends BaseCrudMapper<Demo> {
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.example.demo.mapper.DemoMapper">
|
||||
|
||||
|
||||
<update id="forceDeleteEntities" parameterType="String">
|
||||
DELETE FROM demo WHERE name=#{name}
|
||||
</update>
|
||||
|
||||
|
||||
</mapper>
|
||||
```
|
|
@ -1,153 +1,31 @@
|
|||
# Service与实现
|
||||
# BaseService接口
|
||||
|
||||
## Service类
|
||||
## BaseService类
|
||||
|
||||
> 对于一个自定义的entity,我们推荐您继承diboot-core中封装好的BaseService接口及BaseServiceImpl实现。
|
||||
> 对于entity对应的service,推荐继承diboot-core中封装好的BaseService接口及BaseServiceImpl实现,以使用增强扩展。
|
||||
|
||||
```java
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.diboot.core.service.BaseService;
|
||||
import com.example.demo.entity.Demo;
|
||||
|
||||
public interface DemoService extends BaseService<Demo> {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
> 提示:BaseService类并没有继承mybatis-plus中的IService接口,如果需要使用mybatis-plus中的IService接口,需要单独继承IService类.
|
||||
> 提示:BaseService类具备mybatis-plus中的IService接口大多数接口,但并没有继承IService,如果需要使用IService接口,可单独继承IService类.
|
||||
|
||||
## 相关接口
|
||||
## 查询相关接口
|
||||
|
||||
### getEntity
|
||||
```java
|
||||
T getEntity(Serializable id);
|
||||
```
|
||||
> getEntity接口可以通过一个主键参数得到数据库中的一个实体,如:
|
||||
> getEntity接口可以通过一个主键值得到数据库中的对应记录转换为Entity,如:
|
||||
```java
|
||||
Demo demo = demoService.getEntity(id);
|
||||
```
|
||||
|
||||
### createEntity
|
||||
### getSingleEntity
|
||||
> 获取符合条件的一个Entity实体
|
||||
```java
|
||||
boolean createEntity(T entity);
|
||||
```
|
||||
> createEntity接口将一个entity添加到数据库中,返回成功与失败。通过该接口创建记录成功后,会将新建记录的主键自动设置到该entity中,如:
|
||||
|
||||
```java
|
||||
boolean success = demoService.createEntity(demo);
|
||||
System.out.println(demo.getId());
|
||||
// 输出结果
|
||||
===> 1001
|
||||
```
|
||||
|
||||
### updateEntity
|
||||
```java
|
||||
boolean updateEntity(T entity);
|
||||
boolean updateEntity(T entity, Wrapper updateCriteria);
|
||||
boolean updateEntity(Wrapper updateWrapper);
|
||||
```
|
||||
* updateEntity接口可以根据该实体的主键值来更新整个entity的所有字段内容到数据库,返回成功与失败,如:
|
||||
```java
|
||||
boolean success = demoService.updateEntity(demo);
|
||||
```
|
||||
|
||||
* 该接口也可以通过条件更新对应的字段(可以通过条件设置需要更新的字段,以及需要更新记录的条件限制),返回成功与失败,如:
|
||||
```java
|
||||
/*** 将demo中所有可用的记录的name都更新为“张三” */
|
||||
Demo demo = new Demo();
|
||||
demo.setName("张三");
|
||||
// 设置更新条件
|
||||
LambdaQueryWrapper<Demo> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Demo::isDeleted, false);
|
||||
// 执行更新操作
|
||||
boolean success = demoService.updateEntity(demo, wrapper);
|
||||
System.out.println(success);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
```
|
||||
|
||||
* 该接口也可以通过更新条件来执行更新,返回成功与失败,对于更新条件可以参考[mybatis-plus的UpdateWrapper文档](https://mybatis.plus/guide/wrapper.html#updatewrapper)。
|
||||
|
||||
### createOrUpdateEntity
|
||||
```java
|
||||
boolean createOrUpdateEntity(T entity);
|
||||
```
|
||||
> 该接口新建或更新一个实体到数据库,如果该实体主键有值,则更新到数据库,若主键无值,则新建一条记录到数据库,如果主键有值,但数据库中未找到,则报错,如:
|
||||
```java
|
||||
boolean success = demoService.createOrUpdateEntity(demo);
|
||||
System.out.println(success);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
```
|
||||
|
||||
### createOrUpdateEntities
|
||||
```java
|
||||
boolean createOrUpdateEntities(Collection entityList);
|
||||
```
|
||||
> 该接口将对一个Collection类型的列表中的每一个实体进行新建或更新到数据库,如:
|
||||
```java
|
||||
boolean success = demoService.createOrUpdateEntities(demoList);
|
||||
System.out.println(success);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
```
|
||||
|
||||
### createEntityAndRelatedEntities
|
||||
```java
|
||||
/**
|
||||
* 添加entity 及 其关联子项entities
|
||||
* @param entity 主表entity
|
||||
* @param relatedEntities 从表/关联表entities
|
||||
* @param relatedEntitySetter 设置关联从表绑定id的setter,如Dictionary::setParentId
|
||||
* @return
|
||||
*/
|
||||
boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
```
|
||||
> 该接口将对一个1对多关联数据的设置关联id并批量保存,如:
|
||||
```java
|
||||
boolean success = dictionaryService.createEntityAndRelatedEntities(dictionary, dictionaryList, Dictionary::setParentId);
|
||||
// 输出
|
||||
===> true
|
||||
```
|
||||
类似的还有1对多数据的批量更新与删除:
|
||||
~~~java
|
||||
boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
boolean deleteEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
~~~
|
||||
|
||||
### deleteEntity
|
||||
```java
|
||||
boolean deleteEntity(Serializable id);
|
||||
```
|
||||
> 该接口通过主键字段对实体进行删除操作,如:
|
||||
```java
|
||||
boolean success = demoService.deleteEntity(demo.getId());
|
||||
System.out.println(success);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
```
|
||||
|
||||
### deletedEntities
|
||||
```java
|
||||
boolean deleteEntities(Wrapper queryWrapper);
|
||||
```
|
||||
> 该接口通过查询条件对符合该查询条件的所有记录进行删除操作,如:
|
||||
```java
|
||||
/*** 删除所有名称为“张三”的记录 **/
|
||||
// 设置查询条件
|
||||
LambdaQueryWrapper<Demo> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Demo::getName, "张三");
|
||||
// 执行删除操作
|
||||
boolean success = demoService.deleteEntities(wrapper);
|
||||
System.out.println(success);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
T getSingleEntity(Wrapper queryWrapper);
|
||||
```
|
||||
|
||||
### getEntityListCount
|
||||
|
@ -161,10 +39,7 @@ LambdaQueryWrapper<Demo> wrapper = new LambdaQueryWrapper<>();
|
|||
wrapper.eq(Demo::getName, "张三");
|
||||
// 获取总条数
|
||||
int count = demoService.getEntityListCount(wrapper);
|
||||
System.out.println(count);
|
||||
|
||||
// 输出
|
||||
===> true
|
||||
log.debug(count);
|
||||
```
|
||||
|
||||
### getEntityList(Wrapper queryWrapper)
|
||||
|
@ -190,6 +65,15 @@ List<T> getEntityListLimit(Wrapper queryWrapper, int limitCount);
|
|||
List<T> getEntityListByIds(List ids);
|
||||
```
|
||||
|
||||
### getValuesOfField
|
||||
* since v2.1
|
||||
> 获取指定条件的Entity ID或其他字段的集合,示例如:
|
||||
```java
|
||||
QueryWrapper<Dictionary> queryWrapper = new QueryWrapper<>().eq("type", "GENDER");
|
||||
// 提取符合条件的id集合
|
||||
List<Long> ids = dictionaryService.getValuesOfField(queryWrapper, Dictionary::getId);
|
||||
```
|
||||
|
||||
### getMapList
|
||||
> 该方法通过查询条件查询和分页参数出符合条件的Map列表,其中分页参数是可选参数,返回查询出的Map列表。
|
||||
```java
|
||||
|
@ -197,33 +81,125 @@ List<Map<String, Object>> getMapList(Wrapper queryWrapper);
|
|||
List<Map<String, Object>> getMapList(Wrapper queryWrapper, Pagination pagination);
|
||||
```
|
||||
|
||||
### getKeyValueList
|
||||
> 该方法通过查询条件查询出查询出符合条件的 KeyValue 列表,该KeyValue是一个键值对,所以再查询条件中需要指定需要查询的字段。
|
||||
### getKeyValueList, getKeyValueMap
|
||||
> 该方法通过查询条件查询出查询出符合条件的 KeyValue 列表/map,该KeyValue是一个键值对,所以再查询条件中需要指定需要查询的字段。
|
||||
> 注意: KeyValue对象支持第三个扩展字段ext,默认从queryWrapper构建指定select的第三个字段中取值。如queryWrapper.select(name, value, ext)
|
||||
```java
|
||||
List<KeyValue> getKeyValueList(Wrapper queryWrapper);
|
||||
```
|
||||
|
||||
### getViewObject
|
||||
> 该方法通过主键,查询出该主键VO实例,返回一个VO实例。
|
||||
> 通过Entity主键,查询出该主键VO实例,返回一个VO实例。
|
||||
提示:如果该VO通过相应注解绑定了数据字典关联或者数据表关联,那么该方法也将查询出相对应的数据字典信息或者关联数据信息等。
|
||||
```java
|
||||
<VO> VO getViewObject(Serializable id, Class<VO> voClass);
|
||||
```
|
||||
|
||||
### getViewObjectList
|
||||
> 该方法通过查询条件,分页条件,查询出符合该查询条件的当页数据列表,返回一个VO实例列表。
|
||||
> 通过查询条件,分页条件,查询出符合该查询条件的当页数据列表,返回一个VO实例列表。
|
||||
提示:如果该VO通过相应注解绑定了数据字典关联或数据表关联,那么该方法查询出的VO列表中,每一个VO元素也将有对应的数据字典信息或关联表信息等。
|
||||
```java
|
||||
<VO> List<VO> getViewObjectList(Wrapper queryWrapper, Pagination pagination, Class<VO> voClass);
|
||||
```
|
||||
### bindingFieldTo
|
||||
|
||||
### exists 是否存在匹配记录
|
||||
```java
|
||||
FieldBinder<T> bindingFieldTo(List voList);
|
||||
EntityBinder<T> bindingEntityTo(List voList);
|
||||
/**
|
||||
* 是否存在符合条件的记录
|
||||
* @param getterFn entity的getter方法
|
||||
* @param value 需要检查的值
|
||||
* @return true/false
|
||||
*/
|
||||
boolean exists(IGetter<T> getterFn, Object value);
|
||||
|
||||
/**
|
||||
* 是否存在符合条件的记录
|
||||
* @param queryWrapper
|
||||
* @return true/false
|
||||
*/
|
||||
boolean exists(Wrapper queryWrapper);
|
||||
```
|
||||
|
||||
### bindingEntityListTo
|
||||
## 更新相关接口
|
||||
|
||||
### createEntity
|
||||
```java
|
||||
EntityListBinder<T> bindingEntityListTo(List voList);
|
||||
```
|
||||
boolean createEntity(T entity);
|
||||
```
|
||||
> createEntity接口将一个entity添加到数据库中,返回成功与失败。创建成功后,会将该记录的主键自动设置到该entity中,如:
|
||||
|
||||
```java
|
||||
boolean success = demoService.createEntity(demo);
|
||||
log.debug(demo.getId());
|
||||
```
|
||||
|
||||
### updateEntity
|
||||
```java
|
||||
boolean updateEntity(T entity);
|
||||
boolean updateEntity(T entity, Wrapper updateCriteria);
|
||||
boolean updateEntity(Wrapper updateWrapper);
|
||||
```
|
||||
* updateEntity接口可以根据该实体的主键值来更新整个entity的所有非空字段值到数据库,返回成功与失败,如:
|
||||
```java
|
||||
boolean success = demoService.updateEntity(demo);
|
||||
```
|
||||
* 该接口也可以通过更新条件来执行更新,具体可参考[mybatis-plus的UpdateWrapper文档](https://mybatis.plus/guide/wrapper.html#updatewrapper)。
|
||||
|
||||
### createOrUpdateEntity
|
||||
```java
|
||||
boolean createOrUpdateEntity(T entity);
|
||||
```
|
||||
> 该接口新建或更新一个实体记录到数据库,如果该实体主键有值,则更新,无值,则新建。如主键有值,但数据库中未找到,则报错,如:
|
||||
```java
|
||||
boolean success = demoService.createOrUpdateEntity(demo);
|
||||
log.debug(success);
|
||||
```
|
||||
|
||||
### createOrUpdateEntities
|
||||
```java
|
||||
boolean createOrUpdateEntities(Collection entityList);
|
||||
```
|
||||
> 批量创建或更新实体集合,如:
|
||||
```java
|
||||
boolean success = demoService.createOrUpdateEntities(demoList);
|
||||
log.debug(success);
|
||||
```
|
||||
|
||||
### createEntityAndRelatedEntities
|
||||
* since v2.0.5
|
||||
```java
|
||||
/**
|
||||
* 添加entity 及 其关联子项entities
|
||||
* @param entity 主表entity
|
||||
* @param relatedEntities 从表/关联表entities
|
||||
* @param relatedEntitySetter 设置关联从表绑定id的setter,如Dictionary::setParentId
|
||||
* @return
|
||||
*/
|
||||
boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
```
|
||||
> 该接口将对一个1对多关联数据的设置关联id并批量保存,如:
|
||||
```java
|
||||
boolean success = dictionaryService.createEntityAndRelatedEntities(dictionary, dictionaryList, Dictionary::setParentId);
|
||||
```
|
||||
类似的还有1对多数据的批量更新与删除:
|
||||
~~~java
|
||||
boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
boolean deleteEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
|
||||
~~~
|
||||
|
||||
### deleteEntity
|
||||
```java
|
||||
boolean deleteEntity(Serializable id);
|
||||
```
|
||||
> 该接口通过主键字段对实体进行删除操作,如:
|
||||
```java
|
||||
boolean success = demoService.deleteEntity(demo.getId());
|
||||
log.debug(success);
|
||||
```
|
||||
|
||||
### deletedEntities
|
||||
```java
|
||||
boolean deleteEntities(Wrapper queryWrapper);
|
||||
```
|
||||
> 该接口通过查询条件对符合该查询条件的所有记录进行删除操作
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# 安装
|
||||
|
||||
## diboot-core是什么?
|
||||
|
||||
> diboot-core 是 diboot 2.0版本的核心基础框架,基于Spring Boot、Mybatis-plus封装,实现基础代码的简化及高效。
|
||||
> 使用diboot-core可以更加简单快捷地创建web后端应用,您之前的诸多代码将被极大简化,系统也更容易维护。同时搭档[devtools](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-devtools-example),你需要写的仅有业务逻辑代码。
|
||||
|
||||
* 集成了[mybatis-plus](https://mp.baomidou.com/),以实现单表CRUD无SQL。
|
||||
* 扩展实现了多表关联查询的无SQL方案,只需要一个简单注解@Bind*,就可以实现关联对象(含字段、实体、实体集合等)的数据绑定,且实现方案是将关联查询拆解为单表查询,保障最佳性能。
|
||||
* 支持@BindDict注解简单绑定数据字典,实现value-name转换。
|
||||
* 实现了Entity/DTO自动转换为QueryWrapper(@BindQuery注解绑定字段参数对应的查询条件,无需再手动构建QueryWrapper查询条件)。
|
||||
* 另外还提供其他常用开发场景的最佳实践封装。
|
||||
* 我们还封装了[diboot-core-starter](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example),简化diboot-core的初始化配置(自动配置、自动创建数据字典表)
|
||||
|
||||
## 支持数据库
|
||||
MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
|
||||
|
||||
## diboot-core使用
|
||||
|
||||
使用步骤请参考 [diboot-core README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core)
|
||||
|
||||
参考样例 [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)
|
||||
|
||||
## 相关依赖
|
||||
:::tip
|
||||
以下依赖在引入diboot-core-starter依赖中,可以不再引入。
|
||||
:::
|
||||
* **javax.servlet-api**(javax.servlet:javax.servlet-api:4.0.1)
|
||||
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.2.4.RELEASE)
|
||||
* **mybatis-plus-boot-starter**(com.baomidou:mybatis-plus-boot-starter:3.2.0)
|
||||
* **commons-io**(commons-io:commons-io:2.6)
|
||||
* **commons-lang3**(org.apache.commons:commons-lang3:3.9)
|
||||
* **fastjson**(com.alibaba:fastjson:1.2.60)
|
||||
|
||||
:::tip
|
||||
需要额外添加的jar
|
||||
:::
|
||||
* **数据库驱动包** (如 mysql:mysql-connector-java:8.0.18)
|
||||
|
|
@ -2,68 +2,26 @@
|
|||
|
||||
> diboot-core中的实体Entity是与数据库表对应的映射对象,下文简称实体。
|
||||
|
||||
所有实体统一存放在entity包名下,命名采用将表名转换为首字母大写的驼峰命名法命名,比如**sys_user**的实体名为**SysUser。**
|
||||
所有实体命名约定采用将表名转换为首字母大写的驼峰命名法命名,比如**sys_user**的实体名为**SysUser**
|
||||
|
||||
## BaseEntity
|
||||
|
||||
> BaseEntity是diboot-core提供的基础实体类,提供了我们默认数据表结构的默认字段,比如id、is_deleted、create_time等,默认的方法如toMap等。
|
||||
|
||||
* getFromExt方法
|
||||
|
||||
```java
|
||||
/***
|
||||
* 从extdata JSON中提取扩展属性值
|
||||
* @param extAttrName
|
||||
* @return
|
||||
*/
|
||||
public Object getFromExt(String extAttrName){
|
||||
if(this.extdataMap == null){
|
||||
return null;
|
||||
}
|
||||
return this.extdataMap.get(extAttrName);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
该方法传入一个属性名称,返回该属性值
|
||||
|
||||
* addIntoExt
|
||||
|
||||
```java
|
||||
/***
|
||||
* 添加扩展属性和值到extdata JSON中
|
||||
* @param extAttrName
|
||||
* @param extAttrValue
|
||||
*/
|
||||
public void addIntoExt(String extAttrName, Object extAttrValue){
|
||||
if(extAttrName == null && extAttrValue == null){
|
||||
return;
|
||||
}
|
||||
if(this.extdataMap == null){
|
||||
this.extdataMap = new LinkedHashMap<>();
|
||||
}
|
||||
this.extdataMap.put(extAttrName, extAttrValue);
|
||||
}
|
||||
```
|
||||
|
||||
该方法传入一个属性名与属性值,即可将该值设置到extdata的json字符串中。
|
||||
> BaseEntity是diboot-core提供的基础实体类,提供了我们默认数据表结构的默认字段,比如id、is_deleted、create_time等。
|
||||
|
||||
## 数据校验
|
||||
|
||||
> 我们在数据提交过程中可能会需要后端进行数据格式校验,我们默认是用validation来做后端数据校验,我们可以在实体中需要校验的字段上添加校验相关的注解,如下:
|
||||
> 数据提交过程中一般需要后端进行数据格式校验,默认是用validation来做后端数据校验,字段上校验注解示例如下:
|
||||
|
||||
```java
|
||||
@NotNull(message = "上级ID不能为空,如无请设为0")
|
||||
private Long parentId;
|
||||
```
|
||||
|
||||
## @TableField注解
|
||||
## 数据库表中不存在的列
|
||||
|
||||
> 如果mapper也继承我们core的BaseMapper来处理,那么实体中的所有字段都被认为在相对应的数据表中是存在的,如果某个字段在对应数据表中不存在,则会执行SQL时报错。
|
||||
|
||||
所以,如果某个字段在相对应的数据表中不存在,而我们又需要使用到该字段,那么可以添加@Tablefield注解,并且设置@TableField(exist = false)中的exist参数为false即可,如下:
|
||||
> Entity中的属性默认会自动映射为数据库列,如果某个字段在对应数据表中不存在,需要使用Mybatis-plus的 @TableField(exist = false) 注解告知Mybatis-plus忽略该字段。
|
||||
|
||||
```java
|
||||
@TableField(exist = false)
|
||||
private List<Long> userIdList;
|
||||
private String ignoreMe;
|
||||
```
|
|
@ -2,40 +2,37 @@
|
|||
|
||||
## BeanUtils(Bean)
|
||||
|
||||
* copyProperties 方法
|
||||
* copyProperties 对象拷贝
|
||||
```java
|
||||
//方法定义
|
||||
public static Object copyProperties(Object source, Object target){...}
|
||||
//方法调用示例
|
||||
Object obj = BeanUtils.copyProperties(source, target);
|
||||
```
|
||||
该方法用来复制一个对象的属性到另一个对象,入参为被复制对象(source)、作为返回值的目标对象(target)。
|
||||
该方法用来复制source对象的值到另一个target对象。其在Spring的copyProperties之外扩展支持了@Accept注解指定的非同名字段拷贝,注解定义示例。
|
||||
```java
|
||||
// 指定接收来自源对象的genderLabel属性值
|
||||
@Accept(name = "genderLabel")
|
||||
private String gender;
|
||||
```
|
||||
|
||||
* convert 方法
|
||||
* convert 类型转换
|
||||
```java
|
||||
//方法定义
|
||||
public static <T> T convert(Object source, Class<T> clazz){...}
|
||||
//方法调用示例
|
||||
Organization org = BeanUtils.convert(source, Organization.class);
|
||||
System.out.println(org.getName());
|
||||
//执行结果
|
||||
===> xxx有限公司
|
||||
```
|
||||
该方法用来将一个对象转换为另外的对象实例,入参为被转化对象(source)、目标对象的类类型(clazz)。
|
||||
|
||||
* convertList 方法
|
||||
* convertList 集合中的类型转换
|
||||
```java
|
||||
//方法定义
|
||||
public static <T> List<T> convertList(List sourceList, Class<T> clazz){...}
|
||||
//方法调用示例
|
||||
List<Organization> orgList = BeanUtils.convertList(sourceList, Organization.class);
|
||||
System.out.println(orgList.get(0).getName());
|
||||
//执行结果
|
||||
===> xxx有限公司
|
||||
```
|
||||
该方法用来将对象集合转换为另外的对象集合实例,入参为被转化对象集合(sourceList)、目标对象的类类型(clazz)。
|
||||
|
||||
* bindProperties 方法
|
||||
* bindProperties 绑定map属性到对象
|
||||
```java
|
||||
//方法定义
|
||||
public static void bindProperties(Object model, Map<String, Object> propMap){...}
|
||||
|
@ -44,7 +41,7 @@ BeanUtils.bindProperties(model, propMap);
|
|||
```
|
||||
该方法用来绑定Map中的属性值到Model,入参为被绑定对象(model)、属性值Map(propMap)。
|
||||
|
||||
* getProperty 方法
|
||||
* getProperty 获取属性值
|
||||
```java
|
||||
//方法定义
|
||||
public static Object getProperty(Object obj, String field){...}
|
||||
|
@ -53,7 +50,7 @@ Object obj = BeanUtils.getProperty(obj, field);
|
|||
```
|
||||
该方法用来获取对象的属性值,入参为目标对象(obj)、对象字段名(field)。
|
||||
|
||||
* getStringProperty 方法
|
||||
* getStringProperty 获取属性值并转为String
|
||||
```java
|
||||
//方法定义
|
||||
public static String getStringProperty(Object obj, String field){...}
|
||||
|
@ -62,7 +59,7 @@ String str = BeanUtils.getStringProperty(obj, field);
|
|||
```
|
||||
该方法用来获取对象的属性值并转换为字符串类型,入参为目标对象(obj)、字段名(field)。
|
||||
|
||||
* setProperty 方法
|
||||
* setProperty 设置属性值
|
||||
```java
|
||||
//方法定义
|
||||
public static void setProperty(Object obj, String field, Object value){...}
|
||||
|
@ -71,7 +68,7 @@ BeanUtils.setProperty(obj, field, value);
|
|||
```
|
||||
该方法用来设置对象属性值,入参为目标对象(obj)、字段名(field)、字段值(value)。
|
||||
|
||||
* convertToStringKeyObjectMap 方法
|
||||
* convertToStringKeyObjectMap 转换集合为Key-Object的map,key为指定字段值
|
||||
```java
|
||||
//方法定义
|
||||
public static <T> Map<String, T> convertToStringKeyObjectMap(List<T> allLists, String... fields){...}
|
||||
|
@ -80,7 +77,7 @@ Map map = BeanUtils.convertToStringKeyObjectMap(allLists, fields);
|
|||
```
|
||||
该方法用来将对象集合转化成键值对为String-Object的Map形式,入参为目标对象集合(allLists)、字段名(fields)。
|
||||
|
||||
* convertToStringKeyObjectListMap 方法
|
||||
* convertToStringKeyObjectListMap 转换集合为Key-Object的map,key为指定字段值的String
|
||||
```java
|
||||
//方法定义
|
||||
public static <T> Map<String, List<T>> convertToStringKeyObjectListMap(List<T> allLists, String... fields){...}
|
||||
|
@ -89,7 +86,7 @@ Map map = BeanUtils.convertToStringKeyObjectListMap(allLists, fields);
|
|||
```
|
||||
该方法用来将对象集合转化成键值对为String-List的Map形式,入参为目标对象集合(allLists)、字段名(fields)。
|
||||
|
||||
* buildTree 方法
|
||||
* buildTree 构建树形结构
|
||||
```java
|
||||
//该方法用来构建支持无限层级的树形结构,默认顶层父级节点的parentId=0,入参为对象集合(allNodes)
|
||||
//方法定义
|
||||
|
@ -104,7 +101,7 @@ public static <T> List<T> buildTree(List<T> allNodes, Object rootNodeId){
|
|||
List<Menu> menuTree = BeanUtils.buildTree(allNodes, 1L);
|
||||
```
|
||||
|
||||
* extractDiff 方法
|
||||
* extractDiff 提取对象值变化
|
||||
```java
|
||||
//该方法用来提取两个model的差异值,入参为两个实体对象oldModel、newModel
|
||||
//方法定义
|
||||
|
@ -119,7 +116,7 @@ public static String extractDiff(BaseEntity oldModel, BaseEntity newModel, Set<S
|
|||
String str = BeanUtils.extractDiff(oldModel, newModel, fields);
|
||||
```
|
||||
|
||||
* collectToList 方法
|
||||
* collectToList 收集某字段值到集合
|
||||
```java
|
||||
//该方法用来从集合列表中提取指定属性值到新的集合,入参为对象集合(objectList)、IGetter对象(getterFn)
|
||||
//方法定义
|
||||
|
@ -134,7 +131,7 @@ public static <E> List collectToList(List<E> objectList, String getterPropName){
|
|||
List list = BeanUtils.collectToList(objectList, getterPropName);
|
||||
```
|
||||
|
||||
* collectIdToList 方法
|
||||
* collectIdToList 收集id字段值到集合
|
||||
```java
|
||||
//方法定义
|
||||
public static <E> List collectIdToList(List<E> objectList){...}
|
||||
|
@ -143,22 +140,7 @@ List list = BeanUtils.collectIdToList(objectList);
|
|||
```
|
||||
该方法用来从集合列表中提取Id主键值到新的集合,入参为对象集合(objectList)。
|
||||
|
||||
* bindPropValueOfList 方法
|
||||
```java
|
||||
//该方法用来绑定Map中的属性值到集合,入参为ISetter对象、目标集合(fromList)、IGetter对象、Map对象(valueMatchMap)
|
||||
//方法定义
|
||||
public static <T1,T2,R,E> void bindPropValueOfList(ISetter<T1,R> setFieldFn, List<E> fromList, IGetter<T2> getFieldFun, Map valueMatchMap){...}
|
||||
//方法调用示例
|
||||
BeanUtils.bindPropValueOfList(setFieldFn, fromList, getFieldFun, valueMatchMap);
|
||||
|
||||
//该方法用来绑定Map中的属性值到集合,入参为字段名(setterFieldName)、目标集合(fromList)、字段名(getterFieldName)、Map对象(valueMatchMap)
|
||||
//方法定义
|
||||
public static <E> void bindPropValueOfList(String setterFieldName, List<E> fromList, String getterFieldName, Map valueMatchMap){...}
|
||||
//方法调用示例
|
||||
BeanUtils.bindPropValueOfList(setterFieldName, fromList, getterFieldName, valueMatchMap);
|
||||
```
|
||||
|
||||
* convertToFieldName 方法
|
||||
* convertToFieldName 获取属性名
|
||||
```java
|
||||
//该方法用来转换方法引用为属性名,入参为IGetter对象。
|
||||
//方法定义
|
||||
|
@ -173,26 +155,30 @@ public static <T,R> String convertToFieldName(ISetter<T,R> fn){...}
|
|||
String str = BeanUtils.convertToFieldName(fn);
|
||||
```
|
||||
|
||||
* extractAllFields 方法
|
||||
* extract*Fields 提取Field(包含父类中)
|
||||
```java
|
||||
//方法定义
|
||||
public static List<Field> extractAllFields(Class clazz){...}
|
||||
//方法调用示例
|
||||
// 提取所有Field
|
||||
List<Field> list = BeanUtils.extractAllFields(Organization.class);
|
||||
// 提取指定注解的Field
|
||||
List<Field> list = BeanUtils.extractFields(Organization.class, MyAnnotation.class);
|
||||
// 提取指定属性名的Field
|
||||
Field field = extractField(Organization.class, "name");
|
||||
```
|
||||
|
||||
* cloneBean 克隆对象
|
||||
```java
|
||||
//方法调用
|
||||
User user2 = BeanUtils.cloneBean(user1);
|
||||
```
|
||||
|
||||
* distinctByKey 基于某字段值去重
|
||||
```java
|
||||
//方法调用
|
||||
List<Dictionary> list = BeanUtils.distinctByKey(dictionaryList, Dictionary::getId);
|
||||
```
|
||||
该方法用来获取类的所有属性(包含父类),入参为类类型(clazz)。
|
||||
|
||||
## ContextHelper(Spring上下文)
|
||||
|
||||
* setApplicationContext 方法
|
||||
```java
|
||||
//方法定义
|
||||
public void setApplicationContext(ApplicationContext applicationContext){...}
|
||||
//方法调用示例
|
||||
ContextHelper.setApplicationContext(applicationContext);
|
||||
```
|
||||
该方法用来设置ApplicationContext上下文,入参为上下文对象(applicationContext)。
|
||||
|
||||
* getApplicationContext 方法
|
||||
```java
|
||||
//方法定义
|
||||
|
@ -258,7 +244,7 @@ public static final String FORMAT_TIME_HHmm = "HH:mm"; //例:12:30
|
|||
public static final String FORMAT_TIME_HHmmss = "HH:mm:ss"; //例:12:30:30 表示 12点30分30秒
|
||||
public static final String FORMAT_DATETIME_Y4MDHM = "yyyy-MM-dd HH:mm"; //例:2019-08-16 12:30 表示 2019年8月16日 12点30分
|
||||
public static final String FORMAT_DATETIME_Y4MDHMS = "yyyy-MM-dd HH:mm:ss";//例:2019-08-16 12:30:30 表示 2019年8月16日 12点30分30秒
|
||||
protected static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
|
||||
public static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
|
||||
```
|
||||
格式常量表示某种日期时间格式的一个字符串类型的常量,D类中已经内置了如下列表所示的格式常量,
|
||||
可以通过类似D.FORMAT_DATE_y2M这样的调用来获取这些常量的内容。
|
||||
|
@ -269,7 +255,7 @@ protected static final String[] WEEK = new String[]{"星期日", "星期一", "
|
|||
public static String now(String format){...}
|
||||
//方法调用示例
|
||||
String nowDateStr = D.now("yyyy-MM-dd");
|
||||
System.out.println(nowDateStr);
|
||||
log.debug(nowDateStr);
|
||||
//执行结果
|
||||
===> 2019-08-20
|
||||
```
|
||||
|
@ -281,7 +267,7 @@ System.out.println(nowDateStr);
|
|||
public static String toTimestamp(Date date){...}
|
||||
//方法调用示例
|
||||
String dateStr = D.toTimestamp(date);
|
||||
System.out.println(dateStr);
|
||||
log.debug(dateStr);
|
||||
//执行结果
|
||||
===> 190820094202
|
||||
```
|
||||
|
@ -293,7 +279,7 @@ System.out.println(dateStr);
|
|||
public static String getMonth(){...}
|
||||
//方法调用示例
|
||||
String monthStr = D.getMonth();
|
||||
System.out.println(monthStr);
|
||||
log.debug(monthStr);
|
||||
//执行结果
|
||||
===> 1908
|
||||
```
|
||||
|
@ -305,7 +291,7 @@ System.out.println(monthStr);
|
|||
public static String today(){...}
|
||||
//方法调用示例
|
||||
String todayStr = D.today();
|
||||
System.out.println(todayStr);
|
||||
log.debug(todayStr);
|
||||
//执行结果
|
||||
===> 20190820
|
||||
```
|
||||
|
@ -317,7 +303,7 @@ System.out.println(todayStr);
|
|||
public static Date convert2FormatDate(String datetime, String fmt){...}
|
||||
//方法调用示例
|
||||
Date date = D.convert2FormatDate("2019-08-20", "yyyy-MM-dd");
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 00:00:00 CST 2019
|
||||
```
|
||||
|
@ -329,7 +315,7 @@ System.out.println(date);
|
|||
public static String convert2FormatString(Date date, String fmt){...}
|
||||
//方法调用示例
|
||||
String dateStr = D.convert2FormatString(new Date(), "yyyy-MM-dd");
|
||||
System.out.println(dateStr);
|
||||
log.debug(dateStr);
|
||||
//执行结果
|
||||
===> 2019-08-20
|
||||
```
|
||||
|
@ -341,7 +327,7 @@ System.out.println(dateStr);
|
|||
public static String getDate(Date date, int... daysOffset){...}
|
||||
//方法调用示例
|
||||
String dateStr = D.getDate(new Date(), 0);
|
||||
System.out.println(dateStr);
|
||||
log.debug(dateStr);
|
||||
//执行结果
|
||||
===> 2019-08-20
|
||||
```
|
||||
|
@ -353,7 +339,7 @@ System.out.println(dateStr);
|
|||
public static String getDateTime(Date date, int... daysOffset){...}
|
||||
//方法调用示例
|
||||
String dateTimeStr = D.getDateTime(new Date(), 0);
|
||||
System.out.println(dateTimeStr);
|
||||
log.debug(dateTimeStr);
|
||||
//执行结果
|
||||
===> 2019-08-20 09:53
|
||||
```
|
||||
|
@ -365,7 +351,7 @@ System.out.println(dateTimeStr);
|
|||
public static boolean isWorkingTime(){...}
|
||||
//方法调用示例
|
||||
boolean isWorkingTime = D.isWorkingTime();
|
||||
System.out.println(isWorkingTime);
|
||||
log.debug(isWorkingTime);
|
||||
//执行结果
|
||||
===> true
|
||||
```
|
||||
|
@ -377,7 +363,7 @@ System.out.println(isWorkingTime);
|
|||
public static String getAmPm(){...}
|
||||
//方法调用示例
|
||||
String timeStr = D.getAmPm();
|
||||
System.out.println(timeStr);
|
||||
log.debug(timeStr);
|
||||
//执行结果
|
||||
===> 早上
|
||||
```
|
||||
|
@ -389,7 +375,7 @@ System.out.println(timeStr);
|
|||
public static String getYearMonth(){...}
|
||||
//方法调用示例
|
||||
String yearMonthStr = D.getYearMonth();
|
||||
System.out.println(yearMonthStr);
|
||||
log.debug(yearMonthStr);
|
||||
//执行结果
|
||||
===> 1908
|
||||
```
|
||||
|
@ -401,7 +387,7 @@ System.out.println(yearMonthStr);
|
|||
public static String getYearMonthDay(){...}
|
||||
//方法调用示例
|
||||
String yearMonthDayStr = D.getYearMonthDay();
|
||||
System.out.println(yearMonthDayStr);
|
||||
log.debug(yearMonthDayStr);
|
||||
//执行结果
|
||||
===> 190820
|
||||
```
|
||||
|
@ -413,7 +399,7 @@ System.out.println(yearMonthDayStr);
|
|||
public static int getDay(){...}
|
||||
//方法调用示例
|
||||
int day = D.getDay();
|
||||
System.out.println(day);
|
||||
log.debug(day);
|
||||
//执行结果
|
||||
===> 20
|
||||
```
|
||||
|
@ -425,7 +411,7 @@ System.out.println(day);
|
|||
public static String getWeek(Date date){...}
|
||||
//方法调用示例
|
||||
String week = D.getWeek(new Date());
|
||||
System.out.println(week);
|
||||
log.debug(week);
|
||||
//执行结果
|
||||
===> 星期三
|
||||
```
|
||||
|
@ -437,7 +423,7 @@ System.out.println(week);
|
|||
public static Date timeMillis2Date(Long timeMillis){...}
|
||||
//方法调用示例
|
||||
Date date = D.timeMillis2Date(System.currentTimeMillis());
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 10:06:12 CST 2019
|
||||
```
|
||||
|
@ -449,7 +435,7 @@ System.out.println(date);
|
|||
public static Date datetimeString2Date(String value){...}
|
||||
//方法调用示例
|
||||
Date date = D.datetimeString2Date("2019-08-20 10:11:20");
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 10:11:20 CST 2019
|
||||
```
|
||||
|
@ -461,7 +447,7 @@ System.out.println(date);
|
|||
public static Date convert2Date(String date){...}
|
||||
//方法调用示例
|
||||
Date date = D.convert2Date("2019-08-20");
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 00:00:00 CST 2019
|
||||
```
|
||||
|
@ -473,7 +459,7 @@ System.out.println(date);
|
|||
public static Date convert2DateTime(String dateTime, String... dateFormat){...}
|
||||
//方法调用示例
|
||||
Date date = D.convert2DateTime("2019-08-20 10:14:20", "yyyy-MM-dd HH:mm:ss");
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 10:14:20 CST 2019
|
||||
```
|
||||
|
@ -485,7 +471,7 @@ System.out.println(date);
|
|||
public static Date fuzzyConvert(String dateString){...}
|
||||
//方法调用示例
|
||||
Date date = D.fuzzyConvert("2019-08-20 10:14:20");
|
||||
System.out.println(date);
|
||||
log.debug(date);
|
||||
//执行结果
|
||||
===> Tue Aug 20 10:14:20 CST 2019
|
||||
```
|
||||
|
@ -499,7 +485,7 @@ System.out.println(date);
|
|||
public static String encrypt(String input, String... key){...}
|
||||
//方法调用示例
|
||||
String encryptStr = Encryptor.encrypt("123456", "admin");
|
||||
System.out.println(encryptStr);
|
||||
log.debug(encryptStr);
|
||||
//执行结果
|
||||
===> ZVmTuAFJIjD5PLwkURuvRw==
|
||||
```
|
||||
|
@ -511,13 +497,13 @@ System.out.println(encryptStr);
|
|||
public static String decrypt(String input, String... key){...}
|
||||
//方法调用示例
|
||||
String decryptStr = Encryptor.decrypt("ZVmTuAFJIjD5PLwkURuvRw==", "admin");
|
||||
System.out.println(decryptStr);
|
||||
log.debug(decryptStr);
|
||||
//执行结果
|
||||
===> 123456
|
||||
```
|
||||
该方法用来解密字符串,入参为需解密字符串(input)、解密秘钥(key)。
|
||||
|
||||
## JSON
|
||||
## JSON (扩展自fastjson)
|
||||
|
||||
* stringify 方法
|
||||
```java
|
||||
|
@ -525,7 +511,7 @@ System.out.println(decryptStr);
|
|||
public static String stringify(Object object){...}
|
||||
//方法调用示例
|
||||
String str = JSON.stringify(new Dictionary());
|
||||
System.out.println(str);
|
||||
log.debug(str);
|
||||
//执行结果
|
||||
===> {"editable":false,"parentId":0,"sortId":99,"system":true}
|
||||
```
|
||||
|
@ -537,7 +523,7 @@ System.out.println(str);
|
|||
public static Map toMap(String jsonStr){...}
|
||||
//方法调用示例
|
||||
Map map = JSON.toMap("{"editable":false,"parentId":0,"sortId":99,"system":true}");
|
||||
System.out.println(map);
|
||||
log.debug(map);
|
||||
//执行结果
|
||||
===> {"system":true,"editable":false,"sortId":99,"parentId":0}
|
||||
```
|
||||
|
@ -549,7 +535,7 @@ System.out.println(map);
|
|||
public static LinkedHashMap toLinkedHashMap(String jsonStr){...}
|
||||
//方法调用示例
|
||||
LinkedHashMap linkedMap = JSON.toLinkedHashMap("{"editable":false,"parentId":0,"sortId":99,"system":true}");
|
||||
System.out.println(linkedMap);
|
||||
log.debug(linkedMap);
|
||||
//执行结果
|
||||
===> {editable=false, parentId=0, sortId=99, system=true}
|
||||
```
|
||||
|
@ -575,7 +561,7 @@ System.out.pringtln(dictionary.getSystem());
|
|||
public static String get(String key, String... propertiesFileName){...}
|
||||
//方法调用示例
|
||||
String portStr = PropertiesUtils.get("database.port","system.properties");
|
||||
System.out.println(portStr);
|
||||
log.debug(portStr);
|
||||
//执行结果
|
||||
===> 3306
|
||||
```
|
||||
|
@ -587,7 +573,7 @@ System.out.println(portStr);
|
|||
public static Integer getInteger(String key, String... propertiesFileName){...}
|
||||
//方法调用示例
|
||||
Integer portInt = PropertiesUtils.getInteger("database.port","system.properties");
|
||||
System.out.println(portInt);
|
||||
log.debug(portInt);
|
||||
//执行结果
|
||||
===> 3306
|
||||
```
|
||||
|
@ -599,7 +585,7 @@ System.out.println(portInt);
|
|||
public static boolean getBoolean(String key, String... propertiesFileName){...}
|
||||
//方法调用示例
|
||||
boolean isOpen = PropertiesUtils.getBoolean("database.open","system.properties");
|
||||
System.out.println(isOpen);
|
||||
log.debug(isOpen);
|
||||
//执行结果
|
||||
===> true
|
||||
```
|
||||
|
@ -614,7 +600,7 @@ System.out.println(isOpen);
|
|||
public static String cut(String input){...}
|
||||
//方法调用示例
|
||||
String cutStr = S.cut("ABCDE");
|
||||
System.out.println(cutStr);
|
||||
log.debug(cutStr);
|
||||
//执行结果
|
||||
===> ABCDE
|
||||
|
||||
|
@ -623,7 +609,7 @@ System.out.println(cutStr);
|
|||
public static String cut(String input, int cutLength){...}
|
||||
//方法调用示例
|
||||
String cutStr = S.cut("ABCDE", 1);
|
||||
System.out.println(cutStr);
|
||||
log.debug(cutStr);
|
||||
//执行结果
|
||||
===> A
|
||||
```
|
||||
|
@ -635,7 +621,7 @@ System.out.println(cutStr);
|
|||
public static String join(List<String> stringList){...}
|
||||
//方法调用示例
|
||||
String joinStr = S.join(new ArrayList<String>(){{add("A");add("B");add("C");}});
|
||||
System.out.println(joinStr);
|
||||
log.debug(joinStr);
|
||||
//执行结果
|
||||
===> A,B,C
|
||||
|
||||
|
@ -644,7 +630,7 @@ System.out.println(joinStr);
|
|||
public static String join(String[] stringArray){...}
|
||||
//方法调用示例
|
||||
String joinStr = S.join(new String[]{"A","B","C"});
|
||||
System.out.println(joinStr);
|
||||
log.debug(joinStr);
|
||||
//执行结果
|
||||
===> A,B,C
|
||||
```
|
||||
|
@ -655,7 +641,7 @@ System.out.println(joinStr);
|
|||
public static String[] split(String joinedStr){...}
|
||||
//方法调用示例
|
||||
String[] strArray = S.split("A,B,C");
|
||||
System.out.println(strArray[0]);
|
||||
log.debug(strArray[0]);
|
||||
//执行结果
|
||||
===> A
|
||||
```
|
||||
|
@ -676,7 +662,7 @@ String[] strArray = S.toStringArray(stringList);
|
|||
public static String toSnakeCase(String camelCaseStr){...}
|
||||
//方法调用示例
|
||||
String userName = S.toSnakeCase("userName");
|
||||
System.out.println(userName);
|
||||
log.debug(userName);
|
||||
//执行结果
|
||||
===> user_name
|
||||
```
|
||||
|
@ -688,7 +674,7 @@ System.out.println(userName);
|
|||
public static String toLowerCaseCamel(String snakeCaseStr){...}
|
||||
//方法调用示例
|
||||
String userName = S.toLowerCaseCamel("user_name");
|
||||
System.out.println(userName);
|
||||
log.debug(userName);
|
||||
//执行结果
|
||||
===> userName
|
||||
```
|
||||
|
@ -718,7 +704,7 @@ Integer intValue = S.toInt("1");
|
|||
public static boolean toBoolean(String strValue){...}
|
||||
//方法调用示例
|
||||
boolean isTrue = S.toBoolean("1");
|
||||
System.out.println(isTrue);
|
||||
log.debug(isTrue);
|
||||
//执行结果
|
||||
===> true
|
||||
```
|
||||
|
@ -730,7 +716,7 @@ System.out.println(isTrue);
|
|||
public static String removeDuplicateBlank(String input){...}
|
||||
//方法调用示例
|
||||
String str = S.removeDuplicateBlank("A B");
|
||||
System.out.println(str);
|
||||
log.debug(str);
|
||||
//执行结果
|
||||
===> A B
|
||||
```
|
||||
|
@ -742,7 +728,7 @@ System.out.println(str);
|
|||
public static String newUuid(){...}
|
||||
//方法调用示例
|
||||
String uuid = S.newUuid();
|
||||
System.out.println(uuid);
|
||||
log.debug(uuid);
|
||||
//执行结果
|
||||
===> c8b735798cfe4e0ba897a460d6107b8a
|
||||
```
|
||||
|
@ -754,7 +740,7 @@ System.out.println(uuid);
|
|||
public static String newRandomNum(int length){...}
|
||||
//方法调用示例
|
||||
String randomNum = S.newRandomNum(6);
|
||||
System.out.println(randomNum);
|
||||
log.debug(randomNum);
|
||||
//执行结果
|
||||
===> 513987
|
||||
```
|
||||
|
@ -766,7 +752,7 @@ System.out.println(randomNum);
|
|||
public static String uncapFirst(String input){...}
|
||||
//方法调用示例
|
||||
String str = S.uncapFirst("ABC");
|
||||
System.out.println(str);
|
||||
log.debug(str);
|
||||
//执行结果
|
||||
===> aBC
|
||||
```
|
||||
|
@ -778,50 +764,12 @@ System.out.println(str);
|
|||
public static String capFirst(String input){...}
|
||||
//方法调用示例
|
||||
String str = S.capFirst("abc");
|
||||
System.out.println(str);
|
||||
log.debug(str);
|
||||
//执行结果
|
||||
===> Abc
|
||||
```
|
||||
该方法用来将首字母转为大写,入参为字符串(input)。
|
||||
|
||||
## SqlExecutor(SQL执行)
|
||||
|
||||
* executeQuery 方法
|
||||
```java
|
||||
//方法定义
|
||||
public static <E> List<Map<String,E>> executeQuery(String sql, List<E> params){...}
|
||||
//方法调用示例
|
||||
List list = SqlExecutor.executeQuery(sql, params);
|
||||
```
|
||||
该方法用来执行Select语句,入参为SQL语句(sql)、查询参数(params)。
|
||||
|
||||
* executeQueryAndMergeOneToOneResult 方法
|
||||
```java
|
||||
//方法定义
|
||||
public static <E> Map<String, Object> executeQueryAndMergeOneToOneResult(String sql, List<E> params, String keyName, String valueName){...}
|
||||
//方法调用示例
|
||||
Map map = SqlExecutor.executeQueryAndMergeOneToOneResult(sql, params, keyName, valueName);
|
||||
```
|
||||
该方法用来执行一对一关联查询和合并结果并将结果Map的key转成String类型,入参为SQL语句(sql)、查询参数(params)、字段名(keyName)、字段名(valueName)。
|
||||
|
||||
* executeQueryAndMergeOneToManyResult 方法
|
||||
```java
|
||||
//方法定义
|
||||
public static <E> Map<String, List> executeQueryAndMergeOneToManyResult(String sql, List<E> params, String keyName, String valueName){...}
|
||||
//方法调用示例
|
||||
Map map = SqlExecutor.executeQueryAndMergeOneToManyResult(sql, params, keyName, valueName);
|
||||
```
|
||||
该方法用来执行查询和合并结果并将结果Map的key转成String类型,入参为SQL语句(sql)、查询参数(params)、字段名(keyName)、字段名(valueName)。
|
||||
|
||||
* executeUpdate 方法
|
||||
```java
|
||||
//方法定义
|
||||
public static boolean executeUpdate(String sql, List params){...}
|
||||
//方法调用示例
|
||||
boolean success = SqlExecutor.executeUpdate(sql, params);
|
||||
```
|
||||
该方法用来执行更新操作,入参为SQL语句(sql)、更新参数(params)。
|
||||
|
||||
## V(校验)
|
||||
|
||||
* isEmpty 方法
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# 异常处理
|
||||
## DefaultExceptionHandler
|
||||
|
||||
* 默认异常处理,继承自该类并添加@ControllerAdvice注解即可自动支持:
|
||||
* 兼容JSON请求和Html页面请求的Exception异常处理
|
||||
* Entity绑定校验的统一处理(BindException.class, MethodArgumentNotValidException.class)
|
||||
|
||||
* 示例代码
|
||||
~~~java
|
||||
@ControllerAdvice
|
||||
public class GeneralExceptionHandler extends DefaultExceptionHandler{
|
||||
|
||||
}
|
||||
~~~
|
|
@ -1,6 +1,7 @@
|
|||
# 无SQL关联绑定
|
||||
|
||||
> diboot-core支持通过注解实现数据字典关联、从表Entity(1-1)及Entity集合(1-n)关联,从表字段(1-1)及字段集合(1-n)关联等实现。
|
||||
> 通过重构查询方式 (拆解关联查询,程序中Join) ,简化开发、提高性能
|
||||
|
||||
## 如何开始
|
||||
> 自动绑定关联的前提是具备表相对应的Entity实体类、service接口、mapper等,您可以创建完之后建一个主表的VO类来尝试该功能,如:
|
||||
|
@ -118,6 +119,7 @@ private String orgName;
|
|||
```
|
||||
|
||||
### 绑定从表字段列表
|
||||
* since v2.1
|
||||
> 绑定实体列表使用**@BindFieldList**注解进行处理,将得到关联表对应的实体列表。
|
||||
* @BindFieldList注解参数同@BindField。
|
||||
* 主表1-n直接关联从表字段集合,注解示例如下:
|
|
@ -0,0 +1,93 @@
|
|||
# 无SQL跨表查询
|
||||
|
||||
> 用户可自定义相关实体的查询DTO类,添加相应的BindQuery注解,diboot将自动构建QueryWrapper并查询。
|
||||
> 对于字段条件跨表的查询将自动按需构建LEFT JOIN(无该字段条件时不JOIN),让无需手写SQL覆盖大多数场景。
|
||||
|
||||
## 使用方式
|
||||
### 1. Entity/DTO中定义查询方式
|
||||
示例代码:
|
||||
~~~java
|
||||
public class UserDTO {
|
||||
// 无@BindQuery注解默认会映射为=条件;主表的相等条件无需加注解
|
||||
private Long gender;
|
||||
|
||||
// 有注解,映射为注解指定条件
|
||||
@BindQuery(comparison = Comparison.LIKE)
|
||||
private String realname;
|
||||
|
||||
// join其他表(跨表查询字段)
|
||||
@BindQuery(comparison = Comparison.STARTSWITH, entity=Organization.class, field="name", condition="this.org_id=id")
|
||||
private String orgName;
|
||||
}
|
||||
~~~
|
||||
### 2. 调用QueryBuilder自动构建QueryWrapper
|
||||
> 构建方式有:
|
||||
##### 方式1. controller中调用super.buildQueryWrapper(entityOrDto) 进行构建
|
||||
~~~java
|
||||
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto);
|
||||
~~~
|
||||
> 该方式基于url非空参数取值构建:
|
||||
> url参数示例: /list?gender=M&realname=张
|
||||
> 将构建为 queryWrapper.eq("gender", "M").like("realname", "张")
|
||||
|
||||
##### 方式2. 直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行构建
|
||||
~~~java
|
||||
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto);
|
||||
~~~
|
||||
> 该方式基于dto对象非空值字段构建
|
||||
|
||||
##### 方式3. 明确构建为QueryBuilder.toDynamicJoinQueryWrapper 进行构建
|
||||
~~~java
|
||||
QueryBuilder.toDynamicJoinQueryWrapper(dto).xxx
|
||||
~~~
|
||||
> 该方式支持链式追加查询调用
|
||||
|
||||
|
||||
### 3. 支持动态Join的跨表查询与结果绑定
|
||||
> 动态查询的调用方式有以下两种:
|
||||
##### 方式1. 通过调用BaseService.getEntityList接口
|
||||
~~~java
|
||||
// 调用join关联查询绑定
|
||||
List<Entity> list = xxService.getEntityList(queryWrapper);
|
||||
~~~
|
||||
#### 方式2. 通过Binder调用joinQueryList查询
|
||||
~~~java
|
||||
// 调用join关联查询绑定
|
||||
List<Entity> list = Binder.joinQueryList(queryWrapper, Department.class);
|
||||
~~~
|
||||
##### 方式3. 通过DynamicJoinQueryWrapper链式调用查询
|
||||
~~~java
|
||||
QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(Department.class);
|
||||
~~~
|
||||
|
||||
|
||||
**绑定调用将自动按需(有从表的查询字段时才Join)构建类似如下动态SQL并绑定结果:**
|
||||
|
||||
* 无跨表字段的查询:
|
||||
> SELECT self.* FROM user self WHERE self.gender=? AND self.is_deleted=0
|
||||
|
||||
* 有跨表字段的查询:
|
||||
> SELECT self.* FROM user self
|
||||
LEFT OUTER JOIN organization r1 ON self.org_id=r1.id AND r1.is_deleted=0
|
||||
WHERE self.gender=? AND (r1.name LIKE ?) AND self.is_deleted=0
|
||||
|
||||
基于最佳性能最少Join的原则,无SQL跨表查询仅查询主表数据。如果需要其他关联绑定对象,可调用Binder实现。
|
||||
|
||||
### 4. 数据权限控制
|
||||
> 某些场景下搜索查询需要绑定一些强制条件,用于数据权限控制,如只能查询本部门的数据。
|
||||
##### 1. 在需要控制的字段上添加@DataAccessCheckpoint注解,指定CheckpointType。
|
||||
示例代码:
|
||||
~~~java
|
||||
// 数据权限检查点
|
||||
@DataAccessCheckpoint(type = CheckpointType.ORG)
|
||||
private Long orgId;
|
||||
~~~
|
||||
##### 2. 实现DataAccessCheckInterface接口,返回对应CheckpointType的合法ID集合
|
||||
~~~java
|
||||
public class DataAccessCheckImpl implements DataAccessCheckInterface {
|
||||
@Override
|
||||
public List<Long> getAccessibleIds(CheckpointType type) {
|
||||
// 返回对应检查点的合法ID
|
||||
}
|
||||
~~~
|
||||
通过QueryBuilder构建时,将自动追加IN(合法ID)条件。具体可参考: diboot-IAM组件。
|
|
@ -1,61 +0,0 @@
|
|||
# 查询条件DTO
|
||||
|
||||
> 用户可自定义相关实体的DTO类,并通过注解,自动转换为QueryWrapper并进行查询。
|
||||
|
||||
#### 1. Entity/DTO中声明映射查询条件
|
||||
示例代码:
|
||||
~~~java
|
||||
public class UserDTO {
|
||||
// 无@BindQuery注解默认会映射为=条件
|
||||
private Long gender;
|
||||
|
||||
// 有注解,映射为注解指定条件
|
||||
@BindQuery(comparison = Comparison.LIKE)
|
||||
private String realname;
|
||||
|
||||
// join其他表(跨表查询字段)
|
||||
@BindQuery(comparison = Comparison.STARTSWITH, entity=Organization.class, field="name", condition="this.org_id=id")
|
||||
private String orgName;
|
||||
}
|
||||
~~~
|
||||
#### 2. 调用QueryBuilder.toQueryWrapper(entityOrDto)进行转换
|
||||
~~~java
|
||||
/**
|
||||
* url参数示例: /list?gender=M&realname=张
|
||||
* 将映射为 queryWrapper.eq("gender", "M").like("realname", "张")
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public JsonResult getVOList(UserDto userDto) throws Exception{
|
||||
//调用super.buildQueryWrapper(entityOrDto) 进行转换
|
||||
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto);
|
||||
// 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 转换
|
||||
//QueryWrapper<User> queryWrapper = QueryBuilder.buildQueryWrapper(userDto);
|
||||
|
||||
//... 查询list
|
||||
return JsonResult.OK(list);
|
||||
}
|
||||
~~~
|
||||
|
||||
#### 3. 支持动态Join的关联查询与结果绑定
|
||||
> 动态查询的调用方式有以下两种:
|
||||
##### 方式1. 通过QueryBuilder链式调用
|
||||
~~~java
|
||||
QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(Department.class);
|
||||
~~~
|
||||
##### 方式2. 通过QueryBuilder构建QueryWrapper,再调用Binder或JoinsBinder
|
||||
~~~java
|
||||
// 构建QueryWrapper
|
||||
QueryWrapper<DTO> queryWrapper = QueryBuilder.toQueryWrapper(dto);
|
||||
// 调用join关联查询绑定
|
||||
List<Entity> list = Binder.joinQueryList(queryWrapper, Department.class);
|
||||
~~~
|
||||
|
||||
绑定调用将自动按需(有表的查询字段时才Join)构建类似如下动态SQL并绑定结果:
|
||||
|
||||
无跨表字段的查询:
|
||||
> SELECT self.* FROM user self WHERE self.gender=? AND self.is_deleted=0
|
||||
|
||||
有跨表字段的查询:
|
||||
> SELECT self.* FROM user self
|
||||
LEFT OUTER JOIN organization r1 ON self.org_id=r1.id
|
||||
WHERE self.gender=? AND (r1.name LIKE ?) AND self.is_deleted=0
|
|
@ -0,0 +1,48 @@
|
|||
# 简介
|
||||
|
||||
## diboot-core 高效精简内核
|
||||
|
||||
> diboot-core 是 diboot 2.x版本的核心基础框架,基于Spring Boot、Mybatis-plus封装,实现基础代码的简化及高效。
|
||||
> 使用diboot-core可以更加简单快捷地创建web应用,您之前的诸多代码将被极大简化,系统也更容易维护。
|
||||
> 同时搭档[diboot-devtools](../diboot-devtools/介绍.md),让您彻底摆脱CRUD。
|
||||
|
||||
高效精简内核,重构查询方式,简化开发、提高性能,主要实现:
|
||||
1. 单表CRUD无SQL
|
||||
> 基于Mybatis-Plus实现(Mybatis-Plus具备通用Mapper方案和灵活的查询构造器)
|
||||
2. 关联查询绑定无SQL(注解自动绑定)
|
||||
> 扩展实现了多表关联查询的无SQL方案,只需要一个简单注解@Bind*,就可以实现关联对象(含字段、字段集合、实体、实体集合等)的数据绑定,且实现方案是将关联查询拆解为单表查询,保障最佳性能。
|
||||
3. 数据字典无SQL(注解自动绑定)
|
||||
> 通过@BindDict注解实现数据字典(枚举)的存储值value与显示值name的转换。
|
||||
4. 跨表Join查询无SQL(QueryWrapper自动构建与查询)
|
||||
> @BindQuery注解绑定字段参数对应的查询条件及关联表,自动将请求参数绑定转换为QueryWrapper,并动态执行单表或Join联表查询。
|
||||
5. BaseService扩展增强,支持常规的单表及关联开发场景接口
|
||||
> createEntityAndRelatedEntities、getValuesOfField、exists、getKeyValueList、getViewObject*等接口
|
||||
6. 其他常用Service接口、工具类的最佳实践封装
|
||||
> 字符串处理、常用校验、BeanUtils、DateUtils等
|
||||
7. 提供[diboot-core-starter](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example),简化diboot-core的初始化配置(自动配置、自动创建数据字典表)
|
||||
|
||||
## 支持数据库
|
||||
MySQL、MariaDB、PostgreSQL、ORACLE、SQLServer
|
||||
|
||||
## diboot-core 使用
|
||||
|
||||
使用步骤请参考 [diboot-core README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core)
|
||||
|
||||
参考样例 [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)
|
||||
|
||||
## 相关依赖
|
||||
:::tip
|
||||
以下依赖在引入diboot-core-starter依赖中,可以不再引入。
|
||||
:::
|
||||
* **javax.servlet-api**(javax.servlet:javax.servlet-api:4.x)
|
||||
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.x.RELEASE)
|
||||
* **mybatis-plus-boot-starter**(com.baomidou:mybatis-plus-boot-starter:3.x)
|
||||
* **commons-io**(commons-io:commons-io:2.6)
|
||||
* **commons-lang3**(org.apache.commons:commons-lang3:3.9)
|
||||
* **fastjson**(com.alibaba:fastjson:1.2.x)
|
||||
|
||||
:::tip
|
||||
需要额外添加的jar
|
||||
:::
|
||||
* **数据库驱动包** (如 mysql:mysql-connector-java:8.0.18)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# 设计理念
|
||||
|
||||
## diboot 的诞生背景
|
||||
|
||||
> 众多开发团队现状: 效率低、质量差、可维护性差,我们也常听到开发者抱怨每天CRUD、工作就是搬砖。
|
||||
|
||||
> 很多团队雇不起好的架构师,即使有也大概率不会给他机会去做打地基的事情。
|
||||
|
||||
> diboot正在尝试做些改变 :
|
||||
|
||||
## diboot 的设计理念
|
||||
|
||||
* Web系统开发需要一个普适的基础框架,把复杂的问题简单化,最好还能做到更佳性能,规避常见的坑
|
||||
* 程序员很难被替代,但程序员应该聚焦于数据结构设计、业务实现、难点解决,重复CRUD没啥长进
|
||||
* CRUD类重复性的工作是可以被自动化甚至被省掉的,包括单表及常规的关联场景相关场景
|
||||
|
||||
## diboot 主要技术栈
|
||||
|
||||
* 后端Java+关系数据库,跟紧Spring Boot
|
||||
* ORM站队Mybatis,通用Mapper框架选择Mybatis-plus
|
||||
* 前后分离,前端选型Vue
|
|
@ -1,40 +1,25 @@
|
|||
# 介绍
|
||||
|
||||
## diboot-devtools是什么?
|
||||
## diboot-devtools 的诞生背景
|
||||
|
||||
> diboot-devtools是一个面向Java开发人员的开发助理,有了她,你可以摆脱CRUD等重复性的Coding,更专注于业务实现,提高开发效率和代码质量。
|
||||
> 众多开发团队现状: 效率低、质量差、可维护性差,我们也常听到开发者抱怨每天CRUD、工作就是搬砖。
|
||||
|
||||
> **diboot-devtools - 将重复有规律的事情自动化**
|
||||
> diboot-devtools 是面向Java开发人员的自动化助理,有了她,你可以彻底摆脱CRUD等重复性的工作,专注于数据结构设计、业务实现,提高软件的质量、效率、可维护性。
|
||||
|
||||
## 我们的优势
|
||||
* 支持常用的五大数据库(MySQL,MariaDB,ORACLE,SQLServer, PostgreSQL)。
|
||||
* 使用简单,只需在项目中引入devtools依赖,添加相关配置信息后,即可随本地项目启动运行。
|
||||
* 基于主流框架(SpringBoot + Mybatis-Plus),打造全新优化内核[diboot-core](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core),保证生成的代码更简洁,质量更高。
|
||||
* 功能强大,实现数据结构变更与代码联动同步,更方便维护数据库表结构及关联关系,一键生成与非覆盖式更新代码。
|
||||
* 通过devtools维护数据结构,标准化了数据结构定义,同时数据结构变动SQL会被自动记录,便于同步更新生产等环境数据库。
|
||||
* 使用灵活,可按需启用更多功能。例如:是否开启引入 `Lombok`、`Swagger`等。
|
||||
* 适用场景广,紧随SpringBoot步伐迭代,微服务、单体应用等开发场景通用。
|
||||
> **diboot-devtools - 将复杂的事情简单化,重复的事情自动化**
|
||||
|
||||
## 我们能帮您
|
||||
## diboot-devtools 的核心特性
|
||||
* 支持多数据库(MySQL、MariaDB、PostgreSQL、ORACLE、SQLServer)
|
||||
* 使用很简单(UI界面操作,引入依赖,配置参数后,即可随SpringBoot本地项目启动运行)
|
||||
* 功能很强大(数据结构变更与代码联动同步,一键生成&非覆盖式更新后端代码,一键生成前端功能代码,自动记录变更SQL、维护索引)
|
||||
* 单表与联表场景完整CRUD功能前后端代码完全自动生成,无需手写代码
|
||||
* SQL与代码很标准(devtools标准化了数据结构定义与代码实现,降低维护成本)
|
||||
* 配置很灵活(可按需配置生成代码路径、是否启用`Lombok`、`Swagger`等)
|
||||
* 代码基于diboot高效基础框架生成,是diboot的最佳实践
|
||||
|
||||
### 解决软件开发过程中的重复性问题
|
||||
1. 将开发人员从繁琐的机械式的工作中解放出来,将精力集中到更多需要业务实现的工作上,促使人们更多地关注需求,做对的事。
|
||||
2. 让开发人员提高开发效率和质量,使软件系统的代码标准统一,降低维护成本。
|
||||
|
||||
### 解决数据结构与代码联动同步问题
|
||||
1. 传统的开发模式是:先通过客户端修改数据结构,然后再回到代码,新建实体和CRUD、调整字段、改SQL等,过程繁琐易出错。
|
||||
而diboot-devtools,提供了增强的数据结构维护功能,除了基础的表结构维护功能外,还支持配置表关联、数据字典关联,同时可以通过查看关联关系的E-R图,对数据模型一目了然。
|
||||
通过devtools所做的数据结构及关联的更改,都能同步生成或更新相对应的Java代码。数据结构与代码同步的自动化,您只需要喝着咖啡点下提交按钮即可。
|
||||
2. 通过devtools维护数据结构,标准化了数据结构定义,同时数据结构变动SQL会被自动记录,便于同步更新非开发环境数据库,避免生产环境漏执行SQL的问题。
|
||||
3. 可以说,devtools对于数据结构设计带来的改变是全方位的,我们在devtools中提供的数据结构设计和数据模型可视化,以及与代码的联动更新,都在推动一个目标:标准化、自动化、高质量、高效率。
|
||||
|
||||
### 解决软件开发效率的问题
|
||||
1. diboot-core项目极大精简了CRUD和简单关联场景的实现代码,并内置了一些常用工具,以及一些底层模块,基于这些工具和底层模块,可以更快地开始开发工作。
|
||||
2. devtools实现了数据结构与代码联动,在数据结构更改后,也能智能地更新相关代码。
|
||||
3. devtools还会逐步提供一些常用的功能模块,开箱即用。
|
||||
|
||||
### 降低后期维护成本
|
||||
1. 上面的这波操作,带来的另外一个好处是:需要开发人员写的只有真正的业务逻辑代码,代码的可维护性提高。
|
||||
|
||||
## devtools支持数据库
|
||||
MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
|
||||
## devtools支持的数据库版本
|
||||
* MySQL 5.7+
|
||||
* MariaDB 10+
|
||||
* ORACLE 12c+
|
||||
* SQLServer 2017+
|
||||
* PostgreSQL 11+
|
|
@ -14,7 +14,7 @@ diboot.devtools.output-path-sql=devtools-example/src/main/resources/
|
|||
|
||||
## 更新数据库和代码
|
||||
* 该操作会更新**数据库表结构**及**样例数据**,以及将数据结构的更改SQL记录到对应的sql文件中,这点与**仅更新数据库**功能相同。
|
||||
* 会在已有代码的基础上,对Entity、VO、Service及其实现类、Mapper类、Controller类中涉及到的代码进行更新。
|
||||
* 会在已有代码的基础上,对Entity、DTO、VO、Service及其实现类、Mapper类、Controller类中涉及到的代码进行更新。
|
||||
* 更新过程中,近会对更改的相关字段所涉及到的相关字段以及方法进行更新。
|
||||
* 这些被更新的字段以及方法不会影响到其他的字段以及方法,因此对于相关联的字段以及方法之外的代码并没有破坏作用。
|
||||
* 对于被更新的方法,将会覆盖原方法,目前将会覆盖的方法主要为entity以及vo中与更改相关联的字段的**getter/setter**方法,以及在设置关联数据情况下,也将覆盖相对应的Controller文件中的**attachMore**方法
|
||||
|
@ -24,7 +24,7 @@ diboot.devtools.output-path-sql=devtools-example/src/main/resources/
|
|||
|
||||
## 更新数据库&生成代码
|
||||
* 该操作会更新**数据库表结构**及**样例数据**,以及将数据结构的更改SQL记录到对应的sql文件中,这点与**仅更新数据库**功能相同。
|
||||
* 会**重新生成*Entity、VO、Service及其实现类、Mapper及其映射文件、Controller文件等。
|
||||
* 会**重新生成*Entity、DTO、VO、Service及其实现类、Mapper及其映射文件、Controller文件等。
|
||||
::: warning
|
||||
注意:该功能是完全的重新生成代码,同名的相关文件都将被覆盖。
|
||||
:::
|
|
@ -5,21 +5,21 @@
|
|||
## 引入依赖
|
||||
* Gradle项目引入依赖
|
||||
```
|
||||
providedCompile("com.diboot:diboot-devtools-spring-boot-starter:2.0.5")
|
||||
compile("com.diboot:diboot-core-spring-boot-starter:2.0.5")
|
||||
providedCompile("com.diboot:diboot-devtools-spring-boot-starter:{latestVersion}")
|
||||
compile("com.diboot:diboot-core-spring-boot-starter:{latestVersion}")
|
||||
```
|
||||
* Maven项目引入依赖
|
||||
```
|
||||
<dependency>
|
||||
<groupId>com.diboot</groupId>
|
||||
<artifactId>diboot-devtools-spring-boot-starter</artifactId>
|
||||
<version>2.0.5</version>
|
||||
<version>{latestVersion}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.diboot</groupId>
|
||||
<artifactId>diboot-core-spring-boot-starter</artifactId>
|
||||
<version>2.0.5</version>
|
||||
<version>{latestVersion}</version>
|
||||
</dependency>
|
||||
```
|
||||
> diboot-devtools-spring-boot-starter 是用于开发过程的助手工具,须声明 **provided**以免打包至非开发环境。
|
||||
|
@ -34,16 +34,11 @@ spring.main.allow-bean-definition-overriding=true
|
|||
diboot.devtools.codes-version=1.0
|
||||
diboot.devtools.codes-copyright=MyCompany
|
||||
diboot.devtools.codes-author=MyName
|
||||
diboot.devtools.output-path-entity=diboot-example/src/main/java/com/diboot/example/entity/
|
||||
diboot.devtools.output-path-vo=diboot-example/src/main/java/com/diboot/example/vo/
|
||||
diboot.devtools.output-path-service=diboot-example/src/main/java/com/diboot/example/service/
|
||||
diboot.devtools.output-path-mapper=diboot-example/src/main/java/com/diboot/example/mapper/
|
||||
diboot.devtools.output-path-controller=diboot-example/src/main/java/com/diboot/example/controller/
|
||||
diboot.devtools.output-path-sql=diboot-example/src/main/resources/
|
||||
diboot.devtools.output-path=example/src/main/java/com/diboot/example/
|
||||
diboot.devtools.output-path-sql=example/src/main/resources/
|
||||
diboot.devtools.enable-lombok=true
|
||||
#diboot.devtools.enable-swagger=false
|
||||
#diboot.devtools.generate-mapper-xml=false
|
||||
#diboot.devtools.controller-mapping-extend=false
|
||||
```
|
||||
|
||||
* 配置信息说明
|
||||
|
@ -54,11 +49,11 @@ diboot.devtools.enable-lombok=true
|
|||
* codes-version:当前使用diboot-devtools的版本号。
|
||||
* codes-copyright:生成代码的版权归属,显示在每个类或接口的注释中。
|
||||
* codes-author:生成代码的作者,显示在每个类或接口的注释中。
|
||||
* output-path-*:分别指向当前项目中`Entity`、`VO`、`Service及其实现类`、`Mapper及映射文件`、`Controller`、`SQL文件所在的路径`。
|
||||
* output-path:当前项目代码的默认起始路径
|
||||
* output-path-*:分别定义当前项目中`Entity`、`VO`、`Service及其实现类`、`Mapper及映射文件`、`Controller`、等文件所在的路径。
|
||||
* generate-mapper-xml:是否生成Mapper.xml文件,默认true
|
||||
* enable-lombok:是否引入`Lombok`注解,若设置true,请注意添加Lombok依赖。
|
||||
* enable-swagger:是否引入`Swagger`注解,若设置true,请注意添加Swagger依赖。
|
||||
* controller-mapping-extend: controller是否继承crud的url mapping,默认为false
|
||||
|
||||
:::warning
|
||||
如果您使用的是**PostgreSQL数据库**,那么需要额外添加两行配置,以此来适配boolean类型字段所对应的数据库的boolean类型,需添加的配置如下:
|
||||
|
@ -91,7 +86,7 @@ diboot-devtools在初次运行中,会自动安装所需数据库表,如果
|
|||
其中的`URL`即是devtools页面链接,点击即可打开Devtools操作界面。
|
||||
|
||||
## 注意
|
||||
* Devtools是用于开发过程的助手工具,切勿将其打包至其他环境。实践准则:
|
||||
* Devtools是用于开发过程的助手工具,切勿将其打包至非开发环境。实践准则:
|
||||
* 方式一(非开发环境取消devtools依赖):
|
||||
* Gradle项目引入依赖
|
||||
```
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
## 1、引入依赖
|
||||
Gradle:
|
||||
~~~gradle
|
||||
compile("com.diboot:diboot-file-spring-boot-starter:2.0.5")
|
||||
compile("com.diboot:diboot-file-spring-boot-starter:{latestVersion}")
|
||||
~~~
|
||||
或Maven
|
||||
~~~xml
|
||||
<dependency>
|
||||
<groupId>com.diboot</groupId>
|
||||
<artifactId>diboot-file-spring-boot-starter</artifactId>
|
||||
<version>2.0.5</version>
|
||||
<version>{latestVersion}</version>
|
||||
</dependency>
|
||||
~~~
|
||||
|
||||
diboot-file会自动依赖以下jar包,无需重复引入:
|
||||
* commons-fileupload: 1.4
|
||||
* easyexcel:2.1.x
|
||||
* easyexcel:2.x
|
||||
* okhttp:4.3.x
|
||||
* thumbnailator: 0.4.9 (图片压缩,不需要可剔除)
|
||||
* easy-captcha: 1.6.x (验证码,不需要可剔除)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# IAM-base: 身份认证组件 (基础版)
|
||||
# IAM-base: 身份认证与访问控制组件 (基础版)
|
||||
|
||||
## 组件特性
|
||||
* 开箱即用的RBAC角色权限模型
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
## 1、引入依赖
|
||||
Gradle:
|
||||
~~~gradle
|
||||
compile("com.diboot:diboot-iam-base-spring-boot-starter:2.0.5.1")
|
||||
compile("com.diboot:diboot-iam-base-spring-boot-starter:{latestVersion}")
|
||||
compile("com.github.whvcse:easy-captcha:1.6.2")
|
||||
~~~
|
||||
或Maven
|
||||
|
@ -11,7 +11,7 @@ compile("com.github.whvcse:easy-captcha:1.6.2")
|
|||
<dependency>
|
||||
<groupId>com.diboot</groupId>
|
||||
<artifactId>diboot-iam-base-spring-boot-starter</artifactId>
|
||||
<version>2.0.5.1</version>
|
||||
<version>{latestVersion}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.whvcse</groupId>
|
||||
|
@ -60,8 +60,8 @@ diboot.iam.jwt-token-expires-minutes=60
|
|||
#Shiro的匿名urls,用逗号分隔
|
||||
diboot.iam.anon-urls=/test/**,/abc/**
|
||||
|
||||
#是否开启权限自动更新,默认true,非开发环境需关闭
|
||||
diboot.iam.enable-permission-update=true
|
||||
#是否开启权限检查(开发环境可关闭方便调试)
|
||||
#diboot.iam.enable-permission-check=false
|
||||
|
||||
#缓存实现类,默认为: org.apache.shiro.cache.MemoryConstrainedCacheManager
|
||||
diboot.iam.cache-manager-class=org.apache.shiro.cache.MemoryConstrainedCacheManager
|
||||
|
|
|
@ -31,4 +31,23 @@ Employee currentUser = IamSecurityUtils.getCurrentUser();
|
|||
需要创建缓存实现类实现CacheManager接口,并配置参数diboot.iam.cache-manager-class为你的缓存类。
|
||||
```
|
||||
diboot.iam.cache-manager-class=com.xxx.MyCacheManager
|
||||
```
|
||||
```
|
||||
|
||||
## 4. 自定义数据权限
|
||||
> 某些场景下搜索查询需要绑定一些强制条件,用于数据权限控制,如只能查询本部门的数据。
|
||||
##### 1. 在需要控制的字段上添加@DataAccessCheckpoint注解,指定CheckpointType。
|
||||
示例代码:
|
||||
~~~java
|
||||
// 数据权限检查点
|
||||
@DataAccessCheckpoint(type = CheckpointType.ORG)
|
||||
private Long orgId;
|
||||
~~~
|
||||
##### 2. 实现DataAccessCheckInterface接口,返回对应CheckpointType的合法ID集合
|
||||
~~~java
|
||||
public class DataAccessCheckImpl implements DataAccessCheckInterface {
|
||||
@Override
|
||||
public List<Long> getAccessibleIds(CheckpointType type) {
|
||||
// 返回对应检查点的合法ID
|
||||
}
|
||||
~~~
|
||||
通过QueryBuilder构建时,将自动追加IN(合法ID)条件。
|
|
@ -1,2 +0,0 @@
|
|||
# diboot-core 及 diboot-core-spring-boot-starter 相关
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# diboot-devtools-spring-boot-starter相关
|
||||
|
||||
1. **devtools最怕什么?**
|
||||
|
||||
答:项目路径中有中文。。。
|
|
@ -1,9 +0,0 @@
|
|||
# diboot-iam-base-spring-boot-starter相关
|
||||
|
||||
1. **为什么我首次启动后,接口不能访问?**
|
||||
|
||||
答:您好,首次启动,devtools将为您的项目进行数据库初始化和生成基础代码等工作,这些都需要您在这些流程完成之后,重启应用方可生效。
|
||||
|
||||
2. **为何引入iam后启动报错?**
|
||||
|
||||
答:请确保您配置了**@EnableTransactionManagement**注解,可参考[IAM参数配置-注解配置](/guide/diboot-iam/开始使用.html#_2、参数配置:)
|
|
@ -0,0 +1,99 @@
|
|||
# diboot-iam-base-spring-boot-starter相关
|
||||
|
||||
## diboot支持Spring Boot哪些版本?
|
||||
* diboot 2.0.x 支持 Spring boot 2.2.x
|
||||
* diboot 2.1.x 支持 Spring boot 2.3+
|
||||
|
||||
## 如何自定义fastjson配置
|
||||
diboot-core-starter中包含默认的HttpMessageConverters配置,启用fastjson并做了初始化配置。
|
||||
其中关键配置参数为:
|
||||
~~~java
|
||||
@Bean
|
||||
public HttpMessageConverters fastJsonHttpMessageConverters() {
|
||||
...
|
||||
// 设置fastjson的序列化参数:禁用循环依赖检测,数据兼容浏览器端(避免JS端Long精度丢失问题)
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect,
|
||||
SerializerFeature.BrowserCompatible);
|
||||
...
|
||||
}
|
||||
~~~
|
||||
如果该配置无法满足您的开发场景,可以在Configuration文件中重新定义HttpMessageConverters:
|
||||
~~~java
|
||||
@Bean
|
||||
public HttpMessageConverters fastJsonHttpMessageConverters() {
|
||||
...
|
||||
}
|
||||
~~~
|
||||
|
||||
## 无数据库连接配置文件的module下,如何使用diboot-core?
|
||||
diboot-core-starter是在diboot-core的基础上增加了自动配置,配置需要依赖数据库信息。
|
||||
如果是无数据库信息的模块下使用,可以依赖core,替换core-starter。
|
||||
~~~xml
|
||||
<dependency>
|
||||
<groupId>com.diboot</groupId>
|
||||
<artifactId>diboot-core</artifactId>
|
||||
<version>{latestVersion}</version>
|
||||
</dependency>
|
||||
~~~
|
||||
|
||||
## 启动报错:找不到mapper中的自定义接口
|
||||
diboot-devtools默认不指定mapper.xml路径时,mapper.xml文件会生成到mapper同路径下便于维护。
|
||||
此时需要修改pom配置,让编译包含xml、dtd类型文件。
|
||||
* Maven配置:
|
||||
~~~xml
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes>
|
||||
<include>**/*.xml</include>
|
||||
<include>**/*.dtd</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
~~~
|
||||
* Gradle配置:
|
||||
```groovy
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDirs "src/main/java"
|
||||
include '**/*.xml'
|
||||
include '**/*.dtd'
|
||||
include '**/*.class'
|
||||
}
|
||||
resources {
|
||||
srcDirs "src/main/resources"
|
||||
include '**'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 如何构建树形结构?
|
||||
> 树形结构对象约定:要有 parentId属性 和 List children 属性,便于自动构建。
|
||||
* 1. 先把需要构建树形结构的节点全部查出来,如:
|
||||
~~~java
|
||||
List<Menu> menus = menuService.getEntityList(wrapper);
|
||||
~~~
|
||||
* 2. 调用BeanUtils.buildTree构建树形结构
|
||||
~~~java
|
||||
// 如果children属性在VO中,可以调用BeanUtils.convertList转换后再构建
|
||||
menus = BeanUtils.buildTree(menus);
|
||||
~~~
|
||||
|
||||
## IAM的后端代码在哪里?
|
||||
IAM的后端基础代码由devtools自动生成
|
||||
* 配置好diboot组件依赖和devtools依赖
|
||||
* 启动项目,进入devtools的组件初始化页面,选择core及IAM等组件,执行初始化
|
||||
|
||||
注:[diboot-v2-example](https://github.com/dibo-software/diboot-v2-example) 中包含可供参考的后端示例:diboot-iam-example(IAM示例代码)
|
||||
及diboot-online-demo(线上演示项目)。
|
||||
|
||||
## 为何引入iam后启动报错?
|
||||
确保您配置了**@EnableTransactionManagement**注解,可参考 [IAM参数配置-注解配置](/guide/diboot-iam/开始使用.html#_2、参数配置:)
|
|
@ -0,0 +1,65 @@
|
|||
## 版本升级向导
|
||||
|
||||
### diboot v2.0.x 升级至 v2.1.x
|
||||
#### 1. diboot-core:
|
||||
* v2.1.x 版本开始,BaseCrudRestController移除了方法中的request参数,改为在BaseController中统一注入,以简化继承代码。
|
||||
子类需要时直接用request变量即可,无需再定义request参数。
|
||||
修改: 调用父类方法的Controller方法,需删除方法中的request参数定义及传递。
|
||||
示例:
|
||||
~~~java
|
||||
public JsonResult getDepartmentVOList(DepartmentDto departmentDto, HttpServletRequest request) throws Exception{
|
||||
QueryWrapper<Department> queryWrapper = super.buildQueryWrapper(departmentDto, request);
|
||||
...
|
||||
}
|
||||
~~~
|
||||
修改为
|
||||
~~~java
|
||||
public JsonResult getDepartmentVOList(DepartmentDto departmentDto) throws Exception{
|
||||
QueryWrapper<Department> queryWrapper = super.buildQueryWrapper(departmentDto);
|
||||
...
|
||||
}
|
||||
~~~
|
||||
|
||||
* v2.1.x 版本开始,BaseCrudRestController移除了VO泛型参数,便于子类区分不同VO,同时父类方法getViewObject*增加VO class参数用于指定VO。
|
||||
修改示例:
|
||||
~~~java
|
||||
public class DepartmentController extends BaseCustomCrudRestController<Department, DepartmentVO> {
|
||||
...
|
||||
super.getViewObjectList(entity, pagination);
|
||||
“}
|
||||
~~~
|
||||
修改为
|
||||
~~~java
|
||||
public class DepartmentController extends BaseCustomCrudRestController<Department> {
|
||||
...
|
||||
super.getViewObjectList(entity, pagination, DepartmentVO.class);
|
||||
“}
|
||||
~~~
|
||||
|
||||
* v2.1.x版本开始,extdata扩展字段将不再推荐使用,该字段设计目的用于字段冗余的json存储,可以通过数据库的json数据类型实现。
|
||||
devtools从2.1版本开始不再支持extdata的特殊处理。
|
||||
|
||||
* v2.1.x版本依赖组件升级为: Spring Boot 2.3.0,Mybatis-Plus 3.3.2,fastjson 1.2.70。根据您的依赖情况,可能会有依赖冲突需要解决。
|
||||
|
||||
#### 2. diboot-devtools
|
||||
* v2.1版本开始,配置参数:
|
||||
新增 **diboot.devtools.output-path** 代码的生成根路径配置项,
|
||||
如entity, dto, controller, mapper, service, vo等路径无自定义需求,仅配置该根路径即可。
|
||||
示例:
|
||||
~~~properties
|
||||
diboot.devtools.output-path=example/src/main/java/com/diboot/example/
|
||||
~~~
|
||||
同时开放更多的自定义配置项,如:
|
||||
~~~properties
|
||||
diboot.devtools.output-path-mapper-xml=
|
||||
diboot.devtools.output-path-service-impl=
|
||||
diboot.devtools.output-path-dto=
|
||||
diboot.devtools.output-path-exception-handler=
|
||||
~~~
|
||||
|
||||
v2.1.x版本开始支持前端代码生成,如果需要该功能,则需配置。如
|
||||
~~~properties
|
||||
diboot.devtools.output-path-frontend=/Workspace/diboot-antd-admin-ent/
|
||||
~~~
|
||||
|
||||
#### 3. diboot-iam
|
|
@ -38,7 +38,7 @@
|
|||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>2.2.3</version>
|
||||
</dependency>
|
||||
<!-- http -->
|
||||
<dependency>
|
||||
|
|
|
@ -32,7 +32,8 @@ import java.util.List;
|
|||
|
||||
/***
|
||||
* 文件操作辅助类
|
||||
* @author Mazc
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileHelper{
|
||||
|
|
|
@ -32,7 +32,8 @@ import java.util.Base64;
|
|||
|
||||
/***
|
||||
* 图片操作辅助类
|
||||
* @author Mazc
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class ImageHelper {
|
||||
|
|
|
@ -29,7 +29,8 @@ import java.util.zip.ZipOutputStream;
|
|||
|
||||
/***
|
||||
* 文件压缩操作辅助类
|
||||
* @author Mazc
|
||||
* @author mazc@dibo.ltd
|
||||
* @version v2.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZipHelper {
|
||||
|
|
16
pom.xml
16
pom.xml
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<version>2.3.0.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<springboot.version>2.2.6.RELEASE</springboot.version>
|
||||
<springboot.version>2.3.0.RELEASE</springboot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -62,20 +62,26 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- SpringBoot 2.3.0 新增依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>6.0.19.Final</version>
|
||||
<version>6.1.5.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.68</version>
|
||||
<version>1.2.70</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
|
Loading…
Reference in New Issue