Merge remote-tracking branch 'remotes/origin/develop'

# Conflicts:
#	README.md
This commit is contained in:
Yangzhao 2020-07-18 10:16:58 +08:00
commit 430030edda
196 changed files with 11670 additions and 1973 deletions

View File

@ -1,6 +1,6 @@
> 提示diboot v2.0.5版本暂不支持spring boot 2.3请切换至2.2.x版本。
> v2.1版本发布可以告别常规SQL和CRUD了
# diboot
# diboot - 化繁为简,以简驭繁
<p align="center">
<a href="http://www.apache.org/licenses/LICENSE-2.0.html" target="_blank">
<img src="https://img.shields.io/hexpm/l/plug.svg">
@ -10,34 +10,41 @@
</a>
</p>
> [设计目标](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: 精简优化内核
全新精简内核,(基于diboot-core 2.x版本的CRUD和简单关联的常规功能实现代码量比1.x版本减少70%+,主要实现:
高效精简内核,重构查询方式(拆解关联查询,程序中Join),简化开发,主要实现:
#### 1. 单表CRUD无SQL
> 基于Mybatis-Plus实现Mybatis-Plus具备通用Mapper方案和灵活的查询构造器
#### 2. 关联查询无SQL注解自动绑定
> 扩展实现了多表关联查询的无SQL方案只需要一个简单注解@Bind*,就可以实现关联对象(含字段、实体、实体集合等)的数据绑定,且实现方案是将关联查询拆解为单表查询,保障最佳性能。
#### 2. 关联绑定无SQL注解自动绑定
> 扩展实现了多表关联查询的无SQL方案只需要一个简单注解@Bind*,就可以实现关联对象(含字段、字段集合、实体、实体集合等)的数据绑定,且实现方案是将关联查询拆解为单表查询,保障最佳性能。
#### 3. 数据字典无SQL注解自动绑定
> 通过@BindDict注解实现数据字典(枚举)的存储值value与显示值name的转换。
#### 4. Entity/DTO自动转换为QueryWrapper
> @BindQuery注解绑定字段参数对应的查询条件类型Controller中直接绑定转换为QueryWrapper无需再手动构建QueryWrapper查询条件
#### 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 "注解自动绑定多表关联").
## 二、 diboot-devtools 自动化开发助理
* 使用简单UI界面操作引入依赖配置参数后即可随SpringBoot本地项目启动运行
* 功能很强大:
* 单表与关联场景CRUD导入导出的完整功能全自动生成无需手写代码
* 数据结构变更与代码联动同步自动记录变更SQL、维护索引
* 一键生成代码&非覆盖式更新本地后端代码
* 配置很灵活(可按需配置生成代码路径,是否启用`Lombok`、`Swagger`等)
* SQL与代码很标准devtools标准化了数据结构定义与代码实现
* 支持多数据库MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
* 使用很简单UI界面操作引入依赖配置参数后即可随SpringBoot本地项目启动运行
* 功能很强大(数据结构变更与代码联动同步,一键生成&非覆盖式更新代码自动记录变更SQL、维护索引
* 配置很灵活(可按需配置生成代码是否启用`Lombok`、`Swagger`等)
* SQL与代码很标准devtools标准化了数据结构定义与代码实现降低维护成本
> [我要试试](https://www.diboot.com/guide/diboot-devtools/%E4%BB%8B%E7%BB%8D.html)
## 三、iam-base 身份认证基础组件 及 配套VUE前端框架diboot-antd-admin、diboot-element-admin
@ -45,27 +52,29 @@ diboot v2版本目前实现: diboot-core全新内核 + diboot-devtools开发
* RBAC角色权限模型 + JWT的认证授权 实现支持刷新token
* 简化的BindPermission注解支持兼容shiro的简化权限绑定与自动鉴权
* 自动提取需要验证的后端接口, 借助前端功能方便绑定前后端菜单按钮权限
* 支持基于注解的数据权限实现
* 支持灵活的扩展能力(扩展多种登录方式、灵活替换用户实体类、自定义缓存等)
* Starter启动自动安装依赖的数据表
* 启用devtools自动生成初始controller代码到本地
更多介绍请查看: [iam-base-starter README](https://github.com/dibo-software/diboot-v2/tree/master/iam-base-starter "身份认证管理组件").
## 四、diboot-file 文件相关处理组件
* EasyExcel轻量封装支持Java注解校验与@BindDict注解实现字典name-value转换提供完善的校验错误提示
* EasyExcel轻量封装支持Java注解校验与@ExcelBind*注解实现字典及关联字段的name-value转换提供完善的校验错误提示
* 封装常用的文件本地存储、上传下载、图片压缩水印等常用处理
* Starter启动自动安装依赖的数据表
* 启用devtools自动生成初始样例controller代码到本地
更多介绍请查看: [diboot-file-starter README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-file-starter "文件组件").
> 其他组件逐步开发中 ...
## 五、技术交流群
## 五、捐助支持
![捐助二维码](https://www.diboot.com/assets/img/donate.a25badf5.jpg)
感谢JetBrains提供Open Source license[JetBrains IDEA](https://www.jetbrains.com/?from=diboot) 是最好的Java IDE。
## 六、技术交流群
如果您有技术问题,欢迎加群交流:
> QQ群: [731690096]()
> 微信群备注diboot加: [wx20201024]()

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.5</version>
<version>2.1.1</version>
</parent>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
<version>2.1.1</version>
<packaging>jar</packaging>
<description>diboot core starter project</description>
@ -25,7 +25,7 @@
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core</artifactId>
<version>2.0.5</version>
<version>2.1.1</version>
</dependency>
<dependency>

View File

@ -18,8 +18,9 @@ package com.diboot.core.starter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.diboot.core.config.Cons;
import com.diboot.core.util.D;
import com.diboot.core.util.DateConverter;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -30,8 +31,10 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.util.ArrayList;
@ -48,7 +51,7 @@ import java.util.List;
@ComponentScan(basePackages={"com.diboot.core"})
@MapperScan(basePackages = {"com.diboot.core.mapper"})
@Order(1)
public class CoreAutoConfiguration{
public class CoreAutoConfiguration implements WebMvcConfigurer {
@Autowired
Environment environment;
@ -70,6 +73,7 @@ public class CoreAutoConfiguration{
}
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
converter.setDefaultCharset(Charset.forName(Cons.CHARSET_UTF8));
@ -81,11 +85,29 @@ public class CoreAutoConfiguration{
// 设置fastjson的序列化参数禁用循环依赖检测数据兼容浏览器端避免JS端Long精度丢失问题
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.BrowserCompatible);
fastJsonConfig.setDateFormat(D.FORMAT_DATETIME_Y4MDHM);
converter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> httpMsgConverter = converter;
return new HttpMessageConverters(httpMsgConverter);
}
/**
* Mybatis-plus分页插件
*/
@Bean
@ConditionalOnMissingBean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
/**
* 默认支持String-Date类型转换
* @param registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DateConverter());
}
}

View File

@ -1,19 +1,20 @@
# diboot-core: 全新优化内核
## diboot-core: 高效精简内核
主要实现:
1. 单表CRUD和多表关联查询的无SQL化
2. Entity/DTO自动转换为QueryWrapper@BindQuery注解绑定字段参数对应的查询条件,无注解默认映射为等于=条件
2. Entity/DTO自动转换为QueryWrapper@BindQuery注解绑定字段参数的查询条件,可自动构建关联查询
3. 提供其他常用开发场景的最佳实践封装。
## ** 一. 单表CRUD无SQL
### ** 一. 单表CRUD无SQL
> 依赖Mybatis-plus实现Mybatis-plus具备通用Mapper方案和灵活的查询构造器
## ** 二. 多表关联查询无SQL通过注解绑定关联自动拆分成单表查询并绑定结果
> 通过注解实现多数场景下的关联查询无SQL
### 1. 注解自动绑定数据字典(自定义枚举)的显示值Label
### ** 二. 多表关联查询无SQL
> 通过@Bind*注解绑定关联,自动拆分成单表查询并绑定结果
[(了解拆解关联查询的价值)](https://www.kancloud.cn/ddupl/sql_optimize/1141077)
#### 1. 注解自动绑定数据字典(自定义枚举)的显示值Label
~~~java
@BindDict(type="USER_STATUS", field = "status")
private String statusLabel;
~~~
### 2. 注解自动绑定其他表的字段
#### 2. 注解自动绑定其他表的字段
~~~java
// 支持关联条件+附加条件绑定字段
@BindField(entity=Department.class, field="name", condition="department_id=id AND parent_id>=0")
@ -23,7 +24,7 @@ private String deptName;
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
~~~
### 3. 注解自动绑定其他表实体Entity
#### 3. 注解自动绑定其他表实体Entity/VO
~~~java
// 支持关联条件+附加条件绑定Entity
@BindEntity(entity = Department.class, condition="department_id=id")
@ -33,7 +34,7 @@ private Department department;
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id AND department.deleted=0")
private Organization organization;
~~~
### 4. 注解自动绑定其他表实体集合List<Entity>
#### 4. 注解自动绑定其他表实体集合List<Entity>
~~~java
// 支持关联条件+附加条件绑定多个Entity
@BindEntityList(entity = Department.class, condition = "id=parent_id")
@ -43,45 +44,42 @@ private List<Department> children;
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
~~~
#### 5. 注解自动绑定其他表某字段集合List<>
~~~java
// 支持关联条件+附加条件绑定多个Entity的某字段
@BindFieldList(entity = Department.class, field="id", condition = "id=parent_id")
private List<Long> childrenIds;
## ** 三. 注解绑定关联的使用方式
### 1. 引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-core-spring-boot-starter:2.0.5")
// 通过中间表的 多对多关联 绑定Entity某字段支持附加条件
@BindEntityList(entity = Role.class, field="code", condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<String> roleCodes;
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
</dependency>
~~~
> * 使用diboot-devtools会自动引入diboot-core无需配置此依赖。
> * @BindDict注解需要依赖dictionary表初次启动时starter会自动创建该表。
### 2. 定义你的Service继承diboot的BaseService或Mybatis-plus的ISerivice及Mapper
### ** 三. 注解绑定关联的使用方式
### 3. 使用注解绑定:
调用RelationsBinder自动绑定注解相关关联
#### 方式1. 自动绑定关联(不需要转型)
#### 1. 定义你的Entity对应的Service继承diboot的BaseService及Mapper
> * 启用diboot-devtools自动生成后端各层代码。
#### 2. 参照以上注解说明在VO中定义你的关联
#### 3. 使用注解绑定:
调用Binder自动绑定注解相关关联
##### 方式1. 自动绑定关联(不需要转型)
~~~java
//List<MyUserVO> voList = ...;
RelationsBinder.bind(voList);
Binder.bindRelations(voList);
~~~
#### 方式2. 自动转型并绑定关联(需要转型)
##### 方式2. 自动转型并绑定关联(需要转型)
~~~java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
List<MyUserVO> voList = RelationsBinder.convertAndBind(userList, MyUserVO.class);
List<MyUserVO> voList = Binder.convertAndBindRelations(userList, MyUserVO.class);
~~~
## ** 四. Entity/DTO自动转换为QueryWrapper的使用方式
### 1. Entity/DTO中声明映射查询条件
### ** 四. Entity/DTO自动转换QueryWrapper自动构建Join查询
#### 1. Entity/DTO中声明映射查询条件
示例代码:
~~~java
public class UserDTO{
public class UserDTO {
// 无@BindQuery注解默认会映射为=条件
private Long gender;
@ -89,22 +87,76 @@ public class UserDTO{
@BindQuery(comparison = Comparison.LIKE)
private String realname;
//... getter, setter
// join其他表
@BindQuery(comparison = Comparison.STARTSWITH, entity=Organization.class, field="name", condition="this.org_id=id")
private String orgName;
}
~~~
### 2. 调用QueryBuilder.toQueryWrapper(entityOrDto)进行转换
#### 2. 调用QueryBuilder.toQueryWrapper(entityOrDto)进行转换
~~~java
/**
* url参数示例: /list?gender=M&realname=张
* 将映射为 queryWrapper.eq("gender", "M").like("realname", "张")
*/
@GetMapping("/list")
public JsonResult getVOList(UserDto userDto, HttpServletRequest request) throws Exception{
//调用super.buildQueryWrapper(entityOrDto, request) 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行转换
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto, request);
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);
}
~~~
## 五. 样例参考 - [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)
#### 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
LEFT OUTER JOIN organization r1 ON self.org_id=r1.id
WHERE (r1.name LIKE ?) AND self.is_deleted=0
### 五. 使用步骤与样例参考
#### 1. 引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-core-spring-boot-starter:{latestVersion}")
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>{latestVersion}</version>
</dependency>
~~~
#### 2. 配置数据源
以Mysql为例
~~~properties
#datasource config
spring.datasource.url=jdbc:mysql://localhost:3306/diboot_example?characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=diboot
spring.datasource.password=123456
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
~~~
> * @BindDict注解需要依赖dictionary表依赖diboot-core-spring-boot-starter初次启动时starter会自动创建该表。
#### 3. 详细文档 - [diboot-core 官方文档](https://www.diboot.com/guide/diboot-core/%E5%AE%89%E8%A3%85.html)
#### 4. 参考样例 - [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.5</version>
<version>2.1.1</version>
</parent>
<artifactId>diboot-core</artifactId>
<version>2.0.5</version>
<version>2.1.1</version>
<packaging>jar</packaging>
<description>diboot core project</description>
@ -36,6 +36,17 @@
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/test/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.dtd</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/test/resources</directory>
</resource>
</resources>
<plugins>
<plugin>

View File

@ -0,0 +1,108 @@
/*
* 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;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.vo.Pagination;
import java.util.List;
/**
* 绑定器统一调用入口类
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/18
*/
public class Binder {
/**
* 关联查询一条主表数据
* @param queryWrapper
* @param entityClazz 返回结果entity/vo类
* @return
* @throws Exception
*/
public static <DTO,E> E joinQueryOne(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz){
return JoinsBinder.queryOne(queryWrapper, entityClazz);
}
/**
* 关联查询符合条件的全部主表数据集合不分页
* @param queryWrapper 调用QueryBuilder.to*QueryWrapper得到的实例
* @param entityClazz 返回结果entity/vo类
* @return
* @throws Exception
*/
public static <DTO,E> List<E> joinQueryList(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz){
return JoinsBinder.queryList(queryWrapper, entityClazz);
}
/**
* 关联查询符合条件的指定页数据分页
* @param queryWrapper 调用QueryBuilder.to*QueryWrapper得到的实例
* @param entityClazz 返回结果entity/vo类
* @param pagination 分页
* @return
* @throws Exception
*/
public static <DTO,E> List<E> joinQueryList(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz, Pagination pagination){
return JoinsBinder.queryList(queryWrapper, entityClazz, pagination);
}
/**
* 自动转换和绑定单个VO中的注解关联禁止循环调用多个对象请调用convertAndBind(voList, voClass)
* @param voClass 需要转换的VO class
* @param <E>
* @param <VO>
* @return
*/
public static <E, VO> VO convertAndBindRelations(E entity, Class<VO> voClass){
return RelationsBinder.convertAndBind(entity, voClass);
}
/**
* 自动转换和绑定多个VO中的注解关联
* @param entityList 需要转换的VO list
* @param voClass VO class
* @param <E>
* @param <VO>
* @return
*/
public static <E, VO> List<VO> convertAndBindRelations(List<E> entityList, Class<VO> voClass){
return RelationsBinder.convertAndBind(entityList, voClass);
}
/**
* 自动绑定单个VO的关联对象禁止循环调用多个对象请调用bind(voList)
* @param vo 需要注解绑定的对象
* @return
* @throws Exception
*/
public static <VO> void bindRelations(VO vo){
RelationsBinder.bind(vo);
}
/**
* 自动绑定多个VO集合的关联对象
* @param voList 需要注解绑定的对象集合
* @return
* @throws Exception
*/
public static <VO> void bindRelations(List<VO> voList){
RelationsBinder.bind(voList);
}
}

View File

@ -0,0 +1,232 @@
/*
* 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;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.binding.query.dynamic.DynamicJoinQueryWrapper;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.mapper.DynamicQueryMapper;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.*;
/**
* join连接查询绑定器
* @author Mazc@dibo.ltd
* @version v2.1
* @date 2020/04/15
*/
@Slf4j
public class JoinsBinder {
/**
* 关联查询一条数据
* @param queryWrapper
* @param entityClazz 返回结果entity/vo类
* @return
* @throws Exception
*/
public static <DTO,E> E queryOne(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz){
List<E> list = executeJoinQuery(queryWrapper, entityClazz, null, true);
if(V.notEmpty(list)){
return list.get(0);
}
return null;
}
/**
* 关联查询符合条件的全部数据集合不分页
* @param queryWrapper 调用QueryBuilder.to*QueryWrapper得到的实例
* @param entityClazz 返回结果entity/vo类
* @return
* @throws Exception
*/
public static <DTO,E> List<E> queryList(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz){
return queryList(queryWrapper, entityClazz, null);
}
/**
* 关联查询符合条件的指定页数据分页
* @param queryWrapper 调用QueryBuilder.to*QueryWrapper得到的实例
* @param entityClazz 返回结果entity/vo类
* @param pagination 分页
* @return
* @throws Exception
*/
public static <DTO,E> List<E> queryList(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz, Pagination pagination){
return executeJoinQuery(queryWrapper, entityClazz, pagination, false);
}
/**
* 关联查询分页
* @param queryWrapper 调用QueryBuilder.to*QueryWrapper得到的实例
* @param entityClazz 返回结果entity/vo类
* @param pagination 分页
* @return
* @throws Exception
*/
private static <DTO,E> List<E> executeJoinQuery(QueryWrapper<DTO> queryWrapper, Class<E> entityClazz, Pagination pagination, boolean limit1){
// 非动态查询走BaseService
if(queryWrapper instanceof DynamicJoinQueryWrapper == false){
BaseService baseService = ContextHelper.getBaseServiceByEntity(entityClazz);
if(baseService != null){
return baseService.getEntityList(queryWrapper, pagination);
}
else{
throw new BusinessException(Status.FAIL_INVALID_PARAM, "单表查询对象无BaseService实现: "+entityClazz.getSimpleName());
}
}
long begin = System.currentTimeMillis();
// 转换为queryWrapper
DynamicJoinQueryWrapper dynamicJoinWrapper = (DynamicJoinQueryWrapper)queryWrapper;
dynamicJoinWrapper.setMainEntityClass(entityClazz);
List<Map<String, Object>> mapList = null;
if(pagination == null){
if(limit1){
Map<String, Object> oneResult = getDynamicQueryMapper().query(dynamicJoinWrapper);
if(oneResult != null){
mapList = new ArrayList<>();
mapList.add(oneResult);
}
}
else{
mapList = getDynamicQueryMapper().queryForList(dynamicJoinWrapper);
}
}
else{
// 格式化orderBy
formatOrderBy(dynamicJoinWrapper, entityClazz, pagination);
IPage<Map<String, Object>> pageResult = getDynamicQueryMapper().queryForListWithPage(pagination.toPage(), dynamicJoinWrapper);
pagination.setTotalCount(pageResult.getTotal());
mapList = pageResult.getRecords();
}
long ms = (System.currentTimeMillis() - begin);
if(ms > 5000){
log.warn("{} 动态Join查询执行耗时 {} ms建议优化", dynamicJoinWrapper.getDtoClass().getSimpleName(), ms);
}
if(V.isEmpty(mapList)){
return Collections.emptyList();
}
if(mapList.size() > BaseConfig.getBatchSize()){
log.warn("{} 动态Join查询记录数过大( {} 条), 建议优化", dynamicJoinWrapper.getDtoClass().getSimpleName(), mapList.size());
}
// 转换查询结果
List<E> entityList = new ArrayList<>();
for(Map<String, Object> colValueMap : mapList){
Map<String, Object> fieldValueMap = new HashMap<>();
// 格式化map
for(Map.Entry<String, Object> entry : colValueMap.entrySet()){
String fieldName = S.toLowerCaseCamel(entry.getKey());
// 如果是布尔类型检查entity中的定义是Boolean/boolean
if(entry.getValue() instanceof Boolean && S.startsWithIgnoreCase(entry.getKey(),"is_")){
// 检查有is前缀的Boolean类型
Field boolType = BeanUtils.extractField(entityClazz, fieldName);
if(boolType == null){
// 检查无is前缀的boolean类型
String tempFieldName = S.toLowerCaseCamel(S.substringAfter(entry.getKey(), "_"));
boolType = BeanUtils.extractField(entityClazz, tempFieldName);
if(boolType != null){
fieldName = tempFieldName;
}
}
}
fieldValueMap.put(fieldName, entry.getValue());
}
// 绑定map到entity
try{
E entityInst = entityClazz.newInstance();
BeanUtils.bindProperties(entityInst, fieldValueMap);
entityList.add(entityInst);
}
catch (Exception e){
log.warn("new实例并绑定属性值异常", e);
}
}
return entityList;
}
/**
* 格式化orderBy
* @param queryWrapper
* @param pagination
*/
private static <E> void formatOrderBy(DynamicJoinQueryWrapper queryWrapper, Class<E> entityClazz, Pagination pagination){
// 如果是默认id排序检查是否有id字段
if(pagination.isDefaultOrderBy()){
// 优化排序
String pk = ContextHelper.getPrimaryKey(entityClazz);
// 主键非有序id字段需要清空默认排序
if (!Cons.FieldName.id.name().equals(pk)) {
pagination.clearDefaultOrder();
}
}
// 格式化排序
if(V.notEmpty(pagination.getOrderBy())){
List<String> orderByList = new ArrayList<>();
String[] orderByFields = S.split(pagination.getOrderBy());
for(String field : orderByFields){
String fieldName = field, orderType = null;
if(field.contains(":")){
String[] fieldAndOrder = S.split(field, ":");
fieldName = fieldAndOrder[0];
orderType = fieldAndOrder[1];
}
// 获取列定义的AnnoJoiner 得到别名
AnnoJoiner joiner = ParserCache.getAnnoJoiner(queryWrapper.getDtoClass(), fieldName);
if(joiner != null){
if(V.notEmpty(joiner.getAlias())){
fieldName = joiner.getAlias() + "." + joiner.getColumnName();
}
else{
fieldName = "self." + joiner.getColumnName();
}
}
else{
fieldName = "self." + S.toSnakeCase(fieldName);
}
if(V.notEmpty(orderType)){
orderByList.add(fieldName + ":" + orderType);
}
else{
orderByList.add(fieldName);
}
}
pagination.setOrderBy(S.join(orderByList));
}
}
/**
* 获取mapper实例
* @return
*/
private static DynamicQueryMapper getDynamicQueryMapper(){
return ContextHelper.getBean(DynamicQueryMapper.class);
}
}

View File

@ -16,23 +16,35 @@
package com.diboot.core.binding;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.diboot.core.binding.data.CheckpointType;
import com.diboot.core.binding.data.DataAccessAnnoCache;
import com.diboot.core.binding.data.DataAccessCheckInterface;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.Comparison;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.binding.query.dynamic.DynamicJoinQueryWrapper;
import com.diboot.core.binding.query.dynamic.ExtQueryWrapper;
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.V;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.*;
/**
* QueryWrapper构建器 - EntityDTO -> 注解绑定查询条件 并转换为QueryWrapper对象
* QueryWrapper构建器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/07/27
@ -43,97 +55,128 @@ public class QueryBuilder {
/**
* Entity或者DTO对象转换为QueryWrapper
* @param dto
* @param <T>
* @param <DTO>
* @return
*/
public static <T,DTO> QueryWrapper<T> toQueryWrapper(DTO dto){
QueryWrapper<T> wrapper = new QueryWrapper<>();
return (QueryWrapper<T>) dtoToWrapper(wrapper, dto, null);
public static <DTO> QueryWrapper toQueryWrapper(DTO dto){
return dtoToWrapper(dto, null);
}
/**
* Entity或者DTO对象转换为QueryWrapper
* @param dto
* @param fields 指定参与转换的属性值
* @param <T>
* @param <DTO>
* @return
*/
public static <T,DTO> QueryWrapper<T> toQueryWrapper(DTO dto, Collection<String> fields){
QueryWrapper<T> wrapper = new QueryWrapper<>();
return (QueryWrapper<T>) dtoToWrapper(wrapper, dto, fields);
public static <DTO> QueryWrapper toQueryWrapper(DTO dto, Collection<String> fields){
return dtoToWrapper(dto, fields);
}
/**
* Entity或者DTO对象转换为QueryWrapper
* @param dto
* @param <DTO>
* @return
*/
public static <DTO> ExtQueryWrapper toDynamicJoinQueryWrapper(DTO dto){
return toDynamicJoinQueryWrapper(dto, null);
}
/**
* Entity或者DTO对象转换为QueryWrapper
* @param dto
* @param fields 指定参与转换的属性值
* @param <DTO>
* @return
*/
public static <DTO> ExtQueryWrapper toDynamicJoinQueryWrapper(DTO dto, Collection<String> fields){
QueryWrapper queryWrapper = dtoToWrapper(dto, fields);
if(queryWrapper instanceof DynamicJoinQueryWrapper == false){
return (ExtQueryWrapper)queryWrapper;
}
return (DynamicJoinQueryWrapper)queryWrapper;
}
/**
* Entity或者DTO对象转换为LambdaQueryWrapper
* @param dto
* @param <T>
* @return
*/
public static <T,DTO> LambdaQueryWrapper<T> toLambdaQueryWrapper(DTO dto){
return (LambdaQueryWrapper<T>) toQueryWrapper(dto).lambda();
public static <DTO> LambdaQueryWrapper<DTO> toLambdaQueryWrapper(DTO dto){
return (LambdaQueryWrapper<DTO>) toQueryWrapper(dto).lambda();
}
/**
* Entity或者DTO对象转换为LambdaQueryWrapper
* @param dto
* @param fields 指定参与转换的属性值
* @param <T>
* @return
*/
public static <T,DTO> LambdaQueryWrapper<T> toLambdaQueryWrapper(DTO dto, Collection<String> fields){
return (LambdaQueryWrapper<T>) toQueryWrapper(dto, fields).lambda();
public static <DTO> LambdaQueryWrapper<DTO> toLambdaQueryWrapper(DTO dto, Collection<String> fields){
return (LambdaQueryWrapper<DTO>) toQueryWrapper(dto, fields).lambda();
}
/**
* 转换具体实现
* @param wrapper
* @param dto
* @param <T>
* @return
*/
private static <T,DTO> QueryWrapper<T> dtoToWrapper(QueryWrapper wrapper, DTO dto, Collection<String> fields){
private static <DTO> QueryWrapper<DTO> dtoToWrapper(DTO dto, Collection<String> fields){
QueryWrapper wrapper;
// 转换
List<Field> declaredFields = BeanUtils.extractAllFields(dto.getClass());
for (Field field : declaredFields) {
// 非指定属性非逻辑删除字段跳过
if(fields != null && !fields.contains(field.getName())){
continue;
}
//忽略static以及finaltransient
boolean isStatic = Modifier.isStatic(field.getModifiers());
boolean isFinal = Modifier.isFinal(field.getModifiers());
boolean isTransient = Modifier.isTransient(field.getModifiers());
if(isStatic || isFinal || isTransient){
continue;
}
//忽略注解 @TableField(exist = false) 的字段
TableField tableField = field.getAnnotation(TableField.class);
if(tableField != null && tableField.exist() == false){
continue;
LinkedHashMap<String, Object> fieldValuesMap = extractNotNullValues(dto, fields);
if(V.isEmpty(fieldValuesMap)){
wrapper = new QueryWrapper<>();
// 附加数据访问条件
attachDataAccessCondition(wrapper, dto.getClass());
return wrapper;
}
// 只解析有值的
fields = fieldValuesMap.keySet();
// 是否有join联表查询
boolean hasJoinTable = ParserCache.hasJoinTable(dto, fields);
if(hasJoinTable){
wrapper = new DynamicJoinQueryWrapper<>(dto.getClass(), fields);
}
else{
wrapper = new ExtQueryWrapper<>();
}
// 构建QueryWrapper
for(Map.Entry<String, Object> entry : fieldValuesMap.entrySet()){
Field field = BeanUtils.extractField(dto.getClass(), entry.getKey());
//单表场景忽略注解 @TableField(exist = false) 的字段
if(hasJoinTable == false){
TableField tableField = field.getAnnotation(TableField.class);
if(tableField != null && tableField.exist() == false){
continue;
}
}
//忽略字段
BindQuery query = field.getAnnotation(BindQuery.class);
if(query != null && query.ignore()){ //忽略字段
continue;
}
//打开私有访问 获取值
field.setAccessible(true);
Object value = null;
try {
value = field.get(dto);
}
catch (IllegalAccessException e) {
log.error("通过反射获取属性值出错:" + e);
}
if(value == null){
if(query != null && query.ignore()){
continue;
}
Object value = entry.getValue();
// 对比类型
Comparison comparison = (query != null)? query.comparison() : Comparison.EQ;
Comparison comparison = Comparison.EQ;
// 转换条件
String columnName = getColumnName(field);
if(query != null){
comparison = query.comparison();
AnnoJoiner annoJoiner = ParserCache.getAnnoJoiner(dto.getClass(), entry.getKey());
if(annoJoiner != null && V.notEmpty(annoJoiner.getJoin())){
// 获取注解Table
columnName = annoJoiner.getAlias() + "." + annoJoiner.getColumnName();
}
else if(hasJoinTable){
columnName = "self."+columnName;
}
}
else if(hasJoinTable){
columnName = "self."+columnName;
}
// 构建对象
switch (comparison) {
case EQ:
wrapper.eq(columnName, value);
@ -142,7 +185,7 @@ public class QueryBuilder {
if(value.getClass().isArray()){
Object[] valueArray = (Object[])value;
if(valueArray.length == 1){
wrapper.in(columnName, valueArray[0]);
wrapper.eq(columnName, valueArray[0]);
}
else if(valueArray.length >= 2){
wrapper.in(columnName, valueArray);
@ -189,6 +232,15 @@ public class QueryBuilder {
wrapper.between(columnName, valueArray[0], valueArray[1]);
}
}
else if(value instanceof List){
List valueList = (List)value;
if(valueList.size() == 1){
wrapper.ge(columnName, valueList.get(0));
}
else if(valueList.size() >= 2){
wrapper.between(columnName, valueList.get(0), valueList.get(1));
}
}
// 支持逗号分隔的字符串
else if(value instanceof String && ((String) value).contains(",")){
Object[] valueArray = ((String) value).split(",");
@ -201,9 +253,55 @@ public class QueryBuilder {
default:
}
}
// 附加数据访问条件
attachDataAccessCondition(wrapper, dto.getClass());
return wrapper;
}
// 扩展接口
private static DataAccessCheckInterface dataAccessCheckInstance;
private static boolean dataAccessCheckInstanceChecked = false;
/**
* 附加数据访问权限条件
* @param queryWrapper
* @param dtoClass
* @param <DTO>
*/
public static <DTO> void attachDataAccessCondition(QueryWrapper<DTO> queryWrapper, Class<DTO> dtoClass){
if(dataAccessCheckInstanceChecked == false){
dataAccessCheckInstance = ContextHelper.getBean(DataAccessCheckInterface.class);
dataAccessCheckInstanceChecked = true;
}
if(dataAccessCheckInstance != null && DataAccessAnnoCache.hasDataAccessCheckpoint(dtoClass)){
NormalSegmentList segments = queryWrapper.getExpression().getNormal();
for(CheckpointType type : CheckpointType.values()){
String idCol = DataAccessAnnoCache.getDataPermissionColumn(dtoClass, type);
if(V.isEmpty(idCol)){
continue;
}
List<Long> idValues = dataAccessCheckInstance.getAccessibleIds(type);
if(V.isEmpty(idValues)){
continue;
}
// 联表查询附加别名
if(queryWrapper instanceof DynamicJoinQueryWrapper){
idCol = "self."+idCol;
}
// 检查是否已包含该条件如是则warn并退出
if(checkHasColumn(segments, idCol)){
log.warn("附加数据访问条件未生效,因查询条件已包含列: " + idCol);
continue;
}
if(idValues.size() == 1){
queryWrapper.eq(idCol, idValues.get(0));
}
else{
queryWrapper.in(idCol, idValues);
}
}
}
}
/**
* 获取数据表的列名驼峰转下划线蛇形命名
* <br>
@ -212,10 +310,13 @@ public class QueryBuilder {
* @param field
* @return
*/
private static String getColumnName(Field field){
public static String getColumnName(Field field){
String columnName = null;
if (field.isAnnotationPresent(BindQuery.class)) {
columnName = field.getAnnotation(BindQuery.class).field();
if(V.notEmpty(columnName)){
columnName = S.toSnakeCase(columnName);
}
}
else if (field.isAnnotationPresent(TableField.class)) {
columnName = field.getAnnotation(TableField.class).value();
@ -223,4 +324,81 @@ public class QueryBuilder {
return V.notEmpty(columnName) ? columnName : S.toSnakeCase(field.getName());
}
/**
* 提取非空字段及值
* @param dto
* @param fields
* @param <DTO>
* @return
*/
private static <DTO> LinkedHashMap<String, Object> extractNotNullValues(DTO dto, Collection<String> fields){
LinkedHashMap<String, Object> resultMap = new LinkedHashMap<>();
Class<?> dtoClass = dto.getClass();
// 转换
List<Field> declaredFields = BeanUtils.extractAllFields(dtoClass);
for (Field field : declaredFields) {
// 非指定属性非逻辑删除字段跳过
if (fields != null && !fields.contains(field.getName())) {
//Date 属性放过
if (!V.equals(field.getType().getName(), "java.util.Date")) {
continue;
}
}
//忽略static以及finaltransient
boolean isStatic = Modifier.isStatic(field.getModifiers());
boolean isFinal = Modifier.isFinal(field.getModifiers());
boolean isTransient = Modifier.isTransient(field.getModifiers());
if (isStatic || isFinal || isTransient) {
continue;
}
//打开私有访问 获取值
field.setAccessible(true);
Object value = null;
try {
value = field.get(dto);
if (V.isEmpty(value)) {
String prefix = V.equals("boolean", field.getType().getName()) ? "is" : "get";
Method method = dtoClass.getMethod(prefix + S.capFirst(field.getName()));
value = method.invoke(dto);
}
} catch (IllegalAccessException e) {
log.error("通过反射获取属性值出错:{}", e.getMessage());
} catch (NoSuchMethodException e) {
log.warn("通过反射获取属性方法不存在:{}", e.getMessage());
} catch (InvocationTargetException e) {
log.warn("通过反射执行属性方法出错:{}", e.getMessage());
}
// 忽略逻辑删除字段
if(Cons.FieldName.deleted.name().equals(field.getName())
&& "boolean".equals(field.getType().getName())
&& (Boolean)value == false
){
continue;
}
if (value != null) {
resultMap.put(field.getName(), value);
}
}
return resultMap;
}
/**
* 检查是否包含列
* @param segments
* @param idCol
* @return
*/
public static boolean checkHasColumn(NormalSegmentList segments, String idCol){
if(segments.size() > 0){
Iterator<ISqlSegment> iterable = segments.iterator();
while(iterable.hasNext()){
ISqlSegment segment = iterable.next();
if(segment.getSqlSegment().equalsIgnoreCase(idCol)){
return true;
}
}
}
return false;
}
}

View File

@ -16,14 +16,8 @@
package com.diboot.core.binding;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.binding.annotation.BindDict;
import com.diboot.core.binding.annotation.BindEntity;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.binding.annotation.BindField;
import com.diboot.core.binding.binder.BaseBinder;
import com.diboot.core.binding.binder.EntityBinder;
import com.diboot.core.binding.binder.EntityListBinder;
import com.diboot.core.binding.binder.FieldBinder;
import com.diboot.core.binding.annotation.*;
import com.diboot.core.binding.binder.*;
import com.diboot.core.binding.parser.BindAnnotationGroup;
import com.diboot.core.binding.parser.ConditionManager;
import com.diboot.core.binding.parser.FieldAnnotation;
@ -44,7 +38,7 @@ import java.util.List;
import java.util.Map;
/**
* 绑定管理器
* 关联关系绑定管理器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/7/18
@ -135,6 +129,11 @@ public class RelationsBinder {
doBindingEntityList(voList, anno);
}
}
// 绑定Entity field List
List<FieldAnnotation> fieldListAnnoList = bindAnnotationGroup.getBindFieldListAnnotations();
if(fieldListAnnoList != null){
doBindingFieldList(voList, fieldListAnnoList);
}
}
}
@ -203,7 +202,7 @@ public class RelationsBinder {
}
/***
* 绑定Entity
* 绑定EntityList
* @param voList
* @param fieldAnnotation
* @param <VO>
@ -219,6 +218,34 @@ public class RelationsBinder {
}
}
/***
* 绑定FieldList
* @param voList
* @param fieldListAnnoList
* @param <VO>
*/
private static <VO> void doBindingFieldList(List<VO> voList, List<FieldAnnotation> fieldListAnnoList) {
//多个字段合并查询以减少SQL数
Map<String, List<FieldAnnotation>> clazzToListMap = new HashMap<>();
for(FieldAnnotation anno : fieldListAnnoList){
BindFieldList bindField = (BindFieldList) anno.getAnnotation();
String key = bindField.entity().getName() + ":" + bindField.condition();
List<FieldAnnotation> list = clazzToListMap.computeIfAbsent(key, k -> new ArrayList<>());
list.add(anno);
}
// 解析条件并且执行绑定
for(Map.Entry<String, List<FieldAnnotation>> entry : clazzToListMap.entrySet()){
List<FieldAnnotation> list = entry.getValue();
BindFieldList bindAnnotation = (BindFieldList) list.get(0).getAnnotation();
FieldListBinder binder = buildFieldListBinder(bindAnnotation, voList);
for(FieldAnnotation anno : list){
BindFieldList bindField = (BindFieldList) anno.getAnnotation();
binder.link(bindField.field(), anno.getFieldName());
}
parseConditionsAndBinding(binder, bindAnnotation.condition());
}
}
/***
* 解析条件并且执行绑定
* @param condition
@ -276,6 +303,20 @@ public class RelationsBinder {
return null;
}
/**
* 构建FieldListBinder
* @param annotation
* @param voList
* @return
*/
private static FieldListBinder buildFieldListBinder(Annotation annotation, List voList){
IService service = getService(annotation);
if(service != null){
return new FieldListBinder<>(service, voList);
}
return null;
}
/**
* 通过Entity获取对应的Service实现类
* @param annotation
@ -298,6 +339,10 @@ public class RelationsBinder {
BindEntityList bindAnnotation = (BindEntityList)annotation;
entityClass = bindAnnotation.entity();
}
else if(annotation instanceof BindFieldList){
BindFieldList bindAnnotation = (BindFieldList)annotation;
entityClass = bindAnnotation.entity();
}
else{
log.warn("非预期的注解: "+ annotation.getClass().getSimpleName());
return null;

View File

@ -18,6 +18,7 @@ package com.diboot.core.binding.annotation;
import java.lang.annotation.*;
/**
* 绑定字典注解
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/21

View File

@ -18,7 +18,7 @@ package com.diboot.core.binding.annotation;
import java.lang.annotation.*;
/**
* 绑定Entity 注解定义
* 绑定Entity 注解定义1-1
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/21

View File

@ -18,6 +18,7 @@ package com.diboot.core.binding.annotation;
import java.lang.annotation.*;
/**
* 绑定Entity集合注解1-n
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/21

View File

@ -18,6 +18,7 @@ package com.diboot.core.binding.annotation;
import java.lang.annotation.*;
/**
* 绑定字段 1-1
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/21

View File

@ -0,0 +1,48 @@
/*
* 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.annotation;
import java.lang.annotation.*;
/**
* 绑定字段集合1-n
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/21
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface BindFieldList {
/***
* 绑定的Entity类
* @return
*/
Class entity();
/***
* 绑定字段
* @return
*/
String field();
/***
* JOIN连接条件
* @return
*/
String condition();
}

View File

@ -24,14 +24,12 @@ import com.diboot.core.service.BaseService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.IGetter;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* 关系绑定Binder父类
@ -70,6 +68,18 @@ public abstract class BaseBinder<T> {
protected Class<T> referencedEntityClass;
/***
* 构造方法
* @param serviceInstance
* @param voList
*/
public BaseBinder(IService<T> serviceInstance, List voList){
this.referencedService = serviceInstance;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
}
/**
* join连接条件指定当前VO的取值方法和关联entity的取值方法
* @param annoObjectFkGetter 当前VO的取值方法
@ -210,6 +220,26 @@ public abstract class BaseBinder<T> {
return null;
}
/**
* 从Map中提取ID的值
* @param middleTableResultMap
* @return
*/
protected List extractIdValueFromMap(Map<String, List> middleTableResultMap) {
List entityIdList = new ArrayList();
for(Map.Entry<String, List> entry : middleTableResultMap.entrySet()){
if(V.isEmpty(entry.getValue())){
continue;
}
for(Object id : entry.getValue()){
if(!entityIdList.contains(id)){
entityIdList.add(id);
}
}
}
return entityIdList;
}
/**
* 检查list结果过多打印warn
* @param list
@ -245,5 +275,4 @@ public abstract class BaseBinder<T> {
return value;
}
}

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.ISetter;
@ -47,17 +46,13 @@ public class EntityBinder<T> extends BaseBinder<T> {
*/
protected Class<?> annoObjectFieldClass;
public EntityBinder(){}
/***
* 构造方法
* @param referencedService
* @param voList
*/
public EntityBinder(IService<T> referencedService, List voList){
this.referencedService = referencedService;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
super(referencedService, voList);
}
/***

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;
@ -43,10 +42,7 @@ public class EntityListBinder<T> extends EntityBinder<T> {
* @param voList
*/
public EntityListBinder(IService<T> serviceInstance, List voList){
this.referencedService = serviceInstance;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
super(serviceInstance, voList);
}
@Override
@ -117,24 +113,6 @@ public class EntityListBinder<T> extends EntityBinder<T> {
BeanUtils.bindPropValueOfList(annoObjectField, annoObjectList, annoObjectForeignKey, valueEntityListMap);
}
/**
* 从Map中提取ID的值
* @param middleTableResultMap
* @return
*/
private List extractIdValueFromMap(Map<String, List> middleTableResultMap) {
List entityIdList = new ArrayList();
for(Map.Entry<String, List> entry : middleTableResultMap.entrySet()){
if(V.isEmpty(entry.getValue())){
continue;
}
for(Object id : entry.getValue()){
if(!entityIdList.contains(id)){
entityIdList.add(id);
}
}
}
return entityIdList;
}
}

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;
@ -35,11 +34,11 @@ public class FieldBinder<T> extends BaseBinder<T> {
/**
* VO对象绑定赋值的属性名列表
*/
private List<String> annoObjectSetterPropNameList;
protected List<String> annoObjectSetterPropNameList;
/**
* DO/Entity对象对应的getter取值属性名列表
*/
private List<String> referencedGetterColumnNameList;
protected List<String> referencedGetterColumnNameList;
/***
* 构造方法
@ -47,10 +46,7 @@ public class FieldBinder<T> extends BaseBinder<T> {
* @param voList
*/
public FieldBinder(IService<T> serviceInstance, List voList){
this.referencedService = serviceInstance;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
super(serviceInstance, voList);
}
/***

View File

@ -0,0 +1,138 @@
/*
* 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.binder;
import com.baomidou.mybatisplus.extension.service.IService;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 关联字段绑定
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/19
*/
public class FieldListBinder<T> extends FieldBinder<T> {
private static final Logger log = LoggerFactory.getLogger(FieldListBinder.class);
/***
* 构造方法
* @param serviceInstance
* @param voList
*/
public FieldListBinder(IService<T> serviceInstance, List voList) {
super(serviceInstance, voList);
}
@Override
public void bind() {
if(V.isEmpty(annoObjectList)){
return;
}
if(referencedGetterColumnNameList == null){
log.error("调用错误字段绑定必须调用link()指定字段赋值和取值的setter/getter方法");
return;
}
// 解析默认主键字段名
String referencedEntityPkName = S.toSnakeCase(referencedEntityPrimaryKey);
String annoObjectFkFieldName = S.toLowerCaseCamel(annoObjectForeignKey);
// 提取主键pk列表
List annoObjectForeignKeyList = BeanUtils.collectToList(annoObjectList, annoObjectFkFieldName);
if(V.isEmpty(annoObjectForeignKeyList)){
return;
}
Map<String, List> valueEntityListMap = new HashMap<>();
//@BindFieldList(entity = Role.class, field="name", condition="this.id=user_role.user_id AND user_role.role_id=id")
//List<String> roleNameList;
// 构建查询条件
List<String> selectColumns = new ArrayList<>(referencedGetterColumnNameList.size()+1);
selectColumns.add(referencedEntityPkName);
selectColumns.addAll(referencedGetterColumnNameList);
queryWrapper.select(S.toStringArray(selectColumns));
// 处理中间表
if(middleTable != null){
// 将结果转换成map
Map<String, List> middleTableResultMap = middleTable.executeOneToManyQuery(annoObjectForeignKeyList);
if(V.isEmpty(middleTableResultMap)){
return;
}
// 收集查询结果values集合
List entityIdList = extractIdValueFromMap(middleTableResultMap);
// 构建查询条件
queryWrapper.in(referencedEntityPkName, entityIdList);
// 查询entity列表: List<Role>
List<T> list = getEntityList(queryWrapper);
if(V.isEmpty(list)){
return;
}
// 转换entity列表为Map<ID, Entity>
Map<String, T> entityMap = BeanUtils.convertToStringKeyObjectMap(list, S.toLowerCaseCamel(referencedEntityPrimaryKey));
for(Map.Entry<String, List> entry : middleTableResultMap.entrySet()){
// List<roleId>
List annoObjFKList = entry.getValue();
if(V.isEmpty(annoObjFKList)){
continue;
}
List valueList = new ArrayList();
for(Object obj : annoObjFKList){
T ent = entityMap.get(String.valueOf(obj));
if(ent != null){
valueList.add(ent);
}
}
valueEntityListMap.put(entry.getKey(), valueList);
}
}
else{
queryWrapper.in(referencedEntityPkName, annoObjectForeignKeyList);
// 查询entity列表: List<Role>
List<T> list = getEntityList(queryWrapper);
if(V.isEmpty(list)){
return;
}
for(T entity : list){
String keyValue = BeanUtils.getStringProperty(entity, S.toLowerCaseCamel(referencedEntityPrimaryKey));
List entityList = valueEntityListMap.get(keyValue);
if(entityList == null){
entityList = new ArrayList<>();
valueEntityListMap.put(keyValue, entityList);
}
entityList.add(entity);
}
}
// 遍历list并赋值
for(Object annoObject : annoObjectList){
// 将数子类型转换成字符串以解决类型不一致的问题
String annoObjectId = BeanUtils.getStringProperty(annoObject, annoObjectFkFieldName);
List entityList = valueEntityListMap.get(annoObjectId);
if(entityList != null){
for(int i = 0; i< annoObjectSetterPropNameList.size(); i++){
List valObjList = BeanUtils.collectToList(entityList, S.toLowerCaseCamel(referencedGetterColumnNameList.get(i)));
BeanUtils.setProperty(annoObject, annoObjectSetterPropNameList.get(i), valObjList);
}
}
}
}
}

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

@ -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.data;
/**
* checkpoint类型
* @author Mazc@dibo.ltd
* @version v2.1
* @date 2020/04/24
*/
public enum CheckpointType {
USER(0), // 用户范围
ORG(1), // 组织范围
POSITION(2), // 岗位范围
EXT_OBJ(3); // 扩展对象范围
private int index;
CheckpointType(int index){
this.index = index;
}
public int index(){
return index;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.data;
import com.diboot.core.binding.QueryBuilder;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.Status;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 数据访问权限的注解缓存
* @author Mazc@dibo.ltd
* @version v2.1
* @date 2020/04/24
*/
@Slf4j
public class DataAccessAnnoCache {
/**
* 注解缓存
*/
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;
}
/**
* 获取数据权限的用户类型列名
* @param entityDto
* @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 = {"", "", "", "", ""};
List<Field> fieldList = BeanUtils.extractFields(entityDto, DataAccessCheckpoint.class);
if(V.notEmpty(fieldList)){
for(Field fld : fieldList){
DataAccessCheckpoint checkpoint = fld.getAnnotation(DataAccessCheckpoint.class);
if(V.notEmpty(results[checkpoint.type().index()])){
throw new BusinessException(Status.FAIL_VALIDATION, entityDto.getSimpleName() + "中DataPermissionCheckpoint同类型注解重复");
}
results[checkpoint.type().index()] = QueryBuilder.getColumnName(fld);
}
}
DATA_PERMISSION_ANNO_CACHE.put(key, results);
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.data;
import java.util.List;
/**
* 数据权限校验扩展接口
* @author mazc@dibo.ltd
* @version v2.1
* @date 2020/04/24
*/
public interface DataAccessCheckInterface {
/**
* 可访问的对象ID
*/
List<Long> getAccessibleIds(CheckpointType type);
}

View File

@ -0,0 +1,35 @@
/*
* 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.data;
import java.lang.annotation.*;
/**
* 数据权限检查点 - 添加在entity/dto字段上的注解可以支持自动检查数据权限
* @author mazc@dibo.ltd
* @version v2.1
* @date 2020/04/23
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface DataAccessCheckpoint {
/**
* 数据权限类型
* @return
*/
CheckpointType type();
}

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

@ -15,10 +15,7 @@
*/
package com.diboot.core.binding.parser;
import com.diboot.core.binding.annotation.BindDict;
import com.diboot.core.binding.annotation.BindEntity;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.binding.annotation.BindField;
import com.diboot.core.binding.annotation.*;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
@ -47,6 +44,10 @@ public class BindAnnotationGroup {
* 实体集合关联注解
*/
private List<FieldAnnotation> bindEntityListAnnotations;
/**
* 实体集合关联注解
*/
private List<FieldAnnotation> bindFieldListAnnotations;
/**
* 添加注解
@ -78,6 +79,12 @@ public class BindAnnotationGroup {
}
bindEntityListAnnotations.add(new FieldAnnotation(fieldName, fieldClass, annotation));
}
else if(annotation instanceof BindFieldList){
if(bindFieldListAnnotations == null){
bindFieldListAnnotations = new ArrayList<>();
}
bindFieldListAnnotations.add(new FieldAnnotation(fieldName, fieldClass, annotation));
}
}
public List<FieldAnnotation> getBindDictAnnotations() {
@ -96,7 +103,12 @@ public class BindAnnotationGroup {
return bindEntityListAnnotations;
}
public boolean isNotEmpty() {
return bindDictAnnotations != null || bindFieldAnnotations != null || bindEntityAnnotations != null || bindEntityListAnnotations != null;
public List<FieldAnnotation> getBindFieldListAnnotations() {
return bindFieldListAnnotations;
}
public boolean isNotEmpty() {
return bindDictAnnotations != null || bindFieldAnnotations != null || bindEntityAnnotations != null || bindEntityListAnnotations != null || bindFieldListAnnotations != null;
}
}

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,41 +32,12 @@ 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
*/
private 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
* @param condition
* @param binder
* @throws Exception
*/
@ -240,7 +206,7 @@ public class ConditionManager {
// 绑定左手边连接列
String leftHandColumn = removeLeftAlias(leftColumn);
// this. 开头的vo对象字段
if(isVoColumn(leftColumn)){
if(isCurrentObjColumn(leftColumn)){
// 识别到vo对象的属性 departmentId
annoObjectForeignKey = leftHandColumn;
// 对应中间表的关联字段
@ -258,7 +224,7 @@ public class ConditionManager {
if(leftColumn.startsWith(tableName+".")){
// 绑定右手边连接列
String rightHandColumn = removeLeftAlias(rightColumn);
if(isVoColumn(rightColumn)){
if(isCurrentObjColumn(rightColumn)){
// 识别到vo对象的属性 departmentId
annoObjectForeignKey = rightHandColumn;
// 对应中间表的关联字段
@ -336,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
@ -402,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

@ -123,6 +123,12 @@ public class ConditionParser implements ExpressionVisitor,ItemsListVisitor {
}
expressList.add(isNullExpression);
}
@Override
public void visit(IsBooleanExpression isBooleanExpression) {
}
@Override
public void visit(InExpression inExpression) {
if(!(inExpression.getLeftExpression() instanceof Column)){
@ -130,6 +136,12 @@ public class ConditionParser implements ExpressionVisitor,ItemsListVisitor {
}
expressList.add(inExpression);
}
@Override
public void visit(FullTextSearch fullTextSearch) {
}
@Override
public void visit(Between between) {
if(!(between.getLeftExpression() instanceof Column)){
@ -273,6 +285,10 @@ public class ConditionParser implements ExpressionVisitor,ItemsListVisitor {
public void visit(SimilarToExpression aThis) {
}
@Override
public void visit(ArrayExpression arrayExpression) {
}
@Override
public void visit(BitwiseRightShift aThis) {
}
@ -324,6 +340,10 @@ public class ConditionParser implements ExpressionVisitor,ItemsListVisitor {
@Override
public void visit(Division division) {
}
@Override
public void visit(IntegerDivision integerDivision) {
}
@Override
public void visit(Multiplication multiplication) {
}

View File

@ -15,11 +15,15 @@
*/
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;
import org.apache.ibatis.jdbc.SQL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -100,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;
}
}
/**
@ -117,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;
}
/**
@ -133,34 +170,30 @@ public class MiddleTable {
* @param annoObjectForeignKeyList 注解外键值的列表用于拼接SQL查询
* @return
*/
public String toSQL(List annoObjectForeignKeyList){
private String toSQL(List annoObjectForeignKeyList){
if(V.isEmpty(annoObjectForeignKeyList)){
return null;
}
// 构建SQL
StringBuilder sb = new StringBuilder();
sb.append("SELECT ").append(this.equalsToAnnoObjectFKColumn).append(Cons.SEPARATOR_COMMA)
.append(this.equalsToRefEntityPkColumn).append(" FROM ").append(this.table)
.append(" WHERE ").append(this.equalsToAnnoObjectFKColumn).append(" IN (");
String params = S.repeat("?", ",", annoObjectForeignKeyList.size());
sb.append(params).append(")");
// 添加删除标记
boolean appendDeleteFlag = true;
if(this.additionalConditions != null){
for(String condition : this.additionalConditions){
sb.append(" AND (").append(condition).append(")");
if(S.containsIgnoreCase(condition, "is_" + Cons.FieldName.deleted.name())){
appendDeleteFlag = false;
// 构建SQL
return new SQL(){{
SELECT(equalsToAnnoObjectFKColumn + Cons.SEPARATOR_COMMA + equalsToRefEntityPkColumn);
FROM(table);
WHERE(equalsToAnnoObjectFKColumn + " IN (" + params + ")");
// 添加删除标记
boolean appendDeleteFlag = true;
if(additionalConditions != null){
for(String condition : additionalConditions){
WHERE(condition);
if(S.containsIgnoreCase(condition, Cons.COLUMN_IS_DELETED)){
appendDeleteFlag = false;
}
}
}
}
// 如果需要删除
if(appendDeleteFlag){
if(ParserCache.hasDeletedColumn(this.table)){
sb.append(" AND is_deleted = ").append(BaseConfig.getActiveFlagValue());
// 如果需要删除
if(appendDeleteFlag && ParserCache.hasDeletedColumn(table)){
WHERE(Cons.COLUMN_IS_DELETED + " = " + BaseConfig.getActiveFlagValue());
}
}
return sb.toString();
}}.toString();
}
}

View File

@ -15,33 +15,55 @@
*/
package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.Status;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* VO对象中的绑定注解 缓存管理类
* 对象中的绑定注解 缓存管理类
* @author mazc@dibo.ltd<br>
* @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类-表名的缓存
*/
private static Map<String, String> entityClassTableCacheMap = new ConcurrentHashMap<>();
/**
* entity类小驼峰实例名-entity类
*/
private static Map<String, Class<?>> entityName2EntityClassCacheMap = new ConcurrentHashMap<>();
/**
* dto类-BindQuery注解的缓存
*/
private static Map<String, List<AnnoJoiner>> dtoClassBindQueryCacheMap = new ConcurrentHashMap<>();
/**
* 获取指定class对应的Bind相关注解
@ -82,16 +104,210 @@ public class ParserCache {
return group;
}
/**
* 初始化Table的相关对象信息
*/
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);
entityName2EntityClassCacheMap.put(entityClass.getSimpleName(), entityClass);
}
}
}
}
catch (Exception e){
log.warn("解析mapper异常", e);
}
});
}
}
}
/**
* 是否有is_deleted列
* @return
*/
public static boolean hasDeletedColumn(String middleTable){
if(middleTableHasDeletedCacheMap.containsKey(middleTable)){
return middleTableHasDeletedCacheMap.get(middleTable);
}
boolean hasColumn = SqlExecutor.validateQuery("SELECT is_deleted FROM "+middleTable);
middleTableHasDeletedCacheMap.put(middleTable, hasColumn);
return hasColumn;
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;
}
/**
* 获取entity对应的表名
* @param entityClass
* @return
*/
public static String getEntityTableName(Class<?> entityClass){
String entityClassName = entityClass.getName();
String tableName = entityClassTableCacheMap.get(entityClassName);
if(tableName == null){
TableName tableNameAnno = AnnotationUtils.findAnnotation(entityClass, TableName.class);
if(tableNameAnno != null){
tableName = tableNameAnno.value();
}
else{
tableName = S.toSnakeCase(entityClass.getSimpleName());
}
entityClassTableCacheMap.put(entityClassName, tableName);
}
return tableName;
}
/**
* 根据entity类获取mapper实例
* @return
*/
public static BaseMapper getMapperInstance(Class<?> entityClass){
String tableName = getEntityTableName(entityClass);
TableLinkage linkage = getTableLinkage(tableName);
if(linkage == null){
throw new BusinessException(Status.FAIL_INVALID_PARAM, "未找到 "+entityClass.getName()+" 的Mapper定义");
}
BaseMapper mapper = (BaseMapper) ContextHelper.getBean(linkage.getMapperClass());
return mapper;
}
/**
* 根据类的entity类名获取EntityClass
* @return
*/
public static Class<?> getEntityClassByClassName(String className){
initTableToLinkageCacheMap();
return entityName2EntityClassCacheMap.get(className);
}
/**
* 当前DTO是否有Join绑定
* @param dto dto对象
* @param fieldNameSet 有值属性集合
* @param <DTO>
* @return
*/
public static <DTO> boolean hasJoinTable(DTO dto, Collection<String> fieldNameSet){
List<AnnoJoiner> annoList = getBindQueryAnnos(dto.getClass());
if(V.notEmpty(annoList)){
for(AnnoJoiner anno : annoList){
if(V.notEmpty(anno.getJoin()) && fieldNameSet != null && fieldNameSet.contains(anno.getFieldName())){
return true;
}
}
}
return false;
}
/**
* 获取dto类中定义的BindQuery注解
* @param dtoClass
* @return
*/
public static List<AnnoJoiner> getBindQueryAnnos(Class<?> dtoClass){
String dtoClassName = dtoClass.getName();
if(dtoClassBindQueryCacheMap.containsKey(dtoClassName)){
return dtoClassBindQueryCacheMap.get(dtoClassName);
}
// 初始化
List<AnnoJoiner> annos = null;
List<Field> declaredFields = BeanUtils.extractFields(dtoClass, BindQuery.class);
int index = 1;
Map<String, String> joinOn2Alias = new HashMap<>();
for (Field field : declaredFields) {
BindQuery query = field.getAnnotation(BindQuery.class);
if(query == null || query.ignore()){
continue;
}
if(annos == null){
annos = new ArrayList<>();
}
AnnoJoiner annoJoiner = new AnnoJoiner(field, query);
// 关联对象设置别名
if(V.notEmpty(annoJoiner.getJoin())){
String key = annoJoiner.getJoin() + ":" + annoJoiner.getCondition();
String alias = joinOn2Alias.get(key);
if(alias == null){
alias = "r"+index;
annoJoiner.setAlias(alias);
index++;
joinOn2Alias.put(key, alias);
}
else{
annoJoiner.setAlias(alias);
}
annoJoiner.parse();
}
annos.add(annoJoiner);
}
if(annos == null){
annos = Collections.emptyList();
}
dtoClassBindQueryCacheMap.put(dtoClassName, annos);
return annos;
}
/**
* 获取注解joiner
* @param dtoClass
* @param fieldNames
* @return
*/
public static List<AnnoJoiner> getAnnoJoiners(Class<?> dtoClass, Collection<String> fieldNames) {
List<AnnoJoiner> annoList = getBindQueryAnnos(dtoClass);
// 不过滤 返回全部
if(fieldNames == null){
return annoList;
}
// 过滤
if(V.notEmpty(annoList)){
List<AnnoJoiner> matchedAnnoList = new ArrayList<>();
for(AnnoJoiner anno : annoList){
if(fieldNames.contains(anno.getFieldName())){
matchedAnnoList.add(anno);
}
}
return matchedAnnoList;
}
return Collections.emptyList();
}
/**
* 获取注解joiner
* @param dtoClass
* @param key
* @return
*/
public static AnnoJoiner getAnnoJoiner(Class<?> dtoClass, String key) {
List<AnnoJoiner> annoList = getBindQueryAnnos(dtoClass);
if(V.notEmpty(annoList)){
for(AnnoJoiner anno : annoList){
if(key.equals(anno.getFieldName())){
return anno;
}
}
}
return null;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.diboot.core.config.Cons;
import com.diboot.core.entity.BaseEntity;
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

@ -15,11 +15,12 @@
*/
package com.diboot.core.binding.query;
import javax.lang.model.type.NullType;
import java.lang.annotation.*;
/**
* 绑定管理器
* @author Xieshuang
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/7/18
*/
@ -40,6 +41,18 @@ public @interface BindQuery {
*/
String field() default "";
/***
* 绑定的Entity类
* @return
*/
Class entity() default NullType.class;
/***
* JOIN连接条件支持动态的跨表JOIN查询
* @return
*/
String condition() default "";
/**
* 忽略该字段
* @return

View File

@ -0,0 +1,108 @@
/*
* 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.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.Comparison;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import lombok.Getter;
import lombok.Setter;
import javax.lang.model.type.NullType;
import java.io.Serializable;
import java.lang.reflect.Field;
/**
* BindQuery注解连接器
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/16
*/
@Getter @Setter
public class AnnoJoiner implements Serializable {
private static final long serialVersionUID = 5998965277333389063L;
public AnnoJoiner(Field field, BindQuery query){
this.fieldName = field.getName();
this.comparison = query.comparison();
// 列名
if (V.notEmpty(query.field())) {
this.columnName = S.toSnakeCase(query.field());
}
else if (field.isAnnotationPresent(TableField.class)) {
this.columnName = field.getAnnotation(TableField.class).value();
}
if(V.isEmpty(this.columnName)){
this.columnName = S.toSnakeCase(field.getName());
}
// join 表名
if(!NullType.class.equals(query.entity())){
this.join = ParserCache.getEntityTableName(query.entity());
}
// 条件
if(V.notEmpty(query.condition())){
this.condition = query.condition();
}
}
private Comparison comparison;
private String fieldName;
private String columnName;
private String condition;
private String join;
/**
* 别名
*/
private String alias;
/**
* on条件
*/
private String onSegment;
/**
* 中间表
*/
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

@ -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.query.dynamic;
import com.diboot.core.binding.JoinsBinder;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.vo.Pagination;
import lombok.Getter;
import java.util.Collection;
import java.util.List;
/**
* 动态查询wrapper
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/16
*/
public class DynamicJoinQueryWrapper<DTO,E> extends ExtQueryWrapper<DTO,E> {
public DynamicJoinQueryWrapper(Class<DTO> dtoClass, Collection<String> fields){
this.dtoClass = dtoClass;
this.fields = fields;
}
/**
* DTO类
*/
@Getter
private Class<DTO> dtoClass;
/**
* 字段
*/
private Collection<String> fields;
/**
* dto字段和值
*/
public List<AnnoJoiner> getAnnoJoiners(){
return ParserCache.getAnnoJoiners(this.dtoClass, fields);
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
@Override
public E queryOne(Class<E> entityClazz){
return JoinsBinder.queryOne(this, entityClazz);
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
@Override
public List<E> queryList(Class<E> entityClazz){
return JoinsBinder.queryList(this, entityClazz);
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
@Override
public List<E> queryList(Class<E> entityClazz, Pagination pagination){
return JoinsBinder.queryList(this, entityClazz, pagination);
}
}

View File

@ -0,0 +1,171 @@
/*
* 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.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.diboot.core.binding.QueryBuilder;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import org.apache.ibatis.jdbc.SQL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 动态SQL构建Provider
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/15
*/
public class DynamicSqlProvider {
/**
* 构建动态SQL
* @param ew
* @return
*/
public String buildSql(QueryWrapper ew){
return buildDynamicSql(null, ew, true);
}
/**
* 构建动态SQL
* @param ew
* @return
*/
public String buildSqlForList(QueryWrapper ew){
return buildDynamicSql(null, ew, false);
}
/**
* 构建动态SQL
* @param page 分页参数用于MP分页插件AOP不可删除
* @param ew
* @return
*/
public <DTO> String buildSqlForListWithPage(Page<?> page, QueryWrapper<DTO> ew){
return buildDynamicSql(page, ew, false);
}
/**
* 构建动态SQL
* @param page 分页参数用于MP分页插件AOP不可删除
* @param ew
* @return
*/
private <DTO> String buildDynamicSql(Page<?> page, QueryWrapper<DTO> ew, boolean limit1){
DynamicJoinQueryWrapper wrapper = (DynamicJoinQueryWrapper)ew;
return new SQL() {{
if(V.isEmpty(ew.getSqlSelect())){
SELECT("self.*");
}
else{
SELECT(formatSqlSelect(ew.getSqlSelect()));
}
FROM(wrapper.getEntityTable()+" self");
//提取字段根据查询条件中涉及的表动态join
List<AnnoJoiner> annoJoinerList = wrapper.getAnnoJoiners();
if(V.notEmpty(annoJoinerList)){
Set<String> tempSet = new HashSet<>();
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())){
sb.append(" AND ").append(joiner.getAlias()).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);
}
}
}
tempSet = null;
}
MergeSegments segments = ew.getExpression();
if(segments != null){
String normalSql = segments.getNormal().getSqlSegment();
if(V.notEmpty(normalSql)){
WHERE(formatNormalSql(normalSql));
// 动态为主表添加is_deleted=0
String isDeletedSection = "self."+ Cons.COLUMN_IS_DELETED;
if(QueryBuilder.checkHasColumn(segments.getNormal(), isDeletedSection) == false && ParserCache.hasDeletedColumn(wrapper.getEntityTable())){
WHERE(isDeletedSection+ " = " +BaseConfig.getActiveFlagValue());
}
if(segments.getOrderBy() != null){
String orderBySql = segments.getOrderBy().getSqlSegment();
int beginIndex = S.indexOfIgnoreCase(orderBySql,"ORDER BY ");
if(beginIndex >= 0){
orderBySql = S.substring(orderBySql, beginIndex+"ORDER BY ".length());
ORDER_BY(orderBySql);
}
}
}
}
if(limit1){
LIMIT(1);
}
}}.toString();
}
/**
* 格式化sql select列语句
* @param sqlSelect
* @return
*/
private String formatSqlSelect(String sqlSelect){
String[] columns = S.split(sqlSelect);
List<String> selects = new ArrayList<>(columns.length);
for(String column : columns){
column = S.removeDuplicateBlank(column).trim();
selects.add("self."+S.toSnakeCase(column));
}
return S.join(selects);
}
/**
* 格式化where条件的sql
* @param normalSql
* @return
*/
private String formatNormalSql(String normalSql){
if(normalSql.startsWith("(") && normalSql.endsWith(")")){
return S.substring(normalSql,1,normalSql.length()-1);
}
return normalSql;
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 动态查询wrapper
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/16
*/
public class ExtQueryWrapper<DTO,E> extends QueryWrapper<DTO> {
/**
* 主实体class
*/
@Getter @Setter
private Class<E> mainEntityClass;
/**
* 获取entity表名
* @return
*/
public String getEntityTable(){
return ParserCache.getEntityTableName(getMainEntityClass());
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
public E queryOne(Class<E> entityClazz){
this.mainEntityClass = entityClazz;
BaseService baseService = ContextHelper.getBaseServiceByEntity(this.mainEntityClass);
if(baseService != null){
return (E)baseService.getEntity(this);
}
else{
throw new BusinessException(Status.FAIL_INVALID_PARAM, "单表查询对象无BaseService实现: "+this.mainEntityClass.getSimpleName());
}
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
public List<E> queryList(Class<E> entityClazz){
BaseService baseService = ContextHelper.getBaseServiceByEntity(entityClazz);
if(baseService != null){
return (List<E>)baseService.getEntityList(this);
}
else{
throw new BusinessException(Status.FAIL_INVALID_PARAM, "单表查询对象无BaseService实现: "+entityClazz.getSimpleName());
}
}
/**
* 查询一条数据
* @param entityClazz
* @return
*/
public List<E> queryList(Class<E> entityClazz, Pagination pagination){
BaseService baseService = ContextHelper.getBaseServiceByEntity(entityClazz);
if(baseService != null){
return (List<E>)baseService.getEntityList(this, pagination);
}
else{
throw new BusinessException(Status.FAIL_INVALID_PARAM, "单表查询对象无BaseService实现: "+entityClazz.getSimpleName());
}
}
}

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

@ -93,7 +93,7 @@ public class BaseConfig {
* @return
*/
public static int getPageSize() {
Integer length = PropertiesUtils.getInteger("pagination.default.pageSize");
Integer length = PropertiesUtils.getInteger("system.pagination.pageSize");
if(length != null){
return length;
}
@ -105,6 +105,10 @@ public class BaseConfig {
* @return
*/
public static int getBatchSize() {
Integer length = PropertiesUtils.getInteger("system.batch.size");
if(length != null){
return length;
}
return 1000;
}

View File

@ -38,6 +38,10 @@ public class Cons {
* 排序 - 降序标记
*/
public static final String ORDER_DESC = "DESC";
/**
* 逻辑删除列名
*/
public static final String COLUMN_IS_DELETED = "is_deleted";
/***
* 默认字段名定义
*/
@ -61,7 +65,15 @@ public class Cons {
/**
* 创建时间字段
*/
createTime
createTime,
/**
* 更新时间
*/
updateTime,
/**
* 创建人
*/
createBy
}
}

View File

@ -17,13 +17,14 @@ package com.diboot.core.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.binding.Binder;
import com.diboot.core.binding.QueryBuilder;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.config.Cons;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@ -37,48 +38,46 @@ import java.util.*;
public class BaseController {
private static final Logger log = LoggerFactory.getLogger(BaseController.class);
@Autowired
protected HttpServletRequest request;
/***
* 构建查询QueryWrapper (根据BindQuery注解构建相应的查询条件)
* @param entityOrDto Entity对象或者DTO对象 (属性若无BindQuery注解默认构建为为EQ相等条件)
* @param <T>
* @return
*/
public <T,DTO> QueryWrapper<T> buildQueryWrapper(DTO entityOrDto, HttpServletRequest request) throws Exception{
public <DTO> QueryWrapper<DTO> buildQueryWrapper(DTO entityOrDto) throws Exception{
if(entityOrDto instanceof HttpServletRequest){
throw new Exception("参数错误buildQueryWrapper()参数为Entity/DTO对象");
}
return QueryBuilder.toQueryWrapper(entityOrDto, extractParams(request));
return QueryBuilder.toQueryWrapper(entityOrDto, extractQueryParams());
}
/***
* 构建查询LambdaQueryWrapper (根据BindQuery注解构建相应的查询条件)
* @param entityOrDto Entity对象或者DTO对象 (属性若无BindQuery注解默认构建为为EQ相等条件)
* @param <T>
* @return
*/
public <T,DTO> LambdaQueryWrapper<T> buildLambdaQueryWrapper(DTO entityOrDto, HttpServletRequest request) throws Exception{
public <DTO> LambdaQueryWrapper<DTO> buildLambdaQueryWrapper(DTO entityOrDto) throws Exception{
if(entityOrDto instanceof HttpServletRequest){
throw new Exception("参数错误buildQueryWrapper()参数为Entity/DTO对象");
}
return QueryBuilder.toLambdaQueryWrapper(entityOrDto, extractParams(request));
return QueryBuilder.toLambdaQueryWrapper(entityOrDto, extractQueryParams());
}
/***
* 获取请求参数Map
* @param request
* @return
*/
public Map<String, Object> getParamsMap(HttpServletRequest request) throws Exception{
return getParamsMap(request, null);
public Map<String, Object> getParamsMap() throws Exception{
return getParamsMap(null);
}
/***
* 获取请求参数Map
* @param request
* @return
*/
private Map<String, Object> getParamsMap(HttpServletRequest request, List<String> paramList) throws Exception{
private Map<String, Object> getParamsMap(List<String> paramList) throws Exception{
Map<String, Object> result = new HashMap<>(8);
Enumeration paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()){
@ -111,10 +110,9 @@ public class BaseController {
/***
* 获取请求URI (去除contextPath)
* @param request
* @return
*/
protected static String getRequestMappingURI(HttpServletRequest request){
protected String getRequestMappingURI(){
String contextPath = request.getContextPath();
if(V.notEmpty(contextPath)){
return S.replace(request.getRequestURI(), contextPath, "");
@ -124,11 +122,10 @@ public class BaseController {
/**
* 提取请求参数名集合
* @param request
* @return
*/
private static Set<String> extractParams(HttpServletRequest request){
Map<String, Object> paramValueMap = convertParams2Map(request);
protected Set<String> extractQueryParams(){
Map<String, Object> paramValueMap = convertParams2Map();
if(V.notEmpty(paramValueMap)){
return paramValueMap.keySet();
}
@ -137,10 +134,9 @@ public class BaseController {
/***
* 将请求参数值转换为Map
* @param request
* @return
*/
public static Map<String, Object> convertParams2Map(HttpServletRequest request){
protected Map<String, Object> convertParams2Map(){
Map<String, Object> result = new HashMap<>(8);
if(request == null){
return result;
@ -174,15 +170,14 @@ public class BaseController {
*/
protected <VO> List<VO> convertToVoAndBindRelations(List entityList, Class<VO> voClass) {
// 转换为VO
List<VO> voList = RelationsBinder.convertAndBind(entityList, voClass);
List<VO> voList = Binder.convertAndBindRelations(entityList, voClass);
return voList;
}
/***
* 打印所有参数信息
* @param request
*/
protected static void dumpParams(HttpServletRequest request){
protected void dumpParams(){
Map<String, String[]> params = request.getParameterMap();
if(params != null && !params.isEmpty()){
StringBuilder sb = new StringBuilder();
@ -198,74 +193,67 @@ public class BaseController {
/**
* 从request获取Long参数
* @param request
* @param param
* @return
*/
public Long getLong(HttpServletRequest request, String param){
protected Long getLong(String param){
return S.toLong(request.getParameter(param));
}
/**
* 从request获取Long参数
* @param request
* @param param
* @param defaultValue
* @return
*/
public long getLong(HttpServletRequest request, String param, Long defaultValue){
protected long getLong(String param, Long defaultValue){
return S.toLong(request.getParameter(param), defaultValue);
}
/**
* 从request获取Int参数
* @param request
* @param param
* @return
*/
public Integer getInteger(HttpServletRequest request, String param){
protected Integer getInteger(String param){
return S.toInt(request.getParameter(param));
}
/**
* 从request获取Int参数
* @param request
* @param param
* @param defaultValue
* @return
*/
public int getInt(HttpServletRequest request, String param, Integer defaultValue){
protected int getInt(String param, Integer defaultValue){
return S.toInt(request.getParameter(param), defaultValue);
}
/***
* 从request中获取boolean值
* @param request
* @param param
* @return
*/
public boolean getBoolean(HttpServletRequest request, String param){
protected boolean getBoolean(String param){
return S.toBoolean(request.getParameter(param));
}
/***
* 从request中获取boolean值
* @param request
* @param param
* @param defaultBoolean
* @return
*/
public boolean getBoolean(HttpServletRequest request, String param, boolean defaultBoolean){
protected boolean getBoolean(String param, boolean defaultBoolean){
return S.toBoolean(request.getParameter(param), defaultBoolean);
}
/**
* 从request获取Double参数
* @param request
* @param param
* @return
*/
public Double getDouble(HttpServletRequest request, String param){
protected Double getDouble(String param){
if(V.notEmpty(request.getParameter(param))){
return Double.parseDouble(request.getParameter(param));
}
@ -274,12 +262,11 @@ public class BaseController {
/**
* 从request获取Double参数
* @param request
* @param param
* @param defaultValue
* @return
*/
public Double getDouble(HttpServletRequest request, String param, Double defaultValue){
protected Double getDouble(String param, Double defaultValue){
if(V.notEmpty(request.getParameter(param))){
return Double.parseDouble(request.getParameter(param));
}
@ -288,11 +275,10 @@ public class BaseController {
/**
* 从request获取String参数
* @param request
* @param param
* @return
*/
public String getString(HttpServletRequest request, String param){
protected String getString(String param){
if(V.notEmpty(request.getParameter(param))){
return request.getParameter(param);
}
@ -301,12 +287,11 @@ public class BaseController {
/**
* 从request获取String参数
* @param request
* @param param
* @param defaultValue
* @return
*/
public String getString(HttpServletRequest request, String param, String defaultValue){
protected String getString(String param, String defaultValue){
if(V.notEmpty(request.getParameter(param))){
return request.getParameter(param);
}
@ -315,11 +300,10 @@ public class BaseController {
/**
* 从request获取String[]参数
* @param request
* @param param
* @return
*/
public String[] getStringArray(HttpServletRequest request, String param){
protected String[] getStringArray(String param){
if(request.getParameterValues(param) != null){
return request.getParameterValues(param);
}
@ -328,12 +312,11 @@ public class BaseController {
/***
* 从request里获取String列表
* @param request
* @param param
* @return
*/
public List<String> getStringList(HttpServletRequest request, String param){
String[] strArray = getStringArray(request, param);
protected List<String> getStringList(String param){
String[] strArray = getStringArray(param);
if(V.isEmpty(strArray)){
return null;
}
@ -342,12 +325,11 @@ public class BaseController {
/***
* 从request里获取Long列表
* @param request
* @param param
* @return
*/
public List<Long> getLongList(HttpServletRequest request, String param){
String[] strArray = getStringArray(request, param);
protected List<Long> getLongList(String param){
String[] strArray = getStringArray(param);
if(V.isEmpty(strArray)){
return null;
}

View File

@ -21,6 +21,7 @@ import com.diboot.core.config.Cons;
import com.diboot.core.entity.BaseEntity;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.service.BaseService;
import com.diboot.core.service.DictionaryService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
@ -30,8 +31,8 @@ import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
@ -44,26 +45,27 @@ import java.util.Map;
* @version 2.0
* @date 2019/01/01
*/
public class BaseCrudRestController<E extends BaseEntity, VO extends Serializable> extends BaseController {
public class BaseCrudRestController<E extends BaseEntity> extends BaseController {
private static final Logger log = LoggerFactory.getLogger(BaseCrudRestController.class);
/**
* EntityVO对应的class
*/
private Class<E> entityClass;
private Class<VO> voClasss;
/**
* Service实现类
*/
private BaseService baseService;
@Autowired
protected DictionaryService dictionaryService;
/**
* 查询ViewObject用于子类重写的方法
* @param id
* @param request
* @return
* @throws Exception
*/
protected JsonResult getViewObject(Serializable id, HttpServletRequest request) throws Exception{
protected <VO> JsonResult getViewObject(Serializable id, Class<VO> voClass) throws Exception{
// 检查String类型id
if(id instanceof String && !S.isNumeric((String)id)){
String pk = ContextHelper.getPrimaryKey(getEntityClass());
@ -71,7 +73,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
throw new BusinessException(Status.FAIL_INVALID_PARAM, "参数id类型不匹配");
}
}
VO vo = (VO)getService().getViewObject(id, getVOClass());
VO vo = (VO)getService().getViewObject(id, voClass);
return new JsonResult(vo);
}
@ -83,10 +85,10 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
* @return JsonResult
* @throws Exception
*/
protected JsonResult getViewObjectList(E entity, Pagination pagination, HttpServletRequest request) throws Exception {
QueryWrapper<E> queryWrapper = super.buildQueryWrapper(entity, request);
protected <VO> JsonResult getViewObjectList(E entity, Pagination pagination, Class<VO> voClass) throws Exception {
QueryWrapper<E> queryWrapper = super.buildQueryWrapper(entity);
// 查询当前页的数据
List<VO> voList = getService().getViewObjectList(queryWrapper, pagination, getVOClass());
List<VO> voList = getService().getViewObjectList(queryWrapper, pagination, voClass);
// 返回结果
return JsonResult.OK(voList).bindPagination(pagination);
}
@ -125,7 +127,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
* @return JsonResult
* @throws Exception
*/
protected JsonResult createEntity(E entity, HttpServletRequest request) throws Exception {
protected JsonResult createEntity(E entity) throws Exception {
// 执行创建资源前的操作
String validateResult = this.beforeCreate(entity);
if (validateResult != null) {
@ -152,7 +154,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
* @return JsonResult
* @throws Exception
*/
protected JsonResult updateEntity(Serializable id, E entity, HttpServletRequest request) throws Exception {
protected JsonResult updateEntity(Serializable id, E entity) throws Exception {
// 如果前端没有指定entity.id在此设置以兼容前端不传的情况
if(entity.getId() == null){
String pk = ContextHelper.getPrimaryKey(getEntityClass());
@ -188,7 +190,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
* @return
* @throws Exception
*/
protected JsonResult deleteEntity(Serializable id, HttpServletRequest request) throws Exception {
protected JsonResult deleteEntity(Serializable id) throws Exception {
if (id == null) {
return new JsonResult(Status.FAIL_INVALID_PARAM, "请选择需要删除的条目!");
}
@ -218,7 +220,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
* @return
* @throws Exception
*/
protected JsonResult batchDeleteEntities(Collection<? extends Serializable> ids, HttpServletRequest request) throws Exception {
protected JsonResult batchDeleteEntities(Collection<? extends Serializable> ids) throws Exception {
if (V.isEmpty(ids)) {
return new JsonResult(Status.FAIL_INVALID_PARAM, "请选择需要删除的条目!");
}
@ -355,17 +357,4 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
return this.entityClass;
}
/**
* 获取VO的class
* @return
*/
protected Class<VO> getVOClass(){
if(this.voClasss == null){
this.voClasss = BeanUtils.getGenericityClass(this, 1);
if(this.voClasss == null) {
log.warn("无法从 {} 类定义中获取泛型类voClasss", this.getClass().getName());
}
}
return this.voClasss;
}
}

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,23 +42,24 @@ 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
@JSONField(serialize = false)
@TableField("is_deleted")
@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

@ -23,11 +23,12 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
* 附带extdata扩展字段的Entity父类
* 附带extdata扩展字段的Entity父类 已废弃
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
*/
@Deprecated
public abstract class BaseExtEntity extends BaseEntity {
private static final long serialVersionUID = 10204L;

View File

@ -0,0 +1,60 @@
/*
* 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.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.diboot.core.binding.query.dynamic.DynamicSqlProvider;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import java.util.List;
import java.util.Map;
/**
* 通用联表查询Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
*/
@Mapper
public interface DynamicQueryMapper {
/**
* 动态SQL查询
* @return
*/
@SelectProvider(type= DynamicSqlProvider.class, method="buildSql")
Map<String, Object> query(@Param(Constants.WRAPPER) QueryWrapper ew);
/**
* 动态SQL查询
* @return
*/
@SelectProvider(type= DynamicSqlProvider.class, method="buildSqlForList")
List<Map<String, Object>> queryForList(@Param(Constants.WRAPPER) QueryWrapper ew);
/**
* 动态SQL查询
* @param page
* @return
*/
@SelectProvider(type= DynamicSqlProvider.class, method="buildSqlForListWithPage")
IPage<Map<String, Object>> queryForListWithPage(Page<?> page, @Param(Constants.WRAPPER) QueryWrapper ew);
}

View File

@ -17,6 +17,7 @@ package com.diboot.core.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.diboot.core.binding.binder.EntityBinder;
import com.diboot.core.binding.binder.EntityListBinder;
import com.diboot.core.binding.binder.FieldBinder;
@ -75,6 +76,17 @@ public interface BaseService<T> {
*/
<RE, R> boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
/**
* 创建或更新n-n关联
* 在主对象的service中调用不依赖中间表service实现中间表操作
* @param driverIdGetter 驱动对象getter
* @param driverId 驱动对象ID
* @param followerIdGetter 从动对象getter
* @param followerIdList 从动对象id集合
* @return
*/
<R> boolean createOrUpdateN2NRelations(SFunction<R, ?> driverIdGetter, Object driverId, SFunction<R, ?> followerIdGetter, List<? extends Serializable> followerIdList);
/**
* 更新Entity实体
* @param entity
@ -182,6 +194,15 @@ public interface BaseService<T> {
*/
List<T> getEntityList(Wrapper queryWrapper, Pagination pagination);
/**
* 获取指定条件的Entity ID集合
* @param queryWrapper
* @param getterFn
* @return
* @throws Exception
*/
<FT> List<FT> getValuesOfField(Wrapper queryWrapper, SFunction<T, ?> getterFn);
/**
* 获取指定条件的Entity集合
* @param ids
@ -200,7 +221,7 @@ public interface BaseService<T> {
/**
* 获取符合条件的一个Entity实体
* @param queryWrapper 主键
* @param queryWrapper
* @return entity
*/
T getSingleEntity(Wrapper queryWrapper);

View File

@ -17,21 +17,29 @@ package com.diboot.core.service.impl;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.binding.Binder;
import com.diboot.core.binding.binder.EntityBinder;
import com.diboot.core.binding.binder.EntityListBinder;
import com.diboot.core.binding.binder.FieldBinder;
import com.diboot.core.binding.query.dynamic.DynamicJoinQueryWrapper;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.*;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
@ -70,10 +78,23 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
warning("createEntity", "参数entity为null");
return false;
}
return super.save(entity);
return save(entity);
}
@Override
public boolean save(T entity) {
beforeCreateEntity(entity);
return super.save(entity);
}
/**
* 用于创建之前的自动填充等场景调用
*/
protected void beforeCreateEntity(T entity){
}
@Override
@Transactional(rollbackFor = Exception.class)
public <RE, R> boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter) {
boolean success = createEntity(entity);
if(!success){
@ -83,13 +104,7 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
if(V.isEmpty(relatedEntities)){
return success;
}
Class relatedEntityClass = BeanUtils.getTargetClass(relatedEntities.get(0));
// 获取关联对象对应的Service
BaseService relatedEntityService = ContextHelper.getBaseServiceByEntity(relatedEntityClass);
if(relatedEntityService == null){
log.error("未能识别到Entity: {} 的Service实现请检查", relatedEntityClass.getName());
return false;
}
Class relatedEntityClass = relatedEntities.get(0).getClass();
// 获取主键
Object pkValue = getPrimaryKeyValue(entity);
String attributeName = BeanUtils.convertToFieldName(relatedEntitySetter);
@ -97,7 +112,20 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
relatedEntities.stream().forEach(relatedEntity->{
BeanUtils.setProperty(relatedEntity, attributeName, pkValue);
});
return relatedEntityService.createEntities(relatedEntities);
// 获取关联对象对应的Service
BaseService relatedEntityService = ContextHelper.getBaseServiceByEntity(relatedEntityClass);
if(relatedEntityService != null){
return relatedEntityService.createEntities(relatedEntities);
}
else{
// 查找mapper
BaseMapper mapper = ContextHelper.getBaseMapperByEntity(entity.getClass());
// 新增关联无service只能循环插入
for(RE relation : relatedEntities){
mapper.insert(relation);
}
return true;
}
}
@Override
@ -114,7 +142,26 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
else{
// 批量插入
return super.saveBatch(entityList, BaseConfig.getBatchSize());
return saveBatch(entityList, BaseConfig.getBatchSize());
}
}
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize){
// 批量插入
beforeCreateEntities(entityList);
return super.saveBatch(entityList, batchSize);
}
/**
* 用于创建之前的自动填充等场景调用
*/
protected void beforeCreateEntities(Collection<T> entityList){
if(V.isEmpty(entityList)){
return;
}
for(T entity : entityList){
beforeCreateEntity(entity);
}
}
@ -137,7 +184,11 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateEntities(Collection<T> entityList) {
if(V.isEmpty(entityList)){
return false;
}
boolean success = super.updateBatchById(entityList);
return success;
}
@ -159,6 +210,67 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return super.saveOrUpdateBatch(entityList, BaseConfig.getBatchSize());
}
/**
* 更新n-n关联
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public <R> boolean createOrUpdateN2NRelations(SFunction<R, ?> driverIdGetter, Object driverId,
SFunction<R, ?> followerIdGetter, List<? extends Serializable> followerIdList)
{
if(driverId == null){
throw new BusinessException(Status.FAIL_INVALID_PARAM, "主动ID值不能为空");
}
// 从getter中获取class和fieldName
com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda lambda = LambdaUtils.resolve(driverIdGetter);
Class<R> middleTableClass = (Class<R>) lambda.getImplClass();
// 获取主动从动字段名
String driverFieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
String followerFieldName = convertGetterToFieldName(followerIdGetter);
List<R> n2nRelations = null;
if(V.notEmpty(followerIdList)){
n2nRelations = new ArrayList<>(followerIdList.size());
try{
for(Serializable followerId : followerIdList){
R relation = middleTableClass.newInstance();
BeanUtils.setProperty(relation, driverFieldName, driverId);
BeanUtils.setProperty(relation, followerFieldName, followerId);
n2nRelations.add(relation);
}
}
catch (Exception e){
throw new BusinessException(Status.FAIL_EXCEPTION, e);
}
}
// 删除已有关联
LambdaQueryWrapper<R> queryWrapper = new QueryWrapper<R>().lambda()
.eq(driverIdGetter, driverId);
// 查找service
BaseService baseService = ContextHelper.getBaseServiceByEntity(middleTableClass);
if(baseService != null){
// 条件为空不删除
baseService.deleteEntities(queryWrapper);
// 添加
if(V.notEmpty(n2nRelations)){
baseService.createEntities(n2nRelations);
}
}
else{
// 查找mapper
BaseMapper mapper = ContextHelper.getBaseMapperByEntity(middleTableClass);
// 条件为空不删除
mapper.delete(queryWrapper);
// 新增关联无service只能循环插入
if(V.notEmpty(n2nRelations)){
for(R relation : n2nRelations){
mapper.insert(relation);
}
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public <RE,R> boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE,R> relatedEntitySetter) {
@ -260,7 +372,11 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteEntities(Collection<? extends Serializable> entityIds) {
if(V.isEmpty(entityIds)){
return false;
}
return super.removeByIds(entityIds);
}
@ -276,6 +392,11 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
@Override
public List<T> getEntityList(Wrapper queryWrapper, Pagination pagination) {
// 如果是动态join则调用JoinsBinder
if(queryWrapper instanceof DynamicJoinQueryWrapper){
return Binder.joinQueryList((DynamicJoinQueryWrapper)queryWrapper, entityClass, pagination);
}
// 否则调用MP默认实现
if(pagination != null){
IPage<T> page = convertToIPage(queryWrapper, pagination);
page = super.page(page, queryWrapper);
@ -297,9 +418,58 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
}
/**
* 获取指定条件的Entity ID集合
* @param queryWrapper
* @param getterFn
* @return
* @throws Exception
*/
@Override
public <FT> List<FT> getValuesOfField(Wrapper queryWrapper, SFunction<T, ?> getterFn){
String fieldName = convertGetterToFieldName(getterFn);
String columnName = S.toSnakeCase(fieldName);
// 优化SQL只查询当前字段
if(queryWrapper instanceof QueryWrapper){
if(V.isEmpty(queryWrapper.getSqlSelect())){
((QueryWrapper)queryWrapper).select(columnName);
}
}
else if(queryWrapper instanceof LambdaQueryWrapper){
if(V.isEmpty(queryWrapper.getSqlSelect())){
((LambdaQueryWrapper) queryWrapper).select(getterFn);
}
}
List<Map<String, Object>> mapList = getMapList(queryWrapper);
if(V.isEmpty(mapList)){
return Collections.emptyList();
}
String columnNameUC = V.notEmpty(columnName)? columnName.toUpperCase() : null;
List<FT> fldValues = new ArrayList<>(mapList.size());
for(Map<String, Object> map : mapList){
if(V.isEmpty(map)){
continue;
}
if(map.containsKey(columnName)){
FT value = (FT) map.get(columnName);
if(!fldValues.contains(value)){
fldValues.add(value);
}
}
else if(columnNameUC != null && map.containsKey(columnNameUC)){
FT value = (FT) map.get(columnNameUC);
if(!fldValues.contains(value)){
fldValues.add(value);
}
}
}
return fldValues;
}
@Override
public List<T> getEntityListLimit(Wrapper queryWrapper, int limitCount) {
IPage<T> page = new Page<>(1, limitCount);
Page<T> page = new Page<>(1, limitCount);
page.setSearchCount(false);
page = super.page(page, queryWrapper);
return page.getRecords();
}
@ -343,7 +513,7 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
@Override
public List<Map<String, Object>> getMapList(Wrapper queryWrapper, Pagination pagination) {
if(pagination != null){
IPage<T> page = convertToIPage(queryWrapper, pagination);
IPage page = convertToIPage(queryWrapper, pagination);
IPage<Map<String, Object>> resultPage = super.pageMaps(page, queryWrapper);
// 如果重新执行了count进行查询则更新pagination中的总数
if(page.isSearchCount()){
@ -380,6 +550,10 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
String[] keyValueArray = sqlSelect.split(Cons.SEPARATOR_COMMA);
List<KeyValue> keyValueList = new ArrayList<>(mapList.size());
for(Map<String, Object> map : mapList){
// 如果key和value的的值都为null的时候map也为空则不处理此项
if (V.isEmpty(map)) {
continue;
}
String key = keyValueArray[0], value = keyValueArray[1], ext = null;
// 兼容oracle大写
if(map.containsKey(key) == false && map.containsKey(key.toUpperCase())){
@ -435,18 +609,15 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
if(entity == null){
return null;
}
List<T> enityList = new ArrayList<>();
enityList.add(entity);
// 绑定
List<VO> voList = RelationsBinder.convertAndBind(enityList, voClass);
return voList.get(0);
return Binder.convertAndBindRelations(entity, voClass);
}
@Override
public <VO> List<VO> getViewObjectList(Wrapper queryWrapper, Pagination pagination, Class<VO> voClass) {
List<T> entityList = getEntityList(queryWrapper, pagination);
// 自动转换为VO并绑定关联对象
List<VO> voList = RelationsBinder.convertAndBind(entityList, voClass);
List<VO> voList = Binder.convertAndBindRelations(entityList, voClass);
return voList;
}
@ -463,17 +634,24 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
// 如果是默认id排序
if(pagination.isDefaultOrderBy()){
// 优化排序
Class entityClass = BeanUtils.getGenericityClass(this, 1);
if(entityClass != null){
String pk = ContextHelper.getPrimaryKey(entityClass);
// 主键非有序id字段需要清空默认排序以免报错
if(!Cons.FieldName.id.name().equals(pk)){
log.warn("{} 的主键非有序id无法自动设置排序字段请自行指定", entityClass.getName());
pagination.clearDefaultOrder();
}
String pk = getPrimaryKeyField();
// 主键非有序id字段需要清空默认排序以免报错
if(!Cons.FieldName.id.name().equals(pk)){
// log.warn("{} 的主键非有序id无法自动设置排序字段请自行指定", entityClass.getName());
pagination.clearDefaultOrder();
//设置时间排序
pagination.setDefaultCreateTimeOrderBy();
}
}
return (Page<T>)pagination.toIPage();
return (Page<T>)pagination.toPage();
}
/**
* 获取当前主键字段名
* @return
*/
private String getPrimaryKeyField(){
return ContextHelper.getPrimaryKey(entityClass);
}
/**
@ -486,13 +664,25 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return BeanUtils.getProperty(entity, pk);
}
/**
* 转换SFunction为属性名
* @param getterFn
* @param <T>
* @return
*/
private <T> String convertGetterToFieldName(SFunction<T, ?> getterFn) {
com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda lambda = LambdaUtils.resolve(getterFn);
String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
return fieldName;
}
/***
* 打印警告信息
* @param method
* @param message
*/
private void warning(String method, String message){
log.warn(this.getClass().getName() + "."+ method +" 调用错误: "+message+", 请检查!");
log.warn(this.getClass().getSimpleName() + ".{} 调用错误: {}, 请检查!", method, message);
}
}

View File

@ -16,11 +16,13 @@
package com.diboot.core.util;
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;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Status;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
@ -30,6 +32,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.util.ReflectionUtils;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ -69,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;
}
@ -119,7 +124,8 @@ public class BeanUtils {
}
}
catch (Exception e){
log.warn("对象转换异常, class: {}, error: {}", clazz.getName(), e.getMessage());
log.error("对象转换异常, class: {}, error: {}", clazz.getName(), e);
return Collections.emptyList();
}
return resultList;
}
@ -156,8 +162,14 @@ public class BeanUtils {
* @return
*/
public static Object getProperty(Object obj, String field){
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj);
return wrapper.getPropertyValue(field);
try {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj);
return wrapper.getPropertyValue(field);
}
catch (Exception e) {
log.warn("获取对象属性值出错返回null", e);
}
return null;
}
/***
@ -488,7 +500,7 @@ public class BeanUtils {
try{
for(E object : objectList){
Object fieldValue = getProperty(object, getterPropName);
if(!fieldValueList.contains(fieldValue)){
if(fieldValue != null && !fieldValueList.contains(fieldValue)){
fieldValueList.add(fieldValue);
}
}
@ -576,18 +588,7 @@ public class BeanUtils {
*/
public static <T> String convertToFieldName(IGetter<T> fn) {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
String prefix = null;
if(methodName.startsWith("get")){
prefix = "get";
}
else if(methodName.startsWith("is")){
prefix = "is";
}
if(prefix == null){
log.warn("无效的getter方法: "+methodName);
}
return S.uncapFirst(S.substringAfter(methodName, prefix));
return PropertyNamer.methodToProperty(lambda.getImplMethodName());
}
/***
@ -597,11 +598,7 @@ public class BeanUtils {
*/
public static <T,R> String convertToFieldName(ISetter<T,R> fn) {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
if(!methodName.startsWith("set")){
log.warn("无效的setter方法: "+methodName);
}
return S.uncapFirst(S.substringAfter(methodName, "set"));
return PropertyNamer.methodToProperty(lambda.getImplMethodName());
}
/**
@ -627,6 +624,30 @@ public class BeanUtils {
return fieldList;
}
/**
* 获取类所有属性包含父类中属性
* @param clazz
* @return
*/
public static List<Field> extractFields(Class<?> clazz, Class<? extends Annotation> annotation){
List<Field> fieldList = new ArrayList<>();
Set<String> fieldNameSet = new HashSet<>();
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
if(V.notEmpty(fields)){ //被重写属性以子类override的为准
Arrays.stream(fields).forEach((field)->{
if(!fieldNameSet.contains(field.getName()) && field.getAnnotation(annotation) != null){
fieldList.add(field);
fieldNameSet.add(field.getName());
}
});
}
clazz = clazz.getSuperclass();
}
return fieldList;
}
/**
* 获取类的指定属性包含父类中属性
* @param clazz

View File

@ -18,8 +18,10 @@ package com.diboot.core.util;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.config.Cons;
import com.diboot.core.service.BaseService;
import org.apache.ibatis.session.SqlSessionFactory;
@ -59,11 +61,11 @@ public class ContextHelper implements ApplicationContextAware {
/**
* Entity-对应的Service缓存
*/
private static Map<String, IService> ENTITY_SERVICE_CACHE = null;
private static Map<String, IService> ENTITY_SERVICE_CACHE = new ConcurrentHashMap<>();
/**
* Entity-对应的BaseService缓存
*/
private static Map<String, BaseService> ENTITY_BASE_SERVICE_CACHE = null;
private static Map<String, BaseService> ENTITY_BASE_SERVICE_CACHE = new ConcurrentHashMap<>();
/**
* 存储主键字段非id的Entity
*/
@ -75,9 +77,10 @@ public class ContextHelper implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(APPLICATION_CONTEXT == null){
APPLICATION_CONTEXT = applicationContext;
}
APPLICATION_CONTEXT = applicationContext;
ENTITY_SERVICE_CACHE.clear();
ENTITY_BASE_SERVICE_CACHE.clear();
PK_NID_ENTITY_CACHE.clear();
}
/***
@ -85,7 +88,10 @@ public class ContextHelper implements ApplicationContextAware {
*/
public static ApplicationContext getApplicationContext() {
if (APPLICATION_CONTEXT == null){
return ContextLoader.getCurrentWebApplicationContext();
APPLICATION_CONTEXT = ContextLoader.getCurrentWebApplicationContext();
}
if(APPLICATION_CONTEXT == null){
log.warn("无法获取ApplicationContext请在Spring初始化之后调用!");
}
return APPLICATION_CONTEXT;
}
@ -105,17 +111,22 @@ public class ContextHelper implements ApplicationContextAware {
* @return
*/
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
try{
return getApplicationContext().getBean(clazz);
}
catch (Exception e){
log.debug("无法找到 bean: {}", clazz.getSimpleName());
return null;
}
}
/***
* 获取指定类型的全部实例
* 获取指定类型的全部实现类
* @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;
@ -141,7 +152,7 @@ public class ContextHelper implements ApplicationContextAware {
}
/**
* 根据Entity获取对应的Service (已废弃请调用getIServiceByEntity)
* 根据Entity获取对应的Service (已废弃请调用getBaseServiceByEntity)
* @param entity
* @return
*/
@ -157,8 +168,7 @@ public class ContextHelper implements ApplicationContextAware {
*/
@Deprecated
public static IService getIServiceByEntity(Class entity){
if(ENTITY_SERVICE_CACHE == null){
ENTITY_SERVICE_CACHE = new ConcurrentHashMap<>();
if(ENTITY_SERVICE_CACHE.isEmpty()){
Map<String, IService> serviceMap = getApplicationContext().getBeansOfType(IService.class);
if(V.notEmpty(serviceMap)){
for(Map.Entry<String, IService> entry : serviceMap.entrySet()){
@ -182,8 +192,7 @@ public class ContextHelper implements ApplicationContextAware {
* @return
*/
public static BaseService getBaseServiceByEntity(Class entity){
if(ENTITY_BASE_SERVICE_CACHE == null){
ENTITY_BASE_SERVICE_CACHE = new ConcurrentHashMap<>();
if(ENTITY_BASE_SERVICE_CACHE.isEmpty()){
Map<String, BaseService> serviceMap = getApplicationContext().getBeansOfType(BaseService.class);
if(V.notEmpty(serviceMap)){
for(Map.Entry<String, BaseService> entry : serviceMap.entrySet()){
@ -196,11 +205,20 @@ public class ContextHelper implements ApplicationContextAware {
}
BaseService baseService = ENTITY_BASE_SERVICE_CACHE.get(entity.getName());
if(baseService == null){
log.error("未能识别到Entity: "+entity.getName()+" 的Service实现");
log.info("未能识别到Entity: "+entity.getName()+" 的Service实现");
}
return baseService;
}
/**
* 根据Entity获取对应的BaseMapper实现
* @param entityClass
* @return
*/
public static BaseMapper getBaseMapperByEntity(Class entityClass){
return ParserCache.getMapperInstance(entityClass);
}
/**
* 获取Entity主键
* @return
@ -235,6 +253,9 @@ public class ContextHelper implements ApplicationContextAware {
public static String getJdbcUrl() {
Environment environment = getApplicationContext().getEnvironment();
String jdbcUrl = environment.getProperty("spring.datasource.url");
if(jdbcUrl == null){
jdbcUrl = environment.getProperty("spring.datasource.druid.url");
}
if(jdbcUrl == null){
String master = environment.getProperty("spring.datasource.dynamic.primary");
jdbcUrl = environment.getProperty("spring.datasource.dynamic.datasource."+master+".url");
@ -265,7 +286,7 @@ public class ContextHelper implements ApplicationContextAware {
}
}
if(DATABASE_TYPE == null){
log.warn("无法识别数据库类型,请检查配置!");
log.warn("无法识别数据库类型,请检查数据源配置:spring.datasource.url等");
}
return DATABASE_TYPE;
}

View File

@ -141,6 +141,18 @@ public class D extends DateUtils{
return sdf.format(date);
}
/**
* 获取日期的下一天
* @param date 基准日期
* @return yyyy-MM-dd
*/
public static Date nextDay(Date date){
if(date == null){
return null;
}
return addDays(date, 1);
}
/***
* 获取格式化的日期时间
* @param date

View File

@ -27,11 +27,6 @@ import org.springframework.core.env.Environment;
@Slf4j
public class PropertiesUtils {
/**
* Spring配置环境变量
*/
private static Environment environment;
/***
* 读取配置项的值
* @param key
@ -39,11 +34,9 @@ public class PropertiesUtils {
*/
public static String get(String key){
// 获取配置值
Environment environment = ContextHelper.getApplicationContext().getEnvironment();
if(environment == null){
environment = ContextHelper.getApplicationContext().getEnvironment();
}
if(environment == null){
log.warn("无法获取上下文Environment !");
log.warn("无法获取上下文Environment请在Spring初始化之后调用!");
return null;
}
String value = environment.getProperty(key);

View File

@ -177,19 +177,20 @@ public class S extends StringUtils{
}
}
// 包含_
String result = null;
StringBuilder sb = null;
String[] words = snakeCaseStr.split(Cons.SEPARATOR_UNDERSCORE);
for(String word : words){
if(V.notEmpty(word)){
if(result == null){
result = word.toLowerCase();
}
else{
result += word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
}
if(V.isEmpty(word)){
continue;
}
if(sb == null){
sb = new StringBuilder(word.toLowerCase());
}
else{
sb.append(word.substring(0, 1).toUpperCase()).append(word.substring(1).toLowerCase());
}
}
return result;
return sb != null? sb.toString() : null;
}
/***

View File

@ -53,7 +53,7 @@ public class SqlExecutor {
try(SqlSession session = sqlSessionFactory.openSession(); Connection conn = session.getConnection(); PreparedStatement stmt = conn.prepareStatement(sqlStatement)){
ResultSet rs = stmt.executeQuery();
rs.close();
log.trace("执行验证SQL:{} 成功", sqlStatement);
log.debug("==> {}", sqlStatement);
return true;
}
catch(Exception e){
@ -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

@ -39,7 +39,7 @@ public class V {
/**
* hibernate注解验证
*/
private static Validator VALIDATOR = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
private static Validator VALIDATOR = null;
/***
* 对象是否为空
@ -319,7 +319,7 @@ public class V {
}
}
else{
//TODO 无法识别的格式
// 无法识别的格式
}
}
// 返回校验不通过的结果
@ -376,6 +376,23 @@ public class V {
}
return true;
}
else if(source instanceof Map){
Map sourceMap = (Map)source, targetMap = (Map)target;
if(V.isEmpty(sourceMap) && V.isEmpty(targetMap)){
return true;
}
if(sourceMap.size() != targetMap.size()){
return false;
}
for(Object key : sourceMap.keySet()){
Object value = sourceMap.get(key);
Object targetValue = targetMap.get(key);
if(notEquals(value, targetValue)){
return false;
}
}
return true;
}
else{
log.warn("暂未实现类型 "+ source.getClass().getSimpleName() + "-"+ target.getClass().getSimpleName() + " 的比对!");
return false;
@ -433,6 +450,9 @@ public class V {
* @param obj
*/
public static <T> String validateBean(T obj) {
if(VALIDATOR == null){
VALIDATOR = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
}
// 校验
Set<ConstraintViolation<T>> errors = VALIDATOR.validate(obj);
if(errors == null || errors.size() == 0){
@ -445,4 +465,23 @@ public class V {
return S.join(allErrors);
}
/**
* 检查当前sql中是否包含某列的条件
* @param normalSql
* @param column
* @return
*/
public static boolean checkHasColumn(String normalSql, String column){
normalSql = S.removeDuplicateBlank(normalSql);
int index = S.indexOfIgnoreCase(normalSql, column);
while(index >= 0){
normalSql = S.substring(normalSql, index);
}
if(S.containsIgnoreCase(normalSql, column)){
log.warn("注意:附加数据访问条件失效,因查询条件已包含列: " + column);
return true;
}
return false;
}
}

View File

@ -16,7 +16,6 @@
package com.diboot.core.vo;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.diboot.core.config.BaseConfig;
@ -61,6 +60,7 @@ public class Pagination implements Serializable {
* 默认排序
*/
private static final String DEFAULT_ORDER_BY = Cons.FieldName.id.name()+":"+Cons.ORDER_DESC;
/**
* 排序
*/
@ -119,7 +119,7 @@ public class Pagination implements Serializable {
* @param <T>
* @return
*/
public <T> IPage<T> toIPage(){
public <T> Page<T> toPage(){
List<OrderItem> orderItemList = null;
// 解析排序
if(V.notEmpty(this.orderBy)){
@ -142,14 +142,23 @@ public class Pagination implements Serializable {
}
}
}
IPage<T> page = new Page<T>()
Page<T> page = new Page<T>()
.setCurrent(getPageIndex())
.setSize(getPageSize())
// 如果前端传递过来了缓存的总数则本次不再count统计
.setTotal(getTotalCount() > 0? -1 : getTotalCount());
if(orderItemList != null){
((Page<T>) page).addOrder(orderItemList);
page.addOrder(orderItemList);
}
return page;
}
/**
* 当id不是主键的时候默认使用创建时间排序
*
* @return
*/
public String setDefaultCreateTimeOrderBy() {
return this.orderBy = Cons.FieldName.createTime.name()+":"+Cons.ORDER_DESC;
}
}

View File

@ -16,7 +16,7 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.binding.Binder;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
@ -54,9 +54,9 @@ public class TestEntityBinder {
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(queryWrapper);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<EntityBinderVO> voList = RelationsBinder.convertAndBind(userList, EntityBinderVO.class);
List<EntityBinderVO> voList = Binder.convertAndBindRelations(userList, EntityBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(EntityBinderVO vo : voList){
@ -70,7 +70,7 @@ public class TestEntityBinder {
}
// 单个entity接口测试
EntityBinderVO singleVO = BeanUtils.convert(userList.get(0), EntityBinderVO.class);
RelationsBinder.bind(singleVO);
Binder.bindRelations(singleVO);
// 验证直接关联和通过中间表间接关联的绑定
Assert.assertEquals(singleVO.getDepartmentId(), singleVO.getDepartment().getId());
Assert.assertNotNull(singleVO.getDepartment().getOrgId());

View File

@ -16,7 +16,7 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.binding.Binder;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.service.DictionaryService;
import com.diboot.core.util.JSON;
@ -71,7 +71,7 @@ public class TestEntityListBinder {
queryWrapper.eq(Department::getId, 10001L);
List<Department> entityList = departmentService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListSimpleBinderVO> voList = RelationsBinder.convertAndBind(entityList, EntityListSimpleBinderVO.class);
List<EntityListSimpleBinderVO> voList = Binder.convertAndBindRelations(entityList, EntityListSimpleBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(EntityListSimpleBinderVO vo : voList){
@ -95,9 +95,9 @@ public class TestEntityListBinder {
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(queryWrapper);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListComplexBinderVO> voList = RelationsBinder.convertAndBind(userList, EntityListComplexBinderVO.class);
List<EntityListComplexBinderVO> voList = Binder.convertAndBindRelations(userList, EntityListComplexBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(EntityListComplexBinderVO vo : voList){
@ -114,7 +114,7 @@ public class TestEntityListBinder {
queryWrapper.eq(Dictionary::getType, "GENDER");
Dictionary dictionary = dictionaryService.getSingleEntity(queryWrapper);
DictionaryVO vo = RelationsBinder.convertAndBind(dictionary, DictionaryVO.class);
DictionaryVO vo = Binder.convertAndBindRelations(dictionary, DictionaryVO.class);
Assert.assertTrue(vo.getChildren().size() > 0);
}
}

View File

@ -16,7 +16,7 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.binding.Binder;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import diboot.core.test.StartupApplication;
@ -57,16 +57,16 @@ public class TestFieldBinder {
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(queryWrapper);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<FieldBinderVO> voList = RelationsBinder.convertAndBind(userList, FieldBinderVO.class);
List<FieldBinderVO> voList = Binder.convertAndBindRelations(userList, FieldBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(FieldBinderVO vo : voList){
// 验证直接关联和通过中间表间接关联的绑定
Assert.assertNotNull(vo.getDeptName());
Assert.assertNotNull(vo.getOrgName());
Assert.assertNotNull(vo.getOrgTelphone());
Assert.assertNotNull(vo.getOrgParentId());
// 验证枚举值已绑定
Assert.assertNotNull(vo.getGenderLabel());
@ -79,9 +79,9 @@ public class TestFieldBinder {
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(queryWrapper);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<UserVO> voList = RelationsBinder.convertAndBind(userList, UserVO.class);
List<UserVO> voList = Binder.convertAndBindRelations(userList, UserVO.class);
if(V.notEmpty(voList)){
for(UserVO vo : voList){
Assert.assertNotNull(vo.getDeptName());

View File

@ -0,0 +1,101 @@
/*
* 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;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.Binder;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import diboot.core.test.StartupApplication;
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.EntityListComplexBinderVO;
import diboot.core.test.binder.vo.EntityListSimpleBinderVO;
import diboot.core.test.config.SpringMvcConfig;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* 测试字段绑定
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/06/22
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SpringMvcConfig.class})
@SpringBootTest(classes = {StartupApplication.class})
public class TestFieldListBinder {
@Autowired
UserService userService;
@Autowired
DepartmentService departmentService;
/**
* 验证直接关联的绑定
*/
@Test
public void testSimpleBinder(){
// 加载测试数据
LambdaQueryWrapper<Department> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Department::getId, 10001L);
List<Department> entityList = departmentService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListSimpleBinderVO> voList = Binder.convertAndBindRelations(entityList, EntityListSimpleBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(EntityListSimpleBinderVO vo : voList){
// 验证直接关联的绑定
Assert.assertTrue(V.notEmpty(vo.getChildrenIds()));
System.out.println(JSON.stringify(vo.getChildrenIds()));
// 验证直接关联的绑定
Assert.assertTrue(V.notEmpty(vo.getChildrenNames()));
System.out.println(JSON.stringify(vo.getChildrenNames()));
}
}
/**
* 验证通过中间表间接关联的绑定
*/
@Test
public void testComplexBinder(){
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListComplexBinderVO> voList = Binder.convertAndBindRelations(userList, EntityListComplexBinderVO.class);
// 验证绑定结果
Assert.assertTrue(V.notEmpty(voList));
for(EntityListComplexBinderVO vo : voList){
// 验证通过中间表间接关联的绑定
Assert.assertTrue(V.notEmpty(vo.getRoleCodes()));
Assert.assertTrue(V.notEmpty(vo.getRoleCreateDates()));
System.out.println(JSON.stringify(vo.getRoleCodes()));
}
}
}

View File

@ -0,0 +1,206 @@
/*
* 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;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.binding.Binder;
import com.diboot.core.binding.JoinsBinder;
import com.diboot.core.binding.QueryBuilder;
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;
import org.apache.ibatis.jdbc.SQL;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
/**
* BindQuery测试
* @author Mazc@dibo.ltd
* @version v2.0.6
* @date 2020/04/14
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SpringMvcConfig.class})
@SpringBootTest(classes = {StartupApplication.class})
public class TestJoinQuery {
@Autowired
DepartmentService departmentService;
@Test
public void testDateCompaire(){
Department example = departmentService.getSingleEntity(null);
DepartmentDTO departmentDTO = new DepartmentDTO();
departmentDTO.setCreateTime(example.getCreateTime());
QueryWrapper<Department> queryWrapper = QueryBuilder.toQueryWrapper(departmentDTO);
List<Department> list = departmentService.getEntityList(queryWrapper);
Assert.assertTrue(list.size() >= 1);
}
@Test
public void testSingleTableQuery(){
Department entity = new Department();
entity.setParentId(10001L);
entity.setName("测试组");
entity.setOrgId(100001L);
QueryWrapper<Department> queryWrapper = QueryBuilder.toQueryWrapper(entity);
System.out.println(queryWrapper.getExpression());
List<Department> list = Binder.joinQueryList(queryWrapper, Department.class);
Assert.assertTrue(list.size() == 1);
Assert.assertTrue(queryWrapper.getSqlSegment().contains("parent_id"));
Assert.assertTrue(queryWrapper.getSqlSegment().contains("name"));
Assert.assertTrue(queryWrapper.getSqlSegment().contains("org_id"));
List<String> fields = Arrays.asList("name", "orgId", "parentId");
queryWrapper.clear();
queryWrapper = QueryBuilder.toQueryWrapper(entity, fields);
Assert.assertTrue(queryWrapper.getSqlSegment().contains("parent_id"));
Assert.assertTrue(queryWrapper.getSqlSegment().contains("name"));
Assert.assertTrue(queryWrapper.getSqlSegment().contains("org_id"));
Assert.assertTrue(queryWrapper.getParamNameValuePairs().size() == fields.size());
list = Binder.joinQueryList(queryWrapper, Department.class);
Assert.assertTrue(list.size() == 1);
}
@Test
public void testDynamicSqlQuery(){
// 初始化DTO测试不涉及关联的情况
DepartmentDTO dto = new DepartmentDTO();
dto.setParentId(10001L);
// 验证 转换后的wrapper可以直接查询
QueryWrapper<DepartmentDTO> queryWrapper = QueryBuilder.toQueryWrapper(dto);
List<Department> departments = departmentService.getEntityList(queryWrapper);
Assert.assertTrue(departments.size() == 3);
// builder直接查询不分页 3条结果
List<Department> builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(Department.class);
Assert.assertTrue(builderResultList.size() == 3);
// 初始化DTO
dto = new DepartmentDTO();
dto.setParentId(10001L);
dto.setParentName("产品部");
//boolean类型
dto.setOrgName("苏州帝博");
// 转换为queryWrapper
queryWrapper.clear();
queryWrapper = QueryBuilder.toQueryWrapper(dto);
queryWrapper.select("id,name,parentId,org_id");
// 验证直接查询指定字段
List<String> fields = Arrays.asList("parentId", "parentName", "orgName");
builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto, fields).queryList(Department.class);
Assert.assertTrue(builderResultList.size() == 3);
// 查询单条记录
Department department = Binder.joinQueryOne(queryWrapper, Department.class);
Assert.assertTrue(department.getName() != null);
// 不分页 3条结果
List<Department> list = JoinsBinder.queryList(queryWrapper, Department.class);
Assert.assertTrue(list.size() == 3);
// 不分页直接用wrapper查
list = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(Department.class);
Assert.assertTrue(list.size() == 3);
// 测试继续绑定VO 是否有影响
List<DepartmentVO> voList = Binder.convertAndBindRelations(list, DepartmentVO.class);
Assert.assertTrue(voList.size() == 3);
Assert.assertTrue(voList.get(0).getDepartment() != null);
Assert.assertTrue(voList.get(0).getOrganizationVO() != null);
// 分页
Pagination pagination = new Pagination();
pagination.setPageSize(2);
pagination.setPageIndex(1);
// 第一页 2条结果
list = Binder.joinQueryList(queryWrapper, Department.class, pagination);
Assert.assertTrue(list.size() == pagination.getPageSize());
// 测试排序
pagination.setOrderBy("orgName:DESC,parentName");
pagination.setPageIndex(2);
// 第二页 1条结果
list = Binder.joinQueryList(queryWrapper, Department.class, pagination);
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");
Assert.assertTrue(sql.contains("SELECT is_deleted"));
Assert.assertTrue(sql.contains("FROM test"));
Assert.assertTrue(sql.contains("LIMIT 1"));
}
/**
* 构建检测是否有删除字段的sql
* @param middleTable
* @return
*/
private static String buildCheckDeletedColSql(String middleTable){
return new SQL(){
{
SELECT(Cons.COLUMN_IS_DELETED);
FROM(middleTable);
LIMIT(1);
}
}.toString();
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.data.CheckpointType;
import com.diboot.core.binding.data.DataAccessCheckpoint;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.Comparison;
import com.diboot.core.util.D;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* Department DTO
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
*/
@Getter
@Setter
@Accessors(chain = true)
public class DepartmentDTO extends Department {
private static final long serialVersionUID = 8670003133709715087L;
//private Long parentId;
//private Long orgId;
@BindQuery(comparison = Comparison.CONTAINS)
private String name;
// 绑定join查询
@BindQuery(comparison = Comparison.STARTSWITH, entity = Organization.class, field = "name", condition = "this.org_id=id")
private String orgName;
// 绑定join查询
@BindQuery(entity = Department.class, field = "name", condition = "this.parent_id=id")
private String parentName;
// 数据权限检查点
@DataAccessCheckpoint(type = CheckpointType.ORG)
private Long orgId;
// 查询单个日期
@BindQuery(comparison = Comparison.GE, field = "createTime")
private Date createTime;
@BindQuery(comparison = Comparison.LT, field = "createTime")
private Date createTimeEnd;
private Date getCreateTimeEnd(){
return D.nextDay(createTime);
}
}

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

@ -16,13 +16,15 @@
package diboot.core.test.binder.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.Comparison;
import com.diboot.core.entity.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
* Department
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
@ -39,6 +41,7 @@ public class Department extends BaseEntity {
@TableField
private Long orgId;
@BindQuery(comparison = Comparison.CONTAINS)
@TableField
private String name;
}

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,53 @@
/*
* 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;
import java.util.Date;
/**
* 用户角色
* @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(exist = false)
private Long id;
@TableField
private Long userId;
@TableField
private Long roleId;
@TableField(exist = false)
private boolean deleted;
@TableField(exist = false)
private Date createTime;
}

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

@ -15,9 +15,14 @@
*/
package diboot.core.test.binder.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.service.BaseService;
import com.diboot.core.vo.Pagination;
import diboot.core.test.binder.dto.DepartmentDTO;
import diboot.core.test.binder.entity.Department;
import java.util.List;
/**
* 部门相关Service
* @author mazc@dibo.ltd
@ -26,4 +31,5 @@ import diboot.core.test.binder.entity.Department;
*/
public interface DepartmentService extends BaseService<Department> {
List<Department> getDepartmentSqlList(QueryWrapper<DepartmentDTO> queryWrapper, Pagination pagination);
}

View File

@ -16,6 +16,7 @@
package diboot.core.test.binder.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.service.BaseService;
import diboot.core.test.binder.entity.User;
/**
@ -24,6 +25,6 @@ import diboot.core.test.binder.entity.User;
* @version v2.0
* @date 2019/1/5
*/
public interface UserService extends IService<User> {
public interface UserService extends BaseService<User> {
}

View File

@ -15,19 +15,39 @@
*/
package diboot.core.test.binder.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.service.impl.BaseServiceImpl;
import com.diboot.core.vo.Pagination;
import diboot.core.test.binder.dto.DepartmentDTO;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.mapper.DepartmentMapper;
import diboot.core.test.binder.service.DepartmentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 部门相关Service实现
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/30
*/
@Slf4j
@Service
public class DepartmentServiceImpl extends BaseServiceImpl<DepartmentMapper, Department> implements DepartmentService {
@Override
public List<Department> getDepartmentSqlList(QueryWrapper<DepartmentDTO> queryWrapper, Pagination pagination) {
// 如果是单表return super.
boolean isSingleTableQuery = false;
if(isSingleTableQuery){
return super.getEntityList(queryWrapper, pagination);
}
else{
return null;
}
}
}

View File

@ -16,6 +16,7 @@
package diboot.core.test.binder.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.diboot.core.service.impl.BaseServiceImpl;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.mapper.UserMapper;
import diboot.core.test.binder.service.UserService;
@ -27,6 +28,6 @@ import org.springframework.stereotype.Service;
* @version 2018/12/23
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements UserService {
}

View File

@ -16,12 +16,15 @@
package diboot.core.test.binder.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.binding.annotation.BindEntity;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
* Department VO
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
@ -38,4 +41,15 @@ public class DepartmentVO {
@TableField(exist = false)
private String name;
@TableField
private Long orgId;
// 关联Entity
@BindEntity(entity = Department.class, condition = "this.parent_id=id") // AND ...
private Department department;
// 关联Entity赋值给VO
@BindEntity(entity = Organization.class, condition = "this.org_id=id") // AND ...
private OrganizationVO organizationVO;
}

View File

@ -16,12 +16,14 @@
package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.binding.annotation.BindFieldList;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.Date;
import java.util.List;
/**
@ -39,7 +41,16 @@ 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的单个属性集
@BindFieldList(entity = Role.class, field = "code", condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<String> roleCodes;
// 支持通过中间表的多-多Entity的单个属性集
@BindFieldList(entity = Role.class, field = "createTime", condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Date> roleCreateDates;
}

View File

@ -16,6 +16,7 @@
package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.binding.annotation.BindFieldList;
import diboot.core.test.binder.entity.Department;
import lombok.Getter;
import lombok.Setter;
@ -38,4 +39,12 @@ public class EntityListSimpleBinderVO extends Department {
@BindEntityList(entity = Department.class, condition = "this.id=parent_id")
private List<DepartmentVO> children;
// 1-n 关联取单个属性
@BindFieldList(entity = Department.class, field = "parentId", condition = "this.id=parent_id")
private List<Long> childrenIds;
// 1-n 关联取单个属性
@BindFieldList(entity = Department.class, field = "name", condition = "this.id=parent_id")
private List<String> childrenNames;
}

View File

@ -44,8 +44,8 @@ public class FieldBinderVO extends User{
// 支持级联字段关联相同条件的entity+condition将合并为一条SQL查询
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
@BindField(entity = Organization.class, field="telphone", condition="this.department_id=department.id AND department.org_id=id")
private String orgTelphone;
@BindField(entity = Organization.class, field="parentId", condition="this.department_id=department.id AND department.org_id=id")
private Long orgParentId;
// 绑定数据字典枚举
@BindDict(type="GENDER", field = "gender")

View File

@ -44,7 +44,7 @@ import java.util.List;
*/
@TestConfiguration
@ComponentScan(basePackages={"com.diboot", "diboot.core"})
@MapperScan({"com.diboot.**.mapper", "diboot.**.mapper"})
@MapperScan({"com.diboot.core.mapper", "diboot.core.**.mapper"})
public class SpringMvcConfig implements WebMvcConfigurer{
private static final Logger log = LoggerFactory.getLogger(SpringMvcConfig.class);
@ -63,7 +63,6 @@ public class SpringMvcConfig implements WebMvcConfigurer{
// 设置fastjson的序列化参数禁用循环依赖检测数据兼容浏览器端避免JS端Long精度丢失问题
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.BrowserCompatible);
fastJsonConfig.setDateFormat(D.FORMAT_DATETIME_Y4MDHM);
converter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> httpMsgConverter = converter;

View File

@ -16,23 +16,27 @@
package diboot.core.test.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.diboot.core.binding.QueryBuilder;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.service.impl.DictionaryServiceImpl;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import com.diboot.core.vo.*;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.entity.UserRole;
import diboot.core.test.binder.service.UserService;
import diboot.core.test.config.SpringMvcConfig;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@ -52,9 +56,11 @@ import java.util.*;
public class BaseServiceTest {
@Autowired
@Qualifier("dictionaryService")
DictionaryServiceImpl dictionaryService;
@Autowired
UserService userService;
@Test
public void testGet(){
// 查询总数
@ -87,6 +93,11 @@ public class BaseServiceTest {
List<Long> ids = BeanUtils.collectIdToList(dictionaryList);
dictionaryList = dictionaryService.getEntityListByIds(ids);
Assert.assertTrue(V.notEmpty(dictionaryList));
// 获取map
List<Map<String, Object>> mapList = dictionaryService.getMapList(null, new Pagination());
Assert.assertTrue(mapList.size() > 0 && mapList.size() <= BaseConfig.getPageSize());
}
@Test
@ -144,6 +155,7 @@ public class BaseServiceTest {
dictionaryList.get(2).setItemValue("HZ2");
dictionaryService.updateEntity(dictionaryList.get(2));
Assert.assertTrue(success);
}
@Test
@ -227,7 +239,6 @@ public class BaseServiceTest {
Assert.assertTrue(success);
dictionaryList2 = dictionaryService.getEntityList(queryWrapper);
Assert.assertTrue(V.isEmpty(dictionaryList2));
}
@Test
@ -257,4 +268,82 @@ public class BaseServiceTest {
System.out.println(database);
Assert.assertTrue(database.equals("mysql") || database.equals("oracle"));
}
@Test
public void testGetValuesOfField(){
QueryWrapper<Dictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", "GENDER");
List<Long> ids = dictionaryService.getValuesOfField(queryWrapper, Dictionary::getId);
Assert.assertTrue(ids.size() > 0);
LambdaQueryWrapper<Dictionary> wrapper = new QueryWrapper<Dictionary>().lambda()
.eq(Dictionary::getType, "GENDER");
List<String> itemValues = dictionaryService.getValuesOfField(wrapper, Dictionary::getItemValue);
Assert.assertTrue(itemValues.size() > 0);
System.out.println(JSON.stringify(ids) + " : " + JSON.stringify(itemValues));
}
@Test
public void testGetLimit(){
QueryWrapper<Dictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", "GENDER");
queryWrapper.gt("parent_id", 0);
Dictionary dictionary = dictionaryService.getSingleEntity(queryWrapper);
Assert.assertTrue(dictionary != null);
List<Dictionary> ids = dictionaryService.getEntityListLimit(queryWrapper, 5);
Assert.assertTrue(ids.size() == 2);
}
@Test
public void testPagination(){
Dictionary dict = new Dictionary();
dict.setParentId(1L);
dict.setType("GENDER");
dict.setEditable(true);
QueryWrapper<Dictionary> queryWrapper = QueryBuilder.toQueryWrapper(dict);
// 查询当前页的数据
Pagination pagination = new Pagination();
pagination.setPageSize(1);
List<DictionaryVO> voList = dictionaryService.getViewObjectList(queryWrapper, pagination, DictionaryVO.class);
Assert.assertTrue(voList.size() == 1);
Assert.assertTrue(pagination.getTotalPage() >= 2);
pagination.setPageIndex(2);
voList = dictionaryService.getViewObjectList(queryWrapper, pagination, DictionaryVO.class);
Assert.assertTrue(voList.size() == 1);
}
/**
* 测试n-n的批量新建/更新
*/
@Test
@Transactional
public void testCreateUpdateN2NRelations(){
Long userId = 10001L;
LambdaQueryWrapper<UserRole> queryWrapper = new QueryWrapper<UserRole>().lambda().eq(UserRole::getUserId, userId);
// 新增
List<Long> roleIdList = Arrays.asList(10L, 11L, 12L);
userService.createOrUpdateN2NRelations(UserRole::getUserId, userId, UserRole::getRoleId, roleIdList);
List<UserRole> list = ContextHelper.getBaseMapperByEntity(UserRole.class).selectList(queryWrapper);
Assert.assertTrue(list.size() == roleIdList.size());
// 更新
roleIdList = Arrays.asList(13L);
userService.createOrUpdateN2NRelations(UserRole::getUserId, userId, UserRole::getRoleId, roleIdList);
list = ContextHelper.getBaseMapperByEntity(UserRole.class).selectList(queryWrapper);
Assert.assertTrue(list.size() == 1);
// 删除
roleIdList = null;
userService.createOrUpdateN2NRelations(UserRole::getUserId, userId, UserRole::getRoleId, roleIdList);
list = ContextHelper.getBaseMapperByEntity(UserRole.class).selectList(queryWrapper);
Assert.assertTrue(list.size() == 0);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.service;
import com.diboot.core.binding.data.CheckpointType;
import com.diboot.core.binding.data.DataAccessCheckInterface;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 数据访问控制测试
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/04/24
*/
@Component
public class DataAccessCheckImpl implements DataAccessCheckInterface {
@Override
public List<Long> getAccessibleIds(CheckpointType type) {
return Arrays.asList(100001L);
}
}

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

@ -71,7 +71,7 @@ create table user_role
-- 初始化样例数据
INSERT INTO department (id, parent_id, org_id, name)
VALUES (10001, 0, 100001, '产品部'), (10002, 10001, 100001, '研发组'), (10003, 10001, 100001, '测试组');
VALUES (10001, 0, 100001, '产品部'), (10002, 10001, 100001, '研发组'), (10003, 10001, 100001, '测试组'), (10004, 10001, 100001, 'UI组');
INSERT INTO dictionary (id, parent_id, type, item_name, item_value, description, extdata, sort_id, `is_deletable`, is_editable)
VALUES (1, 0, 'GENDER', '性别', null, '', null, 99, 0, 1), (2, 1, 'GENDER', '', 'M', null, null, 99, 0, 1), (3, 1, 'GENDER', '', 'F', null, null, 99, 0, 1);

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/常用工具类', '常用工具类']
]
}
@ -62,6 +63,7 @@ module.exports = {
['/guide/diboot-antd-admin/添加页面', '添加页面'],
['/guide/diboot-antd-admin/权限控制', '权限控制'],
['/guide/diboot-antd-admin/接口请求', '接口请求'],
['/guide/diboot-antd-admin/组件', '组件'],
['/guide/diboot-antd-admin/CRUD快速集成', 'CRUD快速集成'],
]
}
@ -76,7 +78,8 @@ module.exports = {
['/guide/diboot-element-admin/开始使用', '开始使用'],
['/guide/diboot-element-admin/添加页面', '添加页面'],
['/guide/diboot-element-admin/权限控制', '权限控制'],
['/guide/diboot-antd-admin/接口请求', '接口请求'],
['/guide/diboot-element-admin/接口请求', '接口请求'],
['/guide/diboot-element-admin/组件', '组件'],
['/guide/diboot-element-admin/CRUD快速集成', 'CRUD快速集成'],
]
}
@ -90,18 +93,28 @@ module.exports = {
['/guide/diboot-devtools/介绍', '介绍'],
['/guide/diboot-devtools/开始使用', '开始使用'],
['/guide/diboot-devtools/数据表管理', '数据表管理'],
['/guide/diboot-devtools/代码生成与更新', '代码生成与更新']
['/guide/diboot-devtools/后端代码生成与更新', '后端代码生成与更新'],
['/guide/diboot-devtools/前端功能生成', '前端功能生成']
]
}
],
'/guide/faq/': [
'/guide/notes/faq': [
{
title: 'F&Q',
title: 'FAQ',
collapsable: true,
sidebarDepth: 2,
children: [
['/guide/faq/devtools', 'devtools开发助理'],
['/guide/faq/iam', 'IAM 组件']
['/guide/notes/faq/main', 'FAQ'],
]
}
],
'/guide/notes/upgrade': [
{
title: '版本升级指南',
collapsable: true,
sidebarDepth: 2,
children: [
['/guide/notes/upgrade/2_0_x升级至2_1_x', '2.0.x升级至2.1.x'],
]
}
]
@ -111,10 +124,11 @@ module.exports = {
}, {
text: '基础组件 指南',
items: [
{ text: 'F&Q', link: '/guide/faq/devtools' },
{ text: 'core基础内核', link: '/guide/diboot-core/安装' },
{ 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: '版本升级指南', link: '/guide/notes/upgrade/2_0_x升级至2_1_x' },
{ text: 'F&Q', link: '/guide/notes/faq/main' },
]
}, {
text: '前端项目 指南',
@ -149,11 +163,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/'
}]
}*/]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -2,9 +2,11 @@
## 开始之前
* 在diboot-antd-admin中我们对CRUD等常用功能进行了一些抽象将常用的列表、详情、新建、更新、删除等功能需要的相关属性与方法都抽象成了vue的mixins文件这些文件在**src/components/diboot/mixins**文件夹下,强烈建议您先阅览以下他们
* 在diboot-antd-admin中我们对CRUD等常用功能进行了一些抽象将常用的列表、详情、新建、更新、删除等功能需要的相关属性与方法都抽象成了vue的mixins文件这些文件在**src/components/diboot/mixins**文件夹下。
* 也可以对已有的一些页面组件代码进行阅读,比如**src/views/system/iamUser**文件夹下的相关组件代码。
::: tip
在阅读文档之前,建议您准备好[diboot-antd-admin 最新版的环境](https://github.com/dibo-software/diboot-antd-admin/releases)源码环境,搭配代码使用更佳。
:::
## 列表页
1. 引入列表的mixins文件如下
@ -41,23 +43,51 @@ export default {
}
}
```
5. 删除数据直接在删除按钮上调用remove函数即可传入需要删除的当前id。
6. 导出数据直接在导出按钮上调用exportData函数即可将会传入当前查询参数并异步下载文件。
7. 自定义查询参数处理方法重写rebuildQuery方法接收mixins中已经定义的customQueryParam与queryParam的合并值返回处理后的值该方法将会在获取列表数据前或导出数据前被调用。
8. 钩子函数afterLoadList在列表加载完毕将会执行该操作另外删除函数返回的是 Promise对象所以可以使用.then在删除完毕时执行某些操作。
9. 相关配置:
* customQueryParam: 不会被搜索栏改变的初始查询参数对象,一般用于页面固定的查询参数
* queryParam与查询条件绑定的查询参数对象
* advanced用于构建可收缩/展开的查询框的状态参数
* more当前对象的关联数据对象
* getMore是否在该页面初始化时加载当前对象的关联数据对象加载的关联数据对象存储在more中默认为false
* getListFromMixin是否在页面初始化时自动加载列表数据默认为true
* loadingData标记页面加载数据状态
* pagination分页配置
* baseApi接口前缀必须配置
* listApi列表数据接口默认为 /list
* deleteApiPrefix删除接口前缀默认为 /
* exportApi: 导出接口,默认为 /export
5. 属性配置
| 属性 | 说明 | 类型 |默认值 | 版本|
| ------------- |:-------------| -----| -----| -----|
| baseApi | 请求接口基础路径(必须配置)|String | / | - |
| listApi | 列表数据接口| String | /list |- |
| deleteApiPrefix | 删除接口前缀 | String | / | - |
| exportApi | 导出接口| String |~~/export~~ /excel/export|~~2.0.5~~ **2.1.0**|
| customQueryParam| 自定义参数(不被查询表单重置和改变的参数) | object | {} | - |
| queryParam | 与查询条件绑定的参数(会被查询表单重置和改变的参数) | object | {} | - |
| dateRangeQuery | 日期区间选择配置 <br/>**时间区间字段请放在这个对象中,会自动构建参与查询** | object | {} | **2.1.0** |
| advanced | 高级搜索 展开/关闭 | boolean | false | **2.1.0** |
| data | 存储列表数据 | array | [] |-|
| getMore | 是否使mixin在当前业务的attachMore接口中自动获取关联数据<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>**| boolean | false | - |
| attachMoreList | 获取关联数据列表的配置列表<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>** | array | [] | **2.1.0** |
| more | 存储当前对象的关联数据对象<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>**| object | {} | - |
| getListFromMixin| 是否在页面初始化时自动加载列表数据 | boolean | true | - |
| loadingData | 标记页面加载数据状态 | boolean | false | - |
| pagination | 分页配置 | object | {pageSize: 10,current: 1,total: 0,showSizeChanger: true,pageSizeOptions: ['10', '20', '30', '50', '100'],showTotal: (total, range) => `当前显示 ${range[0]} - ${range[1]} 条/共 ${total} 条` } | - |
6. 功能函数
| 名称 | 说明 | 参数 | 版本|
| ------------- |:-------------| -----| -----|
| handleTableChange | 分页、排序、筛选变化时触发 |function(pagination, filters, sorter) | - |
| appendSorterParam | 构建排序 handleTableChange调用|function(sorter) | **2.1.0** |
| toggleAdvanced | 切换展示更多搜索框(绑定**advanced**属性)| - | **2.1.0** |
| onSearch | 搜索,查询第一页(默认查询按钮触发)| - | - |
| postList | post请求的获取列表可以传递更长、更复杂参数 | - | - |
| getList | get请求获取列表 | - | - |
| attachMore | 加载当前页面关联的对象或者字典参考属性getMore、attachMoreList、more | - | - |
| reset | 重置查询 | - | - |
| remove | 根据id删除 | function(id) | - |
| exportData | 导出数据至excel | - | **2.1.0** |
| downloadFile | 下载文件 | function(res) | **2.1.0** |
| getPopupContainer | 解决带有下拉框组件在滚动时下拉框不随之滚动的问题 | function(trigger) |-|
| contentTransform | 处理查询参数中的moment数据 默认转化为YYYY-MM-DD | function(content, transform = {}) | **2.1.0** |
| dateRange2queryParam | 构建区间查询参数转化dateRangeQuery属性内容 | - | **2.1.0** |
7. 钩子函数
| 名称 | 说明 | 参数 | 版本|
| ------------- |:-------------| -----| -----|
| afterLoadList | 加载数据之后操作 |function(list) | - |
| rebuildQuery | 重新构建查询条件 (接收已经定义的customQueryParam与queryParam的合并值)|function(query) | - |
## 新建与更新
@ -69,36 +99,52 @@ export default {
mixins: [form]
}
```
2. 相同业务对象的新建与更新使用同一个表单组件默认以抽屉形式打开可在引入地方直接调用该组件的open方法进行打开。
3. 使用以上引入列表mixins的方式引入form的mixins文件将会具有mixins中的所有能力。
4. 已有功能:
* open函数可直接调用打开该表单
* 提供根据open中参数情况自动切换新建与更新操作
* 提供打开与关闭完成的钩子函数
* 提供表单提交函数 onSubmit
* 对form下校验规则的自动校验
* 提供对校验的自定义操作
* 提供校验完成后的enhance钩子函数可对需要提交的数据进行再处理
* 可以对新建数据或更新数据的方法重写
* 提供提交成功与提交失败的钩子函数
* 提供表单默认布局参数
* 提供对关联数据的自动加载
* 提供各项配置支持自定义接口等
5. 相关配置:
* baseApi与列表页相同
* createApi新建接口将自动拼接在baseApi之后默认为 /
* updateApiPrefix: 更新接口前缀将自动拼接在baseApi之后默认为 /
* model: 更新时加载的原数据
* title页面标题默认为新建或更新
* reloadMore加载的关联数据对象默认为{}如果list页面以及由more数据且不与自身关联可通过传参方式直接使用list中的more参数
* getMore初始化时是否加载reloadMore数据默认为false
* state当前组件状态对象默认为{visible: false, confirmSubmit: false}
6. 钩子函数:
* afterOpen在组件打开后或者更新时数据加载完毕后执行该函数
* afterClose 在组件关闭后,执行该函数
* enhance在校验完成后对提交数据进行处理的函数
* submitSuccess 提交成功后执行该函数默认关闭该组件并发送complete和changeKey事件
* submitFailed 提交失败后,执行该函数,默认提示错误消息
2. 属性配置:
| 属性 | 说明 | 类型 |默认值 | 版本|
| ------------- |:-------------| -----| -----| -----|
| primaryKey | 主键字段名 | string | id | **2.1.0** |
| baseApi | 请求接口基础路径(必须配置)|String | / | - |
| createApi | 新建接口,自动拼接在*baseApi*之后| String | / |- |
| updateApiPrefix | 更新接口前缀,自动拼接在*baseApi*之后 | String | / | - |
| labelCol | label 默认布局样式 | object |{xs: {span: 24}, sm: {span: 5}}|-|
| wrapperCol | form控件默认布局样式 | object |{xs: {span: 24}, sm: {span: 16}}|-|
| model| 存放form数据 | object | {} | - |
| title | 标题 | String |新建/更新|-|
| more | 存储当前对象的关联数据对象<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>** | object | {} | - |
| attachMoreList | 获取关联数据列表的配置列表<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>** | array | [] | **2.1.0** |
| getMore | 是否使mixin在当前业务的attachMore接口中自动获取关联数据<br/>**<a href="#业务对象关联详解">:point_right:业务对象关联详解</a>**| boolean | false | - |
| state | 当前组件状态对象 | object | {visible: false, confirmSubmit: false} | - |
| isUpload | 当前form是否包含上传<br/>**<a href="#文件上传详解">:point_right:文件上传详解</a>** | boolean | false | **2.1.0** |
| fileWrapper | 文件包装容器 <br/>**<a href="#文件上传详解">:point_right:文件上传详解</a>** | object | {} | **2.1.0** |
| fileUuidList | 文件存储服务器后返回的唯一标识集合<br/>**<a href="#文件上传详解">:point_right:文件上传详解</a>** | array | [] | **2.1.0** |
3. 功能函数
| 名称 | 说明 | 参数 | 版本|
| ------------- |:-------------| -----| -----|
| moment | moment时间相关操作 |- | - |
| open | 打开表单 (根据参数id存在与否设置为更新or新建操作) |function(id) | - |
| close | 关闭表单 |- | - |
| validate | 提交前的验证流程 |- | - |
| add | 新建记录的提交 |function(values) | - |
| update | 更新记录的提交 |function(values) | - |
| onSubmit | 表单提交事件 |- | - |
| getPopupContainer | 解决带有下拉框组件在滚动时下拉框不随之滚动的问题 |function(trigger) | - |
| attachMore | 加载当前页面关联的对象或者字典参考属性getMore、attachMoreList、more |- | - |
| filterOption | select选择框启用search功能后的过滤器 |- | - |
| clearForm | 清除form内容关闭的时候自动调用 |- | - |
| __setFileUuidList__ | 设置文件uuid参考属性isUpload、fileWrapper、fileUuidList |- | - |
| __defaultFileWrapperKeys__ | 初始化fileWrapper关闭时候自动调用 |- | - |
4. 钩子函数
| 名称 | 说明 | 参数 | 版本|
| ------------- |:-------------| -----| -----|
| afterOpen | 在组件打开后,或者更新时数据加载完毕后,执行该函数 |function(id) | - |
| afterClose | 在组件关闭后,执行该函数 |- | - |
| enhance | 在校验完成后,对提交数据进行处理的函数 |function(values) | - |
| submitSuccess | 提交成功后执行该函数默认关闭该组件并发送complete和changeKey事件 |function(result) | - |
| submitFailed | 提交失败后,执行该函数,默认提示错误消息 |function(result) | - |
## 查看详情
@ -110,16 +156,125 @@ export default {
mixins: [detail]
}
```
2. 已有功能:
* 自动加载当前记录数据
* 关闭弹窗或者抽屉
* 可通过父组件传入width参数控制抽屉的宽度
3. 相关配置:
* baseApi与列表页相同
* visible当前组件显示状态默认为false
* model当前详情框详情数据
* title当前详情框标题
4. 钩子函数:
* afterOpen: 打开详情之后执行的函数
* afterClose关闭之后执行的函数
2. 属性配置
| 属性 | 说明 | 类型 |默认值 | 版本|
| ------------- |:-------------| -----| -----| -----|
| baseApi | 请求接口基础路径(必须配置)|String | / | - |
| visible | 当前组件显示状态 | String | / | - |
| model | 当前详情框详情数据 | object |{}|-|
| title | 标题 | String |详情|-|
| spinning | loading状态 | boolean | false | - |
3.功能函数
| 名称 | 说明 | 参数 | 版本|
| ------------- |:-------------| -----| -----|
| open | 打开详情(加载服务端数据)|function(id) | - |
| close | 关闭详情 | - | - |
| downloadFile | 下载文件(传入接口地址)| function(path) | **2.1.0** |
4. 钩子函数
| 属性 | 说明 | 参数 |
| ------------- |:-------------| -----|
| afterOpen | 打开之后的操作|function(id) |
| afterClose| 关闭之后操作 | - |
## 详解
- <a id="业务对象关联详解">业务对象关联详解</a>
- more: 值来源于*getMore*或*attachMoreList* 配置请求接口后返回的结果;
- getMore: 开启关联数据会从当前业务的/attachMore接口中读取开启后优于attachMoreList使用
- attachMoreList: **2.1.0 新增** 实现关联数据从/common/attachMore接口统一读取配置如下
```javascript
// typeD(字典数据)/T关联业务对象
attattachMoreList: [
{
type: 'D', // 查询字典
target: 'GENDER' // 指向字典的 type = GENDER字段值
},
{
type: 'D', //查询字典
target: 'USER_STATUS' // 指向字典的 type = USER_STATUS字段值
},
{
type: 'T', // 查询对象
target: 'iamRole', // 指向IamRole对象
key: 'name', // 指向IamRole#name字段需要查询作为key的字段名称
value: 'id' // 指向IamRole#id字段需要查询作为value的字段名称
}
]
```
- attachMoreList 返回值会自动绑定至more属性中上述配置样例返回值为(⚠data的key规则是上述target的小驼峰命名 + KvList)
```json
{
"code":0,
"data":{
"userStatusKvList":[
{
"k":"有效",
"v":"A"
}
],
"iamRoleKvList":[
{
"k":"超级管理员",
"v":10000
}
],
"genderKvList":[
{
"k":"女",
"v":"F"
},
{
"k":"男",
"v":"M"
}
]
},
"msg":"操作成功"
}
```
- 如非特殊建议使用attachMoreList配置用以简化代码
- <a id="文件上传详解">文件上传详解 (2.1.0新增)</a>
**以下属性讲解基于Upload组件** :point_right: [Upload.vue组件概述](/guide/diboot-antd-admin/组件.html#upload组件)
- isUpload: 标记当前form表单中是否包含上传属性使用如图片、文件默认不包含如果引入组件请手动开启
```javascript
data() {
return {
isUpload: true
}
}
```
- fileWrapper: 所有文件的集合都放置与fileWrapper对象中提交的时候会自动遍历然后提交至服务端进行数据处理
- template内容
```html{5}
<upload
v-if="state.visible"
:prefix="filePrefix"
:action="fileAction"
:file-list="fileWrapper.slideshowImgsList"
:rel-obj-type="relObjType"
rel-obj-field="slideshowImgs"
:limit-count="9"
:is-image="true"
list-type="picture-card"
v-model="form.slideshowImgs"
></upload>
```
- script内容
```javascript
data() {
return {
// 包含属性
fileWrapper: {
//存储form.slideshowImgs属性对应的文件集合
slideshowImgsList: []
}
}
}
```
- fileUuidList: 如果包含上传,那么会自动构建文件的提交数据用于绑定当前对象

View File

@ -28,4 +28,5 @@ diboot-antd-admin前端基础项目是一个与diboot其他后端组件构成
* 登录人员管理界面;
* 角色与权限管理功能;
* 权限管理功能;
* 登录日志管理功能。
* 登录日志管理功能;
* 预置上传、富文本、导入组件 **2.1.0 新增)**

View File

@ -7,11 +7,25 @@
* 在**src/views**文件夹下创建页面对应的文件夹以及对应的页面组件文件
## 添加路由配置
### 自动添加路由配置
:point_right: [devtools 2.1.0 自动化生成您的前端页面](/guide/diboot-devtools/介绍)
:::tip
devtools 2.1.0为您提供了前端页面和后端接口的快速构建,赶快点击体验吧!!!
:::
* 后台菜单是根据路由配置自动生成的,具体可参考[路由与菜单](https://pro.loacg.com/docs/router-and-nav)
### 手动添加路由配置
* 后台菜单是根据路由配置自动生成的,具体可参考[路由与菜单](https://pro.antdv.com/docs/router-and-nav)
* 在**src/config/router.config.js**文件中,可以配置需要新增页面的路由。
* 对于需要进行权限控制的菜单需要放到asyncRouterMap中进行配置其他不需要进行权限控制或所有人可用的菜单可以放到constantRouterMap中。
* 路由配置方式,可参考已有配置,如下:
* 对于需要进行权限控制的菜单,~~需要放到asyncRouterMap中进行配置~~其他不需要进行权限控制或所有人可用的菜单可以放到constantRouterMap中。
> 2.1.0版本之后devtools具有前端生成能力因此手动添加路由不建议直接放在asyncRoutes路由中而是交给**generateRouterMap**处理由devtools生成的路由会自动写入generateRouterMap集合
```javascript
export const asyncRouterMap = []
// 在router.config.js 末尾添加如下内容
const generateRouterMap = []
asyncRouterMap[0].children.splice(1, 0, ...generateRouterMap)
````
* 手动路由配置方式,可参考已有配置,如下:
```javascript
// 系统管理

View File

@ -0,0 +1,276 @@
# 组件
> diboot组件基于Ant Design Vue进行二次封装和业务调整搭配devtools后端接口服务**一键生成交互代码**,减少您的学习成本。
::: tip
以下内容为组件概述实际开发时建议直接使用devtools生成前端代码。
:::
## import组件
> 路径src/components/diboot/components/import/**.vue
>
> 功能提供excel导入、示例下载、数据预览等功能
- 属性
| 名称 | 说明 | 类型 |默认值 | 必填|
| ------------- |:-------------| -----| -----| -----|
| exampleUrl |示例文件地址| string| -| Y|
| uploadUrl |上传地址| string| -| Y|
| previewUrl |预览地址| string| -| Y|
| previewSaveUrl |预览后保存地址| string| -| Y|
| fieldsRequired |提交时候必须的参数| object| {}| N|
- 事件
| 名称 | 说明 |
| ------------- |:-------------|
| finishedUpload |数据上传至数据库成功后触发|
- 示例
```vue
<a-drawer
title="数据上传"
:width="720"
@close="close"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
>
<excel-import
v-if="visible"
:example-url="`${baseApi}/downloadExample/room-example.xlsx`"
:upload-url="`${baseApi}/upload`"
:preview-url="`${baseApi}/preview`"
:preview-save-url="`${baseApi}/previewSave`"
@finishedUpload="handleFinishedUpload"
></excel-import>
<div class="drawer-footer">
<a-button @click="close">关闭</a-button>
</div>
</a-drawer>
<script>
import ExcelImport from '@/components/diboot/components/import/ExcelImport'
export default {
name: 'ImportExample',
data() {
return {
baseApi: '/importExample/excel',
visible: false
}
},
methods: {
open() {
this.visible = true
},
/**
* 刷新数据
*/
handleFinishedUpload() {
//触发上传完成告知list组件
this.$emit('complete')
this.visible = false
},
close() {
this.visible = false
}
},
components: {
ExcelImport
}
}
</script>
```
## richText组件
> 概述富文本组件基于quill封装提供简单的富文本功能
>
> 路径src/components/diboot/components/richText/**.vue
>
> 功能:**QuillEditor.vue(富文本组件)** 和 **QuillHtmlRender.vue(渲染富文本组件)**
- QuillEditor.vue属性
| 名称 | 说明 | 类型 |
| ------------- |:-------------| -----|
| value |v-decorator绑定的值| string|
| placeholder |富文本框的提示| string|
- QuillEditor.vue事件
| 名称 | 说明 |
| ------------- |:-------------|
| change |直接使用v-decorator绑定属性值即可|
- QuillEditor.vue示例
```html
<!--v-if主要为了强制刷新富文本子组件否则在打开更新的时候不会更新字段值-->
<quill-editor
v-if="state.visible"
placeholder="请输入富文本编辑"
v-decorator="[
'richText',
{
initialValue: model.richText
}
]"
></quill-editor>
```
- QuillHtmlRender.vue属性
| 名称 | 说明 | 类型 |
| ------------- |:-------------| -----|
| content |富文本内容| string|
- QuillEditor.vue示例
```html
<quill-html-render :content="model.richText"></quill-html-render>
```
## upload组件
> 概述基于Ant Design Vue的upload组件封装更加贴合diboot接口服务
>
> 路径src/components/diboot/components/upload/Upload.vue
>
> 功能增加图片form校验、增强与后端接口交互简化代码流程
- 属性
| 名称 | 说明 | 类型 |默认值 | 必填|
| ------------- |:-------------| -----| -----| -----|
| prefix |地址前缀axios的baseUrl用于图片回显| string| -| N|
| action |向后端发送的请求地址| string| -| Y|
| relObjType |绑定的业务对象类名| string| -| Y|
| relObjField |绑定业务对象的属性| string| -| Y|
| fileList |文件存储位置| array| -| Y|
| listType |上传列表的内建样式支持text/picture-card| string| text(isImage为true时使用picture-card)| N|
| limitCount |上传数量限制)| number| 1| N|
| limitType |上传类型限制,不传默认所有文件,限制多个使用','分割| string| -| N|
| limitSize |单个文件上传大小(M)| number| 2| N|
| isImage |是否是图片,默认不是图片类型(主要用户上传后构建值)| boolean| false| N|
| uploadText |上传框里面的文本| string| 上传| N|
| value |v-decorator绑定的值| string| -| Y|
- 事件
| 名称 | 说明 |
| ------------- |:-------------|
| change |直接使用v-decorator绑定属性值即可|
- 示例
```vue
<el-form-item label="轮播图" >
<upload
v-if="state.visible"
:prefix="filePrefix"
:action="fileAction"
:file-list="fileWrapper.slideshowImgsList"
:rel-obj-type="relObjType"
rel-obj-field="slideshowImgs"
:limit-count="9"
:is-image="true"
list-type="picture-card"
v-decorator="[
'slideshowImgs',
{
initialValue: model.slideshowImgs
}
]"
></upload>
</el-form-item>
<el-form-item label="附件" >
<upload
v-if="state.visible"
:prefix="filePrefix"
:action="fileAction"
:file-list="fileWrapper.attachmentList"
:rel-obj-type="relObjType"
rel-obj-field="attachment"
:limit-count="1"
v-decorator="[
'attachment',
{
initialValue: model.attachment
}
]"
></upload>
</el-form-item>
<script>
import form from '@/components/diboot/mixins/form'
import Upload from '@/components/diboot/components/upload/Upload'
import { dibootApi } from '@/utils/request'
export default {
name: 'UploadExampleForm',
components: {
Upload
},
mixins: [form],
data() {
return {
baseApi: '/uploadExample',
filePrefix: '/api',
fileAction: '/uploadFile/upload/dto',
//当前业务对象类名
relObjType: 'UploadExample',
fileWrapper: {
//轮播图存放位置
slideshowImgsList: [],
//附件存放位置
attachmentList: [],
},
isUpload: true
}
},
methods: {
enhance(values) {
// 设置文件uuid
this.__setFileUuidList__(values)
},
/**
* 打开表单之后的操作, 加载
* @param id
*/
afterOpen(id) {
if (id) {
// 更新的时候加载上传的轮播图
dibootApi.get(`/uploadFile/getList/${id}/${this.relObjType}/slideshowImgs`).then(res => {
if (res.code === 0) {
if (res.data && res.data.length > 0) {
res.data.forEach(data => {
this.fileWrapper.slideshowImgsList.push(this.fileFormatter(data, true))
})
}
}
})
// 更新的时候加载上传 附件
dibootApi.get(`/uploadFile/getList/${id}/${this.relObjType}/attachment`).then(res => {
if (res.code === 0) {
if (res.data && res.data.length > 0) {
res.data.forEach(data => {
this.fileWrapper.attachmentList.push(this.fileFormatter(data))
})
}
}
})
}
},
/**
* 数据转化
*/
fileFormatter (data, isImage) {
const file = {
uid: data.uuid, // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
name: data.fileName || ' ', // 文件名
status: 'done', // 状态有uploading done error removed
response: '{"status": "success"}', // 服务端响应内容
filePath: data.accessUrl
}
if (isImage) {
Object.assign(file, {
url: `${this.filePrefix}${data.accessUrl}/image`,
thumbUrl: `${this.filePrefix}${data.accessUrl}/image`
})
}
return file
}
},
}
</script>
```

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,18 @@ protected String beforeDelete(BaseEntity entity){...}
//方法调用示例
String str = this.beforeDelete(entity);
```
该方法主要用来处理删除数据之前的逻辑如检验是否具有删除权限等需要子类继承BaseCrudRestController时重写并实现具体处理逻辑。
该方法主要用来处理删除数据之前的逻辑,如检验是否具有删除权限等,供子类重写实现。
## 关于 /common/attachMore 接口
attachMore是用于为前端select下拉框提供初始数据的通用接口默认会初始化生成在DictionaryController中。
当前端展现形式为Select下拉等数据量不大的场景下可调用该接口进行初始化。
当数据量较大不适合用Select展现等场景请通过自定义接口实现。
~~~java
@PostMapping("/common/attachMore")
public JsonResult attachMore(@Valid @RequestBody ValidList<AttachMoreDTO> attachMoreDTOList) {
...
}
~~~
## 数据校验
@ -181,7 +140,7 @@ private String name;
```java
@PostMapping("/")
public JsonResult createEntity(@Valid Demo entity, BindingResult result, HttpServletRequest request)
public JsonResult createEntity(@Valid Demo entity, BindingResult result)
throws Exception{
return super.createEntity(entity, result);
}
@ -189,9 +148,70 @@ public JsonResult createEntity(@Valid Demo entity, BindingResult result, HttpSer
* 如果您使用**json格式**进行数据提交,那么可以在@RequestBody注解前添加@Valid注解如下
```java
public JsonResult createEntity(@Valid @RequestBody Demo entity, BindingResult result, HttpServletRequest request)
public JsonResult createEntity(@Valid @RequestBody Demo entity, BindingResult result)
throws Exception{
return super.createEntity(entity, result);
}
```
## 统一异常处理
异常处理全局实现类为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,145 @@ 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);
~~~
### createOrUpdateN2NRelations 创建或更新n-n关联
* since v2.1.0
```java
/**
* 创建或更新n-n关联
* 在主动对象的service中调用不要求中间表有service
* @param driverIdGetter 驱动对象getter
* @param driverId 驱动对象ID
* @param followerIdGetter 从动对象getter
* @param followerIdList 从动对象id集合
*/
<R> boolean createOrUpdateN2NRelations(SFunction<R, ?> driverIdGetter, Object driverId, SFunction<R, ?> followerIdGetter, List<? extends Serializable> followerIdList);
```
使用示例:
~~~java
List<Long> roleIdList = Arrays.asList(10L, 11L, 12L);
// 新增/修改/删除(集合为空) 中间表关联关系
userService.createOrUpdateN2NRelations(UserRole::getUserId, userId, UserRole::getRoleId, roleIdList);
~~~
### 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,72 +2,26 @@
> diboot-core中的实体Entity是与数据库表对应的映射对象下文简称实体。
所有实体统一存放在entity包名下命名采用将表名转换为首字母大写的驼峰命名法命名,比如**sys_user**的实体名为**SysUser**
所有实体命名约定采用将表名转换为首字母大写的驼峰命名法命名,比如**sys_user**的实体名为**SysUser**
## BaseEntity
> BaseEntity是diboot-core提供的基础实体类提供了我们默认数据表结构的默认字段比如id、is_deleted、create_time等默认的方法如toMap等。
## BaseExtEntity
> BaseExtEntity是基于BaseEntity的扩展实体类对数据表结构的扩展字段extdata添加了相关处理方法extdata将以json字符串形式存储在数据库中。
* 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,147 +0,0 @@
# 无SQL关联
> diboot-core支持通过注解实现数据字典关联、单数据表关联、多数据表关联等功能。
## 如何开始
> 您可以创建完一个表相对应的实体类、service接口、mapper等代码后创建一个VO类来尝试该功能
```java
package com.example.demo.vo;
import com.diboot.core.binding.annotation.BindDict;
import com.diboot.core.binding.annotation.BindEntity;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.binding.annotation.BindField;
import com.example.demo.entity.Demo;
import java.util.List;
public class DemoVO extends Demo {
private static final long serialVersionUID = 5476618743424368148L;
}
```
:::warning
注:@BindDict注解需要依赖dictionary表初次启动时starter会自动安装。
:::
## 处理方式
1. 通过在VO类中添加相关字段以及对应的关联绑定注解来定义我们的绑定类型和需要得到的结果以及额外的条件等信息
2. 绑定完成后,我们需要调用**RelationsBinder**类中的相关方法类执行这个绑定关系,我们目前提供了两种方式可供处理:
### 自动绑定关联
> 该关联会自动将相关信息查询并设置到voList中适用于对已有的voList做处理
```java
//List<MyUserVO> voList = ...;
RelationsBinder.bind(voList);
```
### 自动转型并绑定关联
> 该关联会自动将vo所继承的父类的实体列表进行绑定并自动转型为voList适用于对于非voList的实体列表等做处理
```java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
List<MyUserVO> voList = RelationsBinder.convertAndBind(userList, MyUserVO.class);
```
## 数据字典关联绑定
:::tip
当表中的字段为数据字典类型的值时,可使用数据字典关联来绑定表字段与数据字典的关联关系。
<br>
在通过diboot-core的封装后数据字典关联时开发者不再需要写大量java代码和SQL关联查询只要使用相关注解即可快速转换值value为标签/名称name。
:::
* 当您需要将数据字典表中的某类数据映射到实体中的对应字段时,可以使用数据字典关联方式来对此建立关联关系。
* 我们提供了**BindDict**注解来进行数据字典关联绑定。
* 使用@BindDict注解时需传两个参数分别是type和field。
* type表示关联的数据字典类型
* field表示关联字段。
* 示例如下:
```java
@BindDict(type="USER_STATUS", field = "status")
private String statusLabel;
```
## 数据表关联绑定
* 数据表关联按照关联表的方式上可分为**单数据表直接关联**与**中间表关联**这两种,中间表关联是一种通过中间表进行多对多的关联处理方案。
* 按照得到结果的形式可分为**绑定关联表中对应字段**和**绑定关联表实体**这两种关联方式,前者得到关联表中的目标字段,后者得到关联表的整个实体。
* **绑定关联表实体**即支持绑定单个实体,也支持绑定实体列表。
* 支持对关联查询添加附加条件。
> 直接关联与中间表关联只是传入的参数不同,而绑定表字段与绑定表实体是使用的不同注解。
### 单表关联
:::tip
当两张表数据之间有直接的依赖关系时,如主外键关联关系,即可使用单表关联。
<br>
在通过diboot-core的封装后
单表关联时开发者不再需要写大量java代码和SQL查询只要使用相关注解即可快速绑定它们之间的关联关系。
<br>
同时关联的实现是拆解关联查询为单表查询,可以更加高效利用数据库缓存和索引,提高查询效率。
:::
### 多表关联
:::tip
当两张表数据之间是通过第三张表来产生依赖关系时,即可使用多表关联。
<br>
在通过diboot-core的封装后多表关联时开发者不再需要写大量java代码和SQL查询只要使用相关注解即可快速绑定它们之间的关联关系。
<br>
同时关联的实现是拆解关联查询为单表查询,可以更加高效利用数据库缓存和索引,提高查询效率。
:::
### 绑定目标字段
> 绑定字段使用**@BindField**注解进行处理,将得到关联表的目标字段的值。
* 使用@BindField注解时需传三个参数分别是entity、field、condition。
* entity表示关联实体
* field表示关联表字段名
* condition表示关联条件。
* 单数据表直接关联 **(单表关联)** 获取目标字段值,用来将数据表中的某个字段映射到实体的对应字段,注解及参数示例如下:
```java
@BindField(entity=Department.class, field="name", condition="department_id=id AND parent_id>=0")
private String deptName;
```
* 通过中间表进行多表关联的级联关联 **(多表关联)** 绑定字段
```java
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
```
### 绑定单个实体
> 绑定单个实体使用**@BindEntity**注解进行处理,将得到关联表对应的单个实体。
> 如果属性类型为VO等非Entity对象类型将自动转换为您指定的类型再绑定。
* 使用@BindEntity注解时需传两个参数分别是entity和condition。
* entity表示关联实体类
* condition表示关联条件。
* 单数据表直接关联 **(单表关联)** 获取目标单个实体,注解及参数示例如下:
```java
@BindEntity(entity = Department.class, condition="department_id=id")
private Department department;
```
* 通过中间表进行多表关联的级联关联 **(多表关联)** 绑定对应单个实体,示例如下:
```java
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id")
private Organization organization;
```
### 绑定实体列表
> 绑定实体列表使用**@BindEntityList**注解进行处理,将得到关联表对应的实体列表。
> 如果待绑定List中的泛型参数类型为VO等非Entity对象将自动转换为您指定的类型再绑定。
* 使用@BindEntityList注解时需传两个参数分别是entity和condition。
* entity表示关联实体类
* condition表示关联条件。
* 单数据表直接关联 **(单表关联)** 获取目标的实体列表,注解及参数示例如下:
```java
// 关联其他表
@BindEntity(entity = Department.class, condition="department_id=id")
private List<Department> departmentList;
// 关联自身,实现无限极分类等
@BindEntityList(entity = Department.class, condition = "id=parent_id")
private List<Department> children;
```
* 通过中间表进行多表关联的级联关联 **(多表关联)** 绑定对应的实体列表,示例如下:
```java
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
```

View File

@ -0,0 +1,136 @@
# 无SQL关联绑定
> diboot-core支持通过注解实现数据字典关联、从表Entity(1-1)及Entity集合(1-n)关联,从表字段(1-1)及字段集合(1-n)关联等实现。
> 通过重构查询方式 (拆解关联查询,程序中Join) ,简化开发、提高性能
## 如何开始
> 自动绑定关联的前提是具备表相对应的Entity实体类、service接口、mapper等您可以创建完之后建一个主表的VO类来尝试该功能
```java
import com.diboot.core.binding.annotation.*;
import com.example.demo.entity.Demo;
public class DemoVO extends Demo {
private static final long serialVersionUID = 5476618743424368148L;
// @BindXX注解
}
```
:::warning
注:@BindDict注解需要依赖dictionary表初次启动时diboot-core-starter会自动安装。
:::
## 绑定调用方式
1. 通过在VO类中添加相关字段以及对应的关联绑定注解来定义我们的绑定类型和需要得到的结果以及额外的条件等信息
2. 注解添加后,调用**Binder**类中的相关方法(*bindRelations)执行这个绑定关系,我们目前提供了两种方式可供处理:
### 自动绑定关联
> 该关联会自动将相关信息查询并设置到voList中适用于对已有的voList做处理
```java
//List<MyUserVO> voList = ...;
Binder.bindRelations(voList);
```
### 自动转型并绑定关联
> 该关联会自动将vo所继承的父类的实体列表进行绑定并自动转型为voList适用于对于非voList的实体列表等做处理
```java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
// 转型并绑定关联
List<MyUserVO> voList = Binder.convertAndBindRelations(userList, MyUserVO.class);
```
### 通过BaseService接口实现绑定
```java
// 绑定单个VO对象
service.getViewObject()
// 绑定多个VO集合
service.getViewObjectList()
```
## 数据字典关联绑定
::: tip
当表中的字段为数据字典类型的值时,可使用数据字典关联来绑定表字段与数据字典的关联关系。
<br>
通过@BindDict注解数据字典关联时无需写大量java代码和SQL关联查询即可快速转换值字典value值为label。
:::
* 使用@BindDict注解时需传两个参数分别是type和field。
* type表示关联的数据字典类型
* field表示关联字段。
* 示例如下:
```java
@BindDict(type="USER_STATUS", field = "status")
private String statusLabel;
```
## 数据表关联绑定
* 数据表关联按照关联表的方式上可分为**单数据表直接关联**与**中间表关联**这两种,中间表关联是一种通过中间表进行“"1-1"或"多-多"的关联处理方案。
* 按照得到结果的形式可分为**绑定关联表中对应字段(集合)**和**绑定关联表实体(集合)** 这两种关联方式,前者得到关联表中的目标字段(集合),后者得到关联表的整个实体(集合)。
* 支持对关联查询添加附加条件。
* diboot关联的实现是拆解关联查询为单表查询可以更高效利用数据库缓存和索引降低死锁概率提高性能。
### 绑定从表Entity实体
> 绑定单个实体使用**@BindEntity**注解进行处理,将得到关联表对应的单个实体。
* 使用@BindEntity注解时需传两个参数分别是entity和condition。
* entity表示关联实体类
* condition表示关联条件。
* 主表1-1直接关联从表获取从表Entity注解示例如下
```java
@BindEntity(entity = Department.class, condition="department_id=id")
private Department department;
```
* 主表1-1通过中间表间接关联从表获取从表Entity注解示例如下(condition不同)
```java
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id")
private Organization organization;
```
### 绑定从表Entity实体列表
> 绑定实体列表使用**@BindEntityList**注解进行处理,将得到关联表对应的实体列表。
* @BindEntityList注解参数与@BindEntity相同
* 主表1-n直接关联从表获取从表的Entity列表注解示例如下
```java
// 关联其他表
@BindEntityList(entity = Department.class, condition="department_id=id")
private List<Department> departmentList;
// 关联自身,实现无限极分类等
@BindEntityList(entity = Department.class, condition = "id=parent_id")
private List<Department> children;
```
* 主表1-n通过中间表关联从表绑定从表的Entity列表示例如下
```java
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
```
### 绑定从表字段
> 绑定字段使用**@BindField**注解进行处理,将得到关联表的目标字段的值(集合)。
* @BindField@BindFieldList 注解需三个参数分别是entity、field、condition。
* entity表示关联实体
* field表示关联表字段名
* condition表示关联条件。
* 主表1-1直接关联从表获取从表字段值注解示例如下
```java
@BindField(entity=Department.class, field="name", condition="department_id=id AND parent_id>=0")
private String deptName;
```
* 主表1-1通过中间表关联从表的级联关联注解示例如下
```java
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
```
### 绑定从表字段列表
* since v2.1
> 绑定实体列表使用**@BindFieldList**注解进行处理,将得到关联表对应的实体列表。
* @BindFieldList注解参数同@BindField
* 主表1-n直接关联从表字段集合注解示例如下
```java
// 关联其他表
@BindFieldList(entity = Department.class, field="id", condition="department_id=id")
private List<Long> departmentIds;
```
* 主表1-n通过中间表关联从表绑定从表的Entity某字段的列表示例如下
```java
@BindFieldList(entity = Role.class, field="code", condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<String> roleCodes;
```

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,33 +0,0 @@
# 查询条件DTO
> 用户可自定义相关实体的DTO类将其在Controller类的相关方法中自动转换为QueryWrapper进行条件查询时使用
## 创建DTO
```java
public class UserDTO{
// 无@BindQuery注解默认会映射为=条件
private Long gender;
// 有注解,映射为注解指定条件
@BindQuery(comparison = Comparison.LIKE)
private String realname;
//... getter, setter方法等
}
```
## 自动转换
> Controller类中对相关路由的查询条件可进行如下绑定
```java
/**
* url参数示例: /list?gender=M&realname=张
* 将映射为 queryWrapper.eq("gender", "M").like("realname", "张")
*/
@GetMapping("/list")
public JsonResult getVOList(UserDto userDto, HttpServletRequest request) throws Exception{
//调用super.buildQueryWrapper(entityOrDto, request) 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行转换
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto, request);
//... 查询list
return JsonResult.OK(list);
}
```

View File

@ -0,0 +1,75 @@
# 起步简介
## diboot-core 简介
> diboot-core 是 diboot 2.x版本的核心基础框架基于Spring Boot、Mybatis-plus封装实现基础代码的简化及高效开发。
> 使用diboot-core可以更加简单快捷地创建web应用您之前的诸多代码将被极大简化更易维护。
> 同时搭档[diboot-devtools](../diboot-devtools/介绍.md)让您彻底摆脱常规SQL与CRUD。
高效精简内核,重构查询方式,简化开发、提高性能,主要实现:
1. 单表CRUD无SQL
2. 关联查询绑定无SQL注解自动绑定
3. 数据字典无SQL注解自动绑定
4. 跨表Join查询无SQLQueryWrapper自动构建与查询
5. BaseService扩展增强支持常规的单表及关联开发场景接口
6. 其他常用Service接口、工具类的最佳实践封装
7. 提供[diboot-core-starter](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)简化diboot-core的初始化配置自动配置、自动创建数据字典表
## diboot-core 使用步骤
### 1. 引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-core-spring-boot-starter:{latestVersion}")
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>{latestVersion}</version>
</dependency>
~~~
### 2. 配置参数(数据源)
* 以Mysql为例配置数据源如下
~~~properties
#datasource config
spring.datasource.url=jdbc:mysql://localhost:3306/diboot_example?characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=diboot
spring.datasource.password=123456
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
~~~
> 注:@BindDict注解需要依赖dictionary表diboot-core starter 初次启动时会自动创建该表。
* diboot-core-spring-boot-starter的可选参数配置
~~~properties
# 是否初始化sql默认true初始化之后可以改为false关闭
diboot.core.init-sql=false
~~~
### 3. 参考样例 - [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)
> diboot-core的最佳实践建议自定义自己的Base类避免直接继承core中的Base类便于后期扩展。
启用devtools可一键生成初始Base类代码到本地。
## 支持数据库
MySQL、MariaDB、PostgreSQL、ORACLE、SQLServer
## 相关依赖
::: 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.3.x.RELEASE)
* **mybatis-plus-boot-starter**(com.baomidou:mybatis-plus-boot-starter:3.3.x)
* **commons-io**(commons-io:commons-io:2.6)
* **commons-lang3**(org.apache.commons:commons-lang3:3.10)
* **fastjson**(com.alibaba:fastjson:1.2.x)
::: tip
需要额外添加的jar
:::
* **数据库驱动包** (如 mysql:mysql-connector-java:8.0.18)
> 使用过程中遇到问题,可加群交流。

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Some files were not shown because too many files have changed in this diff Show More