Merge branch 'develop' of https://github.com/dibo-software/diboot-v2 into develop

This commit is contained in:
wuy 2020-06-11 15:27:55 +08:00
commit e1b0e9541b
69 changed files with 1678 additions and 1010 deletions

View File

@ -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. 跨表查询无SQLQueryWrapper自动构建与查询
> @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代码到本地

View File

@ -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;

View File

@ -1,4 +1,4 @@
## diboot-core: 全新优化内核
## diboot-core: 高效精简内核
主要实现:
1. 单表CRUD和多表关联查询的无SQL化
2. Entity/DTO自动转换为QueryWrapper@BindQuery注解绑定字段参数的查询条件可自动构建关联查询

View File

@ -39,7 +39,7 @@ import java.util.*;
/**
* join连接查询绑定器
* @author Mazc@dibo.ltd
* @version v2.0.5
* @version v2.1
* @date 2020/04/15
*/
@Slf4j

View File

@ -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())

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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;
}

View File

@ -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);
}
}
}
}

View File

@ -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){

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.config.Cons;
import com.diboot.core.util.BeanUtils;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.lang.reflect.Field;
/**
* table的相关线索信息
* @author mazc@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;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -210,7 +210,7 @@ public interface BaseService<T> {
/**
* 获取符合条件的一个Entity实体
* @param queryWrapper 主键
* @param queryWrapper
* @return entity
*/
T getSingleEntity(Wrapper queryWrapper);

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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验证错误"));
}
}

View File

@ -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/'
}]
}*/]
}
}

View File

@ -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
```
该方法用来删除数据入参为数据IDid调用该方法成功后会删除相关表中的数据。
* 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, "缓存清空");
```

View File

@ -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>
```

View File

@ -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);
```
> 该接口通过查询条件对符合该查询条件的所有记录进行删除操作

View File

@ -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)

View File

@ -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;
```

View File

@ -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、属性值MappropMap
* 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的mapkey为指定字段值
```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的mapkey为指定字段值的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 方法

View File

@ -1,14 +0,0 @@
# 异常处理
## DefaultExceptionHandler
* 默认异常处理,继承自该类并添加@ControllerAdvice注解即可自动支持:
* 兼容JSON请求和Html页面请求的Exception异常处理
* Entity绑定校验的统一处理BindException.class, MethodArgumentNotValidException.class
* 示例代码
~~~java
@ControllerAdvice
public class GeneralExceptionHandler extends DefaultExceptionHandler{
}
~~~

View File

@ -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直接关联从表字段集合注解示例如下

View File

@ -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组件。

View File

@ -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

View File

@ -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查询无SQLQueryWrapper自动构建与查询
> @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)

View File

@ -0,0 +1,21 @@
# 设计理念
## diboot 的诞生背景
> 众多开发团队现状: 效率低、质量差、可维护性差我们也常听到开发者抱怨每天CRUD、工作就是搬砖。
> 很多团队雇不起好的架构师,即使有也大概率不会给他机会去做打地基的事情。
> diboot正在尝试做些改变 :
## diboot 的设计理念
* Web系统开发需要一个普适的基础框架把复杂的问题简单化最好还能做到更佳性能规避常见的坑
* 程序员很难被替代但程序员应该聚焦于数据结构设计、业务实现、难点解决重复CRUD没啥长进
* CRUD类重复性的工作是可以被自动化甚至被省掉的包括单表及常规的关联场景相关场景
## diboot 主要技术栈
* 后端Java+关系数据库跟紧Spring Boot
* ORM站队Mybatis通用Mapper框架选择Mybatis-plus
* 前后分离前端选型Vue

View File

@ -1,40 +1,25 @@
# 介绍
## diboot-devtools是什么?
## diboot-devtools 的诞生背景
> diboot-devtools是一个面向Java开发人员的开发助理有了她你可以摆脱CRUD等重复性的Coding更专注于业务实现提高开发效率和代码质量
> 众多开发团队现状: 效率低、质量差、可维护性差我们也常听到开发者抱怨每天CRUD、工作就是搬砖
> **diboot-devtools - 将重复有规律的事情自动化**
> diboot-devtools 是面向Java开发人员的自动化助理有了她你可以彻底摆脱CRUD等重复性的工作专注于数据结构设计、业务实现提高软件的质量、效率、可维护性。
## 我们的优势
* 支持常用的五大数据库MySQLMariaDBORACLESQLServer, 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+

View File

@ -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
注意:该功能是完全的重新生成代码,同名的相关文件都将被覆盖。
:::

View File

@ -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项目引入依赖
```

View File

@ -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 (验证码,不需要可剔除)

View File

@ -1,4 +1,4 @@
# IAM-base: 身份认证组件 (基础版)
# IAM-base: 身份认证与访问控制组件 (基础版)
## 组件特性
* 开箱即用的RBAC角色权限模型

View File

@ -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

View File

@ -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)条件。

View File

@ -1,2 +0,0 @@
# diboot-core 及 diboot-core-spring-boot-starter 相关

View File

@ -1,5 +0,0 @@
# diboot-devtools-spring-boot-starter相关
1. **devtools最怕什么**
答:项目路径中有中文。。。

View File

@ -1,9 +0,0 @@
# diboot-iam-base-spring-boot-starter相关
1. **为什么我首次启动后,接口不能访问?**
您好首次启动devtools将为您的项目进行数据库初始化和生成基础代码等工作这些都需要您在这些流程完成之后重启应用方可生效。
2. **为何引入iam后启动报错**
答:请确保您配置了**@EnableTransactionManagement**注解,可参考[IAM参数配置-注解配置](/guide/diboot-iam/开始使用.html#_2、参数配置)

View File

@ -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-exampleIAM示例代码
及diboot-online-demo线上演示项目
## 为何引入iam后启动报错
确保您配置了**@EnableTransactionManagement**注解,可参考 [IAM参数配置-注解配置](/guide/diboot-iam/开始使用.html#_2、参数配置)

View File

@ -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.0Mybatis-Plus 3.3.2fastjson 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

View File

@ -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>

View File

@ -32,7 +32,8 @@ import java.util.List;
/***
* 文件操作辅助类
* @author Mazc
* @author mazc@dibo.ltd
* @version v2.0
*/
@Slf4j
public class FileHelper{

View File

@ -32,7 +32,8 @@ import java.util.Base64;
/***
* 图片操作辅助类
* @author Mazc
* @author mazc@dibo.ltd
* @version v2.0
*/
@Slf4j
public class ImageHelper {

View File

@ -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
View File

@ -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>