+ 动态Join关联的绑定实现

This commit is contained in:
mazhicheng 2020-04-18 16:08:03 +08:00
parent 417dc8a53f
commit a4a11fcc19
42 changed files with 1735 additions and 214 deletions

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</parent>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
<version>2.0.6</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.0.6</version>
</dependency>
<dependency>

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")
@ -44,8 +45,82 @@ private List<Department> children;
private List<Role> roleList;
~~~
## ** 三. 注解绑定关联的使用方式
### 1. 引入依赖
### ** 三. 注解绑定关联的使用方式
#### 1. 定义你的Entity对应的Service继承diboot的BaseService及Mapper
> * 启用diboot-devtools自动生成后端各层代码。
#### 2. 参照以上注解说明在VO中定义你的关联
#### 3. 使用注解绑定:
调用Binder自动绑定注解相关关联
##### 方式1. 自动绑定关联(不需要转型)
~~~java
//List<MyUserVO> voList = ...;
Binder.bindRelations(voList);
~~~
##### 方式2. 自动转型并绑定关联(需要转型)
~~~java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
List<MyUserVO> voList = Binder.convertAndBindRelations(userList, MyUserVO.class);
~~~
### ** 四. Entity/DTO自动转换QueryWrapper自动构建Join查询
#### 1. Entity/DTO中声明映射查询条件
示例代码:
~~~java
public class UserDTO{
// 无@BindQuery注解默认会映射为=条件
private Long gender;
// 有注解,映射为注解指定条件
@BindQuery(comparison = Comparison.LIKE)
private String realname;
// join其他表
@BindQuery(comparison = Comparison.STARTSWITH, entity=Organization.class, field="name", condition="this.org_id=id")
private String orgName;
}
~~~
#### 2. 调用QueryBuilder.toQueryWrapper(entityOrDto)进行转换
~~~java
/**
* url参数示例: /list?gender=M&realname=张
* 将映射为 queryWrapper.eq("gender", "M").like("realname", "张")
*/
@GetMapping("/list")
public JsonResult getVOList(UserDto userDto, HttpServletRequest request) throws Exception{
//调用super.buildQueryWrapper(entityOrDto, request) 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行转换
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto, request);
// 或者
//QueryWrapper<User> queryWrapper = QueryBuilder.buildQueryWrapper(userDto);
//... 查询list
return JsonResult.OK(list);
}
~~~
#### 3. 动态Join的关联查询与结果绑定
> 动态查询的调用方式有以下两种:
##### 方式1. 通过QueryBuilder链式调用
~~~java
QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(Department.class);
~~~
##### 方式2. 通过QueryBuilder构建QueryWrapper再调用Binder或JoinsBinder
~~~java
// 构建QueryWrapper
QueryWrapper<DTO> queryWrapper = QueryBuilder.toQueryWrapper(dto);
// 调用join关联查询绑定
List<Entity> list = Binder.joinQueryList(queryWrapper, Department.class);
~~~
自动按需构建类似如下动态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:2.0.5")
@ -58,53 +133,7 @@ compile("com.diboot:diboot-core-spring-boot-starter:2.0.5")
<version>2.0.5</version>
</dependency>
~~~
> * 使用diboot-devtools会自动引入diboot-core无需配置此依赖。
> * @BindDict注解需要依赖dictionary表初次启动时starter会自动创建该表。
> * @BindDict注解需要依赖dictionary表启用diboot-devtools初次启动时starter会自动创建该表。
### 2. 定义你的Service继承diboot的BaseService或Mybatis-plus的ISerivice及Mapper
### 3. 使用注解绑定:
调用RelationsBinder自动绑定注解相关关联
#### 方式1. 自动绑定关联(不需要转型)
~~~java
//List<MyUserVO> voList = ...;
RelationsBinder.bind(voList);
~~~
#### 方式2. 自动转型并绑定关联(需要转型)
~~~java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
List<MyUserVO> voList = RelationsBinder.convertAndBind(userList, MyUserVO.class);
~~~
## ** 四. Entity/DTO自动转换为QueryWrapper的使用方式
### 1. Entity/DTO中声明映射查询条件
示例代码:
~~~java
public class UserDTO{
// 无@BindQuery注解默认会映射为=条件
private Long gender;
// 有注解,映射为注解指定条件
@BindQuery(comparison = Comparison.LIKE)
private String realname;
//... getter, setter
}
~~~
### 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);
//... 查询list
return JsonResult.OK(list);
}
~~~
## 五. 样例参考 - [diboot-core-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-core-example)
#### 2. 详细文档 - [diboot-core 官方文档](https://www.diboot.com/guide/diboot-core/%E5%AE%89%E8%A3%85.html)
#### 3. 参考样例 - [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.0.6</version>
</parent>
<artifactId>diboot-core</artifactId>
<version>2.0.5</version>
<version>2.0.6</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,221 @@
/*
* 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.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.0.5
* @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, 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 void formatOrderBy(DynamicJoinQueryWrapper queryWrapper, Pagination pagination){
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

@ -18,8 +18,12 @@ package com.diboot.core.binding;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.util.BeanUtils;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
@ -29,10 +33,12 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* QueryWrapper构建器 - EntityDTO -> 注解绑定查询条件 并转换为QueryWrapper对象
* QueryWrapper构建器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/07/27
@ -43,97 +49,123 @@ 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){
// 转换
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)){
return new QueryWrapper<DTO>();
}
QueryWrapper wrapper;
// 是否有join联表查询
boolean hasJoinTable = ParserCache.hasJoinTable(dto, fieldValuesMap.keySet());
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);
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);
@ -216,6 +248,9 @@ public class QueryBuilder {
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 +258,42 @@ 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<>();
// 转换
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;
}
//打开私有访问 获取值
field.setAccessible(true);
Object value = null;
try {
value = field.get(dto);
} catch (IllegalAccessException e) {
log.error("通过反射获取属性值出错:" + e);
}
if (value != null) {
resultMap.put(field.getName(), value);
}
}
return resultMap;
}
}

View File

@ -44,7 +44,7 @@ import java.util.List;
import java.util.Map;
/**
* 绑定管理器
* 关联关系绑定管理器
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/7/18

View File

@ -50,7 +50,7 @@ public class ConditionManager {
* @param condition
* @return
*/
private static List<Expression> getExpressionList(String condition){
public static List<Expression> getExpressionList(String condition){
if(V.isEmpty(condition)){
return null;
}
@ -72,6 +72,7 @@ public class ConditionManager {
/**
* 附加条件到binder
* @param condition
* @param binder
* @throws Exception
*/

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

@ -20,6 +20,7 @@ import com.diboot.core.config.Cons;
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;
@ -137,30 +138,26 @@ public class MiddleTable {
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,20 +15,26 @@
*/
package com.diboot.core.binding.parser;
import com.baomidou.mybatisplus.annotation.TableName;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.config.Cons;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.S;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.V;
import org.apache.ibatis.jdbc.SQL;
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>
@ -42,6 +48,14 @@ public class ParserCache {
* 中间表是否包含is_deleted列 缓存
*/
private static Map<String, Boolean> middleTableHasDeletedCacheMap = new ConcurrentHashMap<>();
/**
* entity类-表名的缓存
*/
private static Map<String, String> entityClassTableCacheMap = new ConcurrentHashMap<>();
/**
* dto类-BindQuery注解的缓存
*/
private static Map<String, List<AnnoJoiner>> dtoClassBindQueryCacheMap = new ConcurrentHashMap<>();
/**
* 获取指定class对应的Bind相关注解
@ -90,8 +104,149 @@ public class ParserCache {
if(middleTableHasDeletedCacheMap.containsKey(middleTable)){
return middleTableHasDeletedCacheMap.get(middleTable);
}
boolean hasColumn = SqlExecutor.validateQuery("SELECT is_deleted FROM "+middleTable);
boolean hasColumn = SqlExecutor.validateQuery(buildCheckDeletedColSql(middleTable));
middleTableHasDeletedCacheMap.put(middleTable, hasColumn);
return hasColumn;
}
/**
* 构建检测是否有删除字段的sql
* @param table
* @return
*/
private static String buildCheckDeletedColSql(String table){
return new SQL(){{
SELECT(Cons.COLUMN_IS_DELETED);
FROM(table);
LIMIT(1);
}}.toString();
}
/**
* 获取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;
}
/**
* 当前DTO是否有Join绑定
* @param dto dto对象
* @param fieldNameSet 有值属性集合
* @param <DTO>
* @return
*/
public static <DTO> boolean hasJoinTable(DTO dto, Set<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.extractAllFields(dtoClass);
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);
}
}
annos.add(annoJoiner);
}
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

@ -15,6 +15,7 @@
*/
package com.diboot.core.binding.query;
import javax.lang.model.type.NullType;
import java.lang.annotation.*;
/**
@ -40,6 +41,18 @@ public @interface BindQuery {
*/
String field() default "";
/***
* 绑定的Entity类
* @return
*/
Class entity() default NullType.class;
/***
* JOIN连接条件
* @return
*/
String condition() default "";
/**
* 忽略该字段
* @return

View File

@ -0,0 +1,89 @@
/*
* 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 join;
/**
* 别名
*/
private String alias;
private String condition;
/**
* 获取On条件
* @return
*/
public String getOnSegment(){
if(V.notEmpty(condition)){
return JoinConditionParser.parseJoinCondition(this.condition, this.alias);
}
return null;
}
}

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,137 @@
/*
* 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.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<>();
for(AnnoJoiner joiner : annoJoinerList){
if(V.notEmpty(joiner.getJoin()) && V.notEmpty(joiner.getOnSegment())){
String joinSegment = joiner.getJoin() + " " + joiner.getAlias() + " ON " + joiner.getOnSegment();
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();
WHERE(normalSql);
// 动态为主表添加is_deleted=0
if(ParserCache.hasDeletedColumn(wrapper.getEntityTable())){
WHERE("self."+ Cons.COLUMN_IS_DELETED+" = "+ 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);
}
}

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

View File

@ -38,6 +38,10 @@ public class Cons {
* 排序 - 降序标记
*/
public static final String ORDER_DESC = "DESC";
/**
* 逻辑删除列名
*/
public static final String COLUMN_IS_DELETED = "is_deleted";
/***
* 默认字段名定义
*/

View File

@ -17,8 +17,8 @@ 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;
@ -40,24 +40,21 @@ public class BaseController {
/***
* 构建查询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, HttpServletRequest request) throws Exception{
if(entityOrDto instanceof HttpServletRequest){
throw new Exception("参数错误buildQueryWrapper()参数为Entity/DTO对象");
}
return QueryBuilder.toQueryWrapper(entityOrDto, extractParams(request));
}
/***
* 构建查询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, HttpServletRequest request) throws Exception{
if(entityOrDto instanceof HttpServletRequest){
throw new Exception("参数错误buildQueryWrapper()参数为Entity/DTO对象");
}
@ -174,7 +171,7 @@ 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;
}

View File

@ -52,7 +52,7 @@ public abstract class BaseEntity implements Serializable {
*/
@TableLogic
@JSONField(serialize = false)
@TableField("is_deleted")
@TableField(Cons.COLUMN_IS_DELETED)
private boolean deleted = false;
/***

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

@ -21,7 +21,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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;
@ -343,7 +343,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()){
@ -438,7 +438,7 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
List<T> enityList = new ArrayList<>();
enityList.add(entity);
// 绑定
List<VO> voList = RelationsBinder.convertAndBind(enityList, voClass);
List<VO> voList = Binder.convertAndBindRelations(enityList, voClass);
return voList.get(0);
}
@ -446,7 +446,7 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
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;
}
@ -473,7 +473,7 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
}
}
return (Page<T>)pagination.toIPage();
return (Page<T>)pagination.toPage();
}
/**

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

View File

@ -319,7 +319,7 @@ public class V {
}
}
else{
//TODO 无法识别的格式
// 无法识别的格式
}
}
// 返回校验不通过的结果

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;
@ -119,7 +118,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,13 +141,13 @@ 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;
}

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;
@ -56,7 +56,7 @@ public class TestEntityBinder {
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(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){
@ -97,7 +97,7 @@ public class TestEntityListBinder {
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(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;
@ -59,7 +59,7 @@ public class TestFieldBinder {
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(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){
@ -81,7 +81,7 @@ public class TestFieldBinder {
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.list(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,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 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.entity.Department;
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 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);
}
@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,58 @@
/*
* 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 lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 定时任务
* @author mazc@dibo.ltd
* @version v2.0
* @date 2018/12/27
*/
@Getter
@Setter
@Accessors(chain = true)
public class DepartmentDTO implements Serializable {
private static final long serialVersionUID = 8670003133709715087L;
private Long parentId;
private Long orgId;
@BindQuery(comparison = Comparison.CONTAINS)
private String name;
// 绑定查询
@BindQuery(comparison = Comparison.STARTSWITH, entity = Organization.class, field = "name", condition = "this.org_id=id")
private String orgName;
// 绑定查询
@BindQuery(comparison = Comparison.STARTSWITH, entity = Organization.class, field = "name2", condition = "this.org_id=id")
private String orgName2;
// 绑定查询
@BindQuery(entity = Department.class, field = "name", condition = "this.parent_id=id")
private String parentName;
}

View File

@ -16,6 +16,8 @@
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;
@ -39,6 +41,7 @@ public class Department extends BaseEntity {
@TableField
private Long orgId;
@BindQuery(comparison = Comparison.CONTAINS)
@TableField
private String name;
}

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

@ -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,9 @@
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;
@ -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
@BindEntity(entity = Organization.class, condition = "this.org_id=id") // AND ...
private OrganizationVO organizationVO;
}

View File

@ -32,7 +32,6 @@ 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,7 +51,6 @@ import java.util.*;
public class BaseServiceTest {
@Autowired
@Qualifier("dictionaryService")
DictionaryServiceImpl dictionaryService;
@Test
@ -87,6 +85,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 +147,7 @@ public class BaseServiceTest {
dictionaryList.get(2).setItemValue("HZ2");
dictionaryService.updateEntity(dictionaryList.get(2));
Assert.assertTrue(success);
}
@Test

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

@ -27,19 +27,19 @@ public class DemoVO extends Demo {
## 处理方式
1. 通过在VO类中添加相关字段以及对应的关联绑定注解来定义我们的绑定类型和需要得到的结果以及额外的条件等信息
2. 绑定完成后,我们需要调用**RelationsBinder**类中的相关方法类执行这个绑定关系,我们目前提供了两种方式可供处理:
2. 绑定完成后,我们需要调用**Binder**类中的相关方法(*bindRelations)执行这个绑定关系,我们目前提供了两种方式可供处理:
### 自动绑定关联
> 该关联会自动将相关信息查询并设置到voList中适用于对已有的voList做处理
```java
//List<MyUserVO> voList = ...;
RelationsBinder.bind(voList);
Binder.bindRelations(voList);
```
### 自动转型并绑定关联
> 该关联会自动将vo所继承的父类的实体列表进行绑定并自动转型为voList适用于对于非voList的实体列表等做处理
```java
// 查询单表获取Entity集合
// List<User> entityList = userService.list(queryWrapper);
List<MyUserVO> voList = RelationsBinder.convertAndBind(userList, MyUserVO.class);
List<MyUserVO> voList = Binder.convertAndBindRelations(userList, MyUserVO.class);
```
## 数据字典关联绑定

View File

@ -7,11 +7,11 @@
<parent>
<artifactId>diboot-root</artifactId>
<groupId>com.diboot</groupId>
<version>2.0.5</version>
<version>2.0.6</version>
</parent>
<artifactId>diboot-file-spring-boot-starter</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
<packaging>jar</packaging>
<description>diboot file component project</description>
@ -25,7 +25,7 @@
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</dependency>
<!-- 文件上传 -->
@ -38,25 +38,26 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
<version>2.1.7</version>
</dependency>
<!-- http -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.3.1</version>
<version>4.4.1</version>
</dependency>
<!-- 图片压缩 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.9</version>
<version>0.4.11</version>
</dependency>
<!-- 验证码示例 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -93,22 +93,7 @@ public class HttpHelper {
put("xsd", "text/xml");
put("xsl", "text/xml");
put("xslt", "text/xml");
put("apk", "application/vnd.android./*
* 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-archive");
put("apk", "application/vnd.android.package-archive");
}};
/**

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</parent>
<artifactId>diboot-iam-base-spring-boot-starter</artifactId>
<version>2.0.5.1</version>
<version>2.0.6</version>
<packaging>jar</packaging>
<description>diboot IAM base project</description>
@ -25,14 +25,14 @@
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</dependency>
<!-- 依赖shiro、JWT -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.1</version>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>

View File

@ -16,7 +16,7 @@
package com.diboot.iam.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.binding.Binder;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
@ -94,7 +94,7 @@ public class IamUserServiceImpl extends BaseIamServiceImpl<IamUserMapper, IamUse
if (V.isEmpty(roleList)){
return null;
}
return RelationsBinder.convertAndBind(roleList, IamRoleVO.class);
return Binder.convertAndBindRelations(roleList, IamRoleVO.class);
}
@Override

16
pom.xml
View File

@ -7,13 +7,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
<packaging>pom</packaging>
<modules>
@ -25,7 +25,7 @@
<properties>
<java.version>1.8</java.version>
<springboot.version>2.2.4.RELEASE</springboot.version>
<springboot.version>2.2.6.RELEASE</springboot.version>
</properties>
<dependencies>
@ -33,7 +33,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
@ -65,22 +65,22 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<version>6.0.19.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
<version>1.2.68</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
<version>3.10</version>
</dependency>
<!-- 单元测试依赖 -->