init: 导入RuoYi‑Vue‑Plus 6.X完整代码
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-mybatis 数据库服务
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- satoken -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-satoken</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- api模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- dynamic-datasource 多数据源-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus 启动器 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus JSqlParser 支持 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus Join -->
|
||||
<dependency>
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
package com.github.yulichang.injector;
|
||||
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractSqlInjector;
|
||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||
import com.baomidou.mybatisplus.core.injector.methods.SelectList;
|
||||
import com.baomidou.mybatisplus.core.mapper.Mapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
|
||||
import com.github.yulichang.base.JoinMapper;
|
||||
import com.github.yulichang.method.*;
|
||||
import com.github.yulichang.toolkit.MPJTableMapperHelper;
|
||||
import com.github.yulichang.toolkit.ReflectionKit;
|
||||
import com.github.yulichang.toolkit.TableHelper;
|
||||
import lombok.Getter;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* SQL 注入器
|
||||
*
|
||||
* @author yulichang
|
||||
* @see DefaultSqlInjector
|
||||
*/
|
||||
@Getter
|
||||
public class MPJSqlInjector extends DefaultSqlInjector {
|
||||
|
||||
/**
|
||||
* 原始 SQL 注入器,用于兼容项目自定义注入逻辑。
|
||||
*/
|
||||
private AbstractSqlInjector sqlInjector;
|
||||
|
||||
/**
|
||||
* 构造 MPJ SQL 注入器。
|
||||
*/
|
||||
public MPJSqlInjector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造带原始注入器的 MPJ SQL 注入器。
|
||||
*
|
||||
* @param sqlInjector 原始 SQL 注入器
|
||||
*/
|
||||
public MPJSqlInjector(ISqlInjector sqlInjector) {
|
||||
if (Objects.nonNull(sqlInjector) && sqlInjector instanceof AbstractSqlInjector) {
|
||||
this.sqlInjector = (AbstractSqlInjector) sqlInjector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mapper 可用的注入方法列表。
|
||||
*
|
||||
* @param configuration MyBatis 配置
|
||||
* @param mapperClass Mapper 类型
|
||||
* @param tableInfo 表信息
|
||||
* @return 注入方法列表
|
||||
*/
|
||||
@Override
|
||||
public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {
|
||||
if (!isJoinMapper(mapperClass)) {
|
||||
if (Objects.nonNull(sqlInjector)) {
|
||||
return sqlInjector.getMethodList(configuration, mapperClass, tableInfo);
|
||||
}
|
||||
return super.getMethodList(configuration, mapperClass, tableInfo);
|
||||
}
|
||||
if (Objects.nonNull(sqlInjector)) {
|
||||
return methodFilter(sqlInjector.getMethodList(configuration, mapperClass, tableInfo));
|
||||
}
|
||||
return methodFilter(super.getMethodList(configuration, mapperClass, tableInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤并追加 MPJ 需要的 SQL 注入方法。
|
||||
*
|
||||
* @param list 原始注入方法列表
|
||||
* @return 过滤后的注入方法列表
|
||||
*/
|
||||
private List<AbstractMethod> methodFilter(List<AbstractMethod> list) {
|
||||
String packageStr = SelectList.class.getPackage().getName();
|
||||
List<String> methodList = Arrays.asList(
|
||||
"Update",
|
||||
"Delete",
|
||||
"SelectOne",
|
||||
"SelectCount",
|
||||
"SelectMaps",
|
||||
"SelectMapsPage",
|
||||
"SelectObjs",
|
||||
"SelectList",
|
||||
"SelectPage");
|
||||
list.removeIf(i -> methodList.contains(i.getClass().getSimpleName()) &&
|
||||
Objects.equals(packageStr, i.getClass().getPackage().getName()));
|
||||
addAll(list, getWrapperMethod());
|
||||
addAll(list, getJoinMethod());
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MPJ 联表操作注入方法。
|
||||
*
|
||||
* @return 联表操作注入方法列表
|
||||
*/
|
||||
private List<AbstractMethod> getJoinMethod() {
|
||||
List<AbstractMethod> list = new ArrayList<>();
|
||||
list.add(new DeleteJoin(SqlMethod.DELETE_JOIN.getMethod()));
|
||||
list.add(new UpdateJoin(SqlMethod.UPDATE_JOIN.getMethod()));
|
||||
list.add(new UpdateJoinAndNull(SqlMethod.UPDATE_JOIN_AND_NULL.getMethod()));
|
||||
list.add(new SelectJoinCount(SqlMethod.SELECT_JOIN_COUNT.getMethod()));
|
||||
list.add(new SelectJoinOne(SqlMethod.SELECT_JOIN_ONE.getMethod()));
|
||||
list.add(new SelectJoinList(SqlMethod.SELECT_JOIN_LIST.getMethod()));
|
||||
list.add(new SelectJoinPage(SqlMethod.SELECT_JOIN_PAGE.getMethod()));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MPJ 覆盖 MyBatis-Plus 默认 Wrapper 的注入方法。
|
||||
*
|
||||
* @return Wrapper 注入方法列表
|
||||
*/
|
||||
private List<AbstractMethod> getWrapperMethod() {
|
||||
List<AbstractMethod> list = new ArrayList<>();
|
||||
list.add(new com.github.yulichang.method.mp.Delete());
|
||||
list.add(new com.github.yulichang.method.mp.SelectOne());
|
||||
list.add(new com.github.yulichang.method.mp.SelectCount());
|
||||
list.add(new com.github.yulichang.method.mp.SelectMaps());
|
||||
list.add(new com.github.yulichang.method.mp.SelectMapsPage());
|
||||
list.add(new com.github.yulichang.method.mp.SelectObjs());
|
||||
list.add(new com.github.yulichang.method.mp.SelectList());
|
||||
list.add(new com.github.yulichang.method.mp.SelectPage());
|
||||
list.add(new com.github.yulichang.method.mp.Update());
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将新增注入方法追加到原始列表中,已存在同名方法时不重复追加。
|
||||
*
|
||||
* @param source 原始方法列表
|
||||
* @param addList 待追加方法列表
|
||||
*/
|
||||
private void addAll(List<AbstractMethod> source, List<AbstractMethod> addList) {
|
||||
for (AbstractMethod method : addList) {
|
||||
if (source.stream().noneMatch(m -> m.getClass().getSimpleName().equals(method.getClass().getSimpleName()))) {
|
||||
source.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Mapper 注入信息,并为 JoinMapper 注册 MPJ 表映射缓存。
|
||||
*
|
||||
* @param builderAssistant Mapper 构建助手
|
||||
* @param mapperClass Mapper 类型
|
||||
*/
|
||||
@Override
|
||||
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
|
||||
super.inspectInject(builderAssistant, mapperClass);
|
||||
if (!isJoinMapper(mapperClass)) {
|
||||
return;
|
||||
}
|
||||
Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
|
||||
MPJTableMapperHelper.init(modelClass, mapperClass);
|
||||
Supplier<Class<?>> supplier = () -> {
|
||||
try {
|
||||
return extractModelClassOld(mapperClass);
|
||||
} catch (Throwable throwable) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TableHelper.init(modelClass, supplier.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧版泛型解析逻辑,提取 Mapper 绑定的实体类型。
|
||||
*
|
||||
* @param mapperClass Mapper 类型
|
||||
* @return Mapper 泛型中的实体类型,无法解析时返回 null
|
||||
*/
|
||||
@SuppressWarnings("IfStatementWithIdenticalBranches")
|
||||
protected Class<?> extractModelClassOld(Class<?> mapperClass) {
|
||||
Type[] types = mapperClass.getGenericInterfaces();
|
||||
ParameterizedType target = null;
|
||||
for (Type type : types) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
|
||||
if (ArrayUtils.isNotEmpty(typeArray)) {
|
||||
for (Type t : typeArray) {
|
||||
if (t instanceof TypeVariable || t instanceof WildcardType) {
|
||||
break;
|
||||
} else {
|
||||
target = (ParameterizedType) type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Mapper 是否继承 MPJ JoinMapper。
|
||||
*
|
||||
* @param mapperClass Mapper 类型
|
||||
* @return true 是 JoinMapper false 不是 JoinMapper
|
||||
*/
|
||||
private boolean isJoinMapper(Class<?> mapperClass) {
|
||||
return JoinMapper.class.isAssignableFrom(mapperClass);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.dromara.common.mybatis.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据权限注解,用于标记数据权限的占位符关键字和替换值
|
||||
* <p>
|
||||
* 一个注解只能对应一个模板
|
||||
* </p>
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataColumn {
|
||||
|
||||
/**
|
||||
* 数据权限模板的占位符关键字,默认为 "deptName"
|
||||
*
|
||||
* @return 占位符关键字数组
|
||||
*/
|
||||
String[] key() default "deptName";
|
||||
|
||||
/**
|
||||
* 数据权限模板的占位符替换值,默认为 "dept_id"
|
||||
*
|
||||
* @return 占位符替换值数组
|
||||
*/
|
||||
String[] value() default "dept_id";
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.dromara.common.mybatis.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据权限组注解,用于标记数据权限配置数组
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataPermission {
|
||||
|
||||
/**
|
||||
* 数据权限配置数组,用于指定数据权限的占位符关键字和替换值
|
||||
*
|
||||
* @return 数据权限配置数组
|
||||
*/
|
||||
DataColumn[] value();
|
||||
|
||||
/**
|
||||
* 权限拼接标识符(用于指定连接语句的sql符号)
|
||||
* 如不填 默认 select 用 OR 其他语句用 AND
|
||||
* 内容 OR 或者 AND
|
||||
*/
|
||||
String joinStr() default "";
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
/**
|
||||
* 数据权限注解Advice
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class DataPermissionAdvice implements MethodInterceptor {
|
||||
|
||||
/**
|
||||
* 拦截带有数据权限注解的方法调用,设置当前线程的数据权限上下文。
|
||||
*
|
||||
* @param invocation 方法调用上下文
|
||||
* @return 代理方法执行结果
|
||||
* @throws Throwable 代理方法执行异常
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Object target = invocation.getThis();
|
||||
Method method = invocation.getMethod();
|
||||
// 设置权限注解
|
||||
DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method));
|
||||
try {
|
||||
// 执行代理方法
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
// 清除权限注解
|
||||
DataPermissionHelper.removePermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限注解
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 当前执行方法
|
||||
* @return 数据权限注解,未配置时返回 null
|
||||
*/
|
||||
private DataPermission getDataPermissionAnnotation(Object target, Method method) {
|
||||
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
||||
// 优先获取方法上的注解
|
||||
if (dataPermission != null) {
|
||||
return dataPermission;
|
||||
}
|
||||
// 方法上没有注解,则获取类上的注解
|
||||
Class<?> targetClass = target.getClass();
|
||||
// 如果是 JDK 动态代理,则获取真实的Class实例
|
||||
if (Proxy.isProxyClass(targetClass)) {
|
||||
return getProxyClassDataPermission(targetClass);
|
||||
}
|
||||
return targetClass.getAnnotation(DataPermission.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JDK 动态代理接口上获取数据权限注解。
|
||||
*
|
||||
* @param targetClass 代理类
|
||||
* @return 数据权限注解,未配置时返回 null
|
||||
*/
|
||||
private DataPermission getProxyClassDataPermission(Class<?> targetClass) {
|
||||
for (Class<?> interfaceClass : targetClass.getInterfaces()) {
|
||||
DataPermission dataPermission = interfaceClass.getAnnotation(DataPermission.class);
|
||||
if (dataPermission != null) {
|
||||
return dataPermission;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.springframework.aop.support.StaticMethodMatcherPointcut;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
/**
|
||||
* 数据权限匹配切点
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
/**
|
||||
* 判断当前方法或目标类型是否命中数据权限切点。
|
||||
*
|
||||
* @param method 当前执行方法
|
||||
* @param targetClass 目标类型
|
||||
* @return true 命中数据权限切点 false 未命中
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
// 优先匹配方法
|
||||
// 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
|
||||
if (method.isAnnotationPresent(DataPermission.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
|
||||
Class<?> targetClassRef = resolveTargetClass(targetClass);
|
||||
return targetClassRef.isAnnotationPresent(DataPermission.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析真实目标类型,兼容 MyBatis Mapper 的 JDK 动态代理类。
|
||||
*
|
||||
* @param targetClass Spring AOP 传入的目标类型
|
||||
* @return 真实目标类型或可匹配数据权限注解的接口类型
|
||||
*/
|
||||
private Class<?> resolveTargetClass(Class<?> targetClass) {
|
||||
if (!Proxy.isProxyClass(targetClass)) {
|
||||
return targetClass;
|
||||
}
|
||||
for (Class<?> interfaceClass : targetClass.getInterfaces()) {
|
||||
// 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
|
||||
// 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
|
||||
// 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
|
||||
if (interfaceClass.isAnnotationPresent(DataPermission.class)) {
|
||||
return interfaceClass;
|
||||
}
|
||||
}
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
|
||||
/**
|
||||
* 数据权限注解切面定义
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
|
||||
|
||||
/**
|
||||
* 数据权限通知逻辑。
|
||||
*/
|
||||
private final Advice advice;
|
||||
|
||||
/**
|
||||
* 数据权限切点匹配器。
|
||||
*/
|
||||
private final Pointcut pointcut;
|
||||
|
||||
/**
|
||||
* 构造数据权限切面定义。
|
||||
*/
|
||||
public DataPermissionPointcutAdvisor() {
|
||||
this.advice = new DataPermissionAdvice();
|
||||
this.pointcut = new DataPermissionPointcut();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限切点。
|
||||
*
|
||||
* @return 数据权限切点
|
||||
*/
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限通知。
|
||||
*
|
||||
* @return 数据权限通知
|
||||
*/
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package org.dromara.common.mybatis.config;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
|
||||
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.dromara.common.core.factory.YmlPropertySourceFactory;
|
||||
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
|
||||
import org.dromara.common.mybatis.config.properties.SqlLogProperties;
|
||||
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
|
||||
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
|
||||
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
|
||||
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
|
||||
import org.dromara.common.mybatis.interceptor.SqlLogInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* mybatis-plus配置类(下方注释有插件介绍)
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@EnableTransactionManagement(proxyTargetClass = true)
|
||||
@MapperScan("${mybatis-plus.mapperPackage}")
|
||||
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
|
||||
@EnableConfigurationProperties(SqlLogProperties.class)
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 组装 MyBatis-Plus 核心拦截器链。
|
||||
*
|
||||
* @return MyBatis-Plus 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 数据权限处理
|
||||
interceptor.addInnerInterceptor(dataPermissionInterceptor());
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor());
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限拦截器
|
||||
*/
|
||||
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
|
||||
return new PlusDataPermissionInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限切面处理器
|
||||
*/
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
|
||||
return new DataPermissionPointcutAdvisor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件,自动识别数据库类型
|
||||
*/
|
||||
public PaginationInnerInterceptor paginationInnerInterceptor() {
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
// 分页合理化
|
||||
paginationInnerInterceptor.setOverflow(true);
|
||||
return paginationInnerInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观锁插件
|
||||
*/
|
||||
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
|
||||
return new OptimisticLockerInnerInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整 SQL 日志拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "mybatis-plus.sql-log", name = "enabled", havingValue = "true")
|
||||
public SqlLogInterceptor sqlLogInterceptor(SqlLogProperties sqlLogProperties) {
|
||||
return new SqlLogInterceptor(sqlLogProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 元对象字段填充控制器
|
||||
*/
|
||||
@Bean
|
||||
public MetaObjectHandler metaObjectHandler() {
|
||||
return new InjectionMetaObjectHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用网卡信息绑定雪花生成器
|
||||
* 防止集群雪花ID重复
|
||||
*/
|
||||
@Bean
|
||||
public IdentifierGenerator idGenerator() {
|
||||
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisExceptionHandler mybatisExceptionHandler() {
|
||||
return new MybatisExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表对象处理器
|
||||
*/
|
||||
@Bean
|
||||
public PostInitTableInfoHandler postInitTableInfoHandler() {
|
||||
return new PlusPostInitTableInfoHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* PaginationInnerInterceptor 分页插件,自动识别数据库类型
|
||||
* https://baomidou.com/pages/97710a/
|
||||
* OptimisticLockerInnerInterceptor 乐观锁插件
|
||||
* https://baomidou.com/pages/0d93c0/
|
||||
* MetaObjectHandler 元对象字段填充控制器
|
||||
* https://baomidou.com/pages/4c6bcf/
|
||||
* ISqlInjector sql注入器
|
||||
* https://baomidou.com/pages/42ea4a/
|
||||
* BlockAttackInnerInterceptor 如果是对全表的删除或更新操作,就会终止该操作
|
||||
* https://baomidou.com/pages/f9a237/
|
||||
* IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截)
|
||||
* IdentifierGenerator 自定义主键策略
|
||||
* https://baomidou.com/pages/568eb2/
|
||||
* TenantLineInnerInterceptor 多租户插件
|
||||
* https://baomidou.com/pages/aef2f2/
|
||||
* DynamicTableNameInnerInterceptor 动态表名插件
|
||||
* https://baomidou.com/pages/2a45ff/
|
||||
*/
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.dromara.common.mybatis.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* SQL 日志配置。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "mybatis-plus.sql-log")
|
||||
public class SqlLogProperties {
|
||||
|
||||
/**
|
||||
* 是否开启完整 SQL 输出。
|
||||
*/
|
||||
private Boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 输出方式,可选 console、log。
|
||||
*/
|
||||
private String output = "console";
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package org.dromara.common.mybatis.core.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entity基类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createDept;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.dromara.common.mybatis.core.domain;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 当前请求的数据权限访问上下文
|
||||
*
|
||||
* @param perms 当前请求接口权限标识集合
|
||||
* @param roleKeys 当前请求角色标识集合
|
||||
* @author Lion Li
|
||||
*/
|
||||
public record DataPermissionAccess(Set<String> perms, Set<String> roleKeys) implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 空访问上下文,表示不按接口权限或角色约束数据权限角色。
|
||||
*/
|
||||
public static final DataPermissionAccess EMPTY = new DataPermissionAccess(Set.of(), Set.of());
|
||||
|
||||
/**
|
||||
* 是否存在数据权限约束。
|
||||
*
|
||||
* @return true 存在权限约束 false 不存在权限约束
|
||||
*/
|
||||
public boolean constrained() {
|
||||
return CollUtil.isNotEmpty(perms) || CollUtil.isNotEmpty(roleKeys);
|
||||
}
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
package org.dromara.common.mybatis.core.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
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.reflect.GenericTypeUtils;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 自定义 Mapper 接口, 实现 自定义扩展
|
||||
*
|
||||
* @param <T> table 泛型
|
||||
* @param <V> vo 泛型
|
||||
* @author Lion Li
|
||||
* @since 2021-05-13
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
|
||||
/**
|
||||
* Mapper 日志对象。
|
||||
*/
|
||||
Log log = LogFactory.getLog(BaseMapperPlus.class);
|
||||
|
||||
/**
|
||||
* Mapper 泛型类型缓存,避免重复解析实体与 VO 类型。
|
||||
*/
|
||||
ClassValue<Class<?>[]> TYPE_ARGUMENT_CACHE = new ClassValue<>() {
|
||||
/**
|
||||
* 解析指定 Mapper 类型的实体与 VO 泛型。
|
||||
*
|
||||
* @param type Mapper 类型
|
||||
* @return 泛型类型数组
|
||||
*/
|
||||
@Override
|
||||
protected Class<?>[] computeValue(Class<?> type) {
|
||||
return GenericTypeUtils.resolveTypeArguments(type, BaseMapperPlus.class);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前实例对象关联的泛型类型 V 的 Class 对象
|
||||
*
|
||||
* @return 返回当前实例对象关联的泛型类型 V 的 Class 对象
|
||||
*/
|
||||
default Class<V> currentVoClass() {
|
||||
return (Class<V>) currentMapperTypes()[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前实例对象关联的泛型类型 T 的 Class 对象
|
||||
*
|
||||
* @return 返回当前实例对象关联的泛型类型 T 的 Class 对象
|
||||
*/
|
||||
default Class<T> currentModelClass() {
|
||||
return (Class<T>) currentMapperTypes()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Mapper 的实体与 VO 泛型类型。
|
||||
*
|
||||
* @return 泛型类型数组
|
||||
*/
|
||||
private Class<?>[] currentMapperTypes() {
|
||||
Class<?>[] types = TYPE_ARGUMENT_CACHE.get(this.getClass());
|
||||
if (types == null || types.length < 2) {
|
||||
throw new IllegalStateException("无法解析 Mapper 泛型类型: " + this.getClass().getName());
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用默认的查询条件查询并返回结果列表
|
||||
*
|
||||
* @return 返回查询结果的列表
|
||||
*/
|
||||
default List<T> selectList() {
|
||||
return this.selectList(new QueryWrapper<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建当前 Mapper 绑定的 Lambda CRUD 链式操作。
|
||||
*
|
||||
* @return Lambda CRUD 链式包装器
|
||||
*/
|
||||
default LambdaCrudChainWrapper<T, V> lambda() {
|
||||
return new LambdaCrudChainWrapper<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建当前 Mapper 绑定的 Lambda 链式更新。
|
||||
*
|
||||
* @return Lambda 链式更新包装器
|
||||
*/
|
||||
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
|
||||
return ChainWrappers.lambdaUpdateChain(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入实体对象集合
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @return 插入操作是否成功的布尔值
|
||||
*/
|
||||
default boolean insertBatch(Collection<T> entityList) {
|
||||
return Db.saveBatch(entityList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量根据ID更新实体对象集合
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @return 更新操作是否成功的布尔值
|
||||
*/
|
||||
default boolean updateBatchById(Collection<T> entityList) {
|
||||
return Db.updateBatchById(entityList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入或更新实体对象集合
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @return 插入或更新操作是否成功的布尔值
|
||||
*/
|
||||
default boolean insertOrUpdateBatch(Collection<T> entityList) {
|
||||
return Db.saveOrUpdateBatch(entityList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入实体对象集合并指定批处理大小
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @param batchSize 批处理大小
|
||||
* @return 插入操作是否成功的布尔值
|
||||
*/
|
||||
default boolean insertBatch(Collection<T> entityList, int batchSize) {
|
||||
return Db.saveBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量根据ID更新实体对象集合并指定批处理大小
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @param batchSize 批处理大小
|
||||
* @return 更新操作是否成功的布尔值
|
||||
*/
|
||||
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
|
||||
return Db.updateBatchById(entityList, batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入或更新实体对象集合并指定批处理大小
|
||||
*
|
||||
* @param entityList 实体对象集合
|
||||
* @param batchSize 批处理大小
|
||||
* @return 插入或更新操作是否成功的布尔值
|
||||
*/
|
||||
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
|
||||
return Db.saveOrUpdateBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询单个VO对象
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoById(Serializable id) {
|
||||
return selectVoById(id, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询单个VO对象并将其转换为指定的VO类
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param <C> VO类的类型
|
||||
* @return 查询到的单个VO对象,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> C selectVoById(Serializable id, Class<C> voClass) {
|
||||
T obj = this.selectById(id);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
return MapstructUtils.convert(obj, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID集合批量查询VO对象列表
|
||||
*
|
||||
* @param idList 主键ID集合
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
|
||||
return selectVoByIds(idList, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表
|
||||
*
|
||||
* @param idList 主键ID集合
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param <C> VO类的类型
|
||||
* @return 查询到的VO对象列表,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
|
||||
List<T> list = this.selectByIds(idList);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return MapstructUtils.convert(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件Map查询VO对象列表
|
||||
*
|
||||
* @param map 查询条件Map
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoByMap(Map<String, Object> map) {
|
||||
return selectVoByMap(map, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件Map查询实体对象列表,并将其转换为指定的VO对象列表
|
||||
*
|
||||
* @param map 查询条件Map
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param <C> VO类的类型
|
||||
* @return 查询到的VO对象列表,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
|
||||
List<T> list = this.selectByMap(map);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return MapstructUtils.convert(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个VO对象
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoOne(Wrapper<T> wrapper) {
|
||||
return selectVoOne(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个VO对象,并根据需要决定是否抛出异常
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param throwEx 是否抛出异常的标志
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
|
||||
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个VO对象,并指定返回的VO对象的类型
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 返回的VO对象的Class对象
|
||||
* @param <C> 返回的VO对象的类型
|
||||
* @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
|
||||
return selectVoOne(wrapper, voClass, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个实体对象,并将其转换为指定的VO对象
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param throwEx 是否抛出异常的标志
|
||||
* @param <C> VO类的类型
|
||||
* @return 查询到的单个VO对象,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
|
||||
T obj = this.selectOne(wrapper, throwEx);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
return MapstructUtils.convert(obj, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有VO对象列表
|
||||
*
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoList() {
|
||||
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询VO对象列表
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoList(Wrapper<T> wrapper) {
|
||||
return selectVoList(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询实体对象列表,并将其转换为指定的VO对象列表
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param <C> VO类的类型
|
||||
* @return 查询到的VO对象列表,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
|
||||
List<T> list = this.selectList(wrapper);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return MapstructUtils.convert(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件分页查询VO对象列表
|
||||
*
|
||||
* @param page 分页信息
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @return 查询到的VO对象分页列表
|
||||
*/
|
||||
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
|
||||
return selectVoPage(page, wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件分页查询实体对象列表,并将其转换为指定的VO对象分页列表
|
||||
*
|
||||
* @param page 分页信息
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
* @param <C> VO类的类型
|
||||
* @param <P> VO对象分页列表的类型
|
||||
* @return 查询到的VO对象分页列表,经过转换为指定的VO类后返回
|
||||
*/
|
||||
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
|
||||
// 根据条件分页查询实体对象列表
|
||||
List<T> list = this.selectList(page, wrapper);
|
||||
// 创建一个新的VO对象分页列表,并设置分页信息
|
||||
IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return (P) voPage;
|
||||
}
|
||||
voPage.setRecords(MapstructUtils.convert(list, voClass));
|
||||
return (P) voPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param mapper 转换函数,用于将查询到的对象转换为指定类型的对象
|
||||
* @param <C> 要转换的对象的类型
|
||||
* @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回
|
||||
*/
|
||||
default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
|
||||
return StreamUtils.toList(this.selectObjs(wrapper), mapper);
|
||||
}
|
||||
|
||||
}
|
||||
+909
@@ -0,0 +1,909 @@
|
||||
package org.dromara.common.mybatis.core.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.SharedString;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.Query;
|
||||
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.Update;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Assert;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import org.dromara.common.mybatis.core.query.AggregateSelectUtils;
|
||||
import org.dromara.common.mybatis.core.query.LambdaQueryCondition;
|
||||
import org.dromara.common.mybatis.core.query.SqlAggregateFunction;
|
||||
import org.dromara.common.mybatis.core.query.SubQuery;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Mapper 级 Lambda CRUD 链式包装器。
|
||||
*
|
||||
* @param <T> table 泛型
|
||||
* @param <V> vo 泛型
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class LambdaCrudChainWrapper<T, V> extends AbstractLambdaWrapper<T, LambdaCrudChainWrapper<T, V>>
|
||||
implements Query<LambdaCrudChainWrapper<T, V>, T, SFunction<T, ?>>,
|
||||
Update<LambdaCrudChainWrapper<T, V>, SFunction<T, ?>>,
|
||||
LambdaQueryCondition<T, LambdaCrudChainWrapper<T, V>> {
|
||||
|
||||
/**
|
||||
* 当前链式操作绑定的 Mapper。
|
||||
*/
|
||||
private final BaseMapperPlus<T, V> crudMapper;
|
||||
|
||||
/**
|
||||
* 更新 SET 片段集合。
|
||||
*/
|
||||
private final List<String> sqlSet;
|
||||
|
||||
/**
|
||||
* 查询字段 SQL 片段。
|
||||
*/
|
||||
private SharedString sqlSelect = new SharedString();
|
||||
|
||||
/**
|
||||
* 构造 Mapper 级 Lambda CRUD 链式包装器。
|
||||
*
|
||||
* @param crudMapper Mapper 对象
|
||||
*/
|
||||
public LambdaCrudChainWrapper(BaseMapperPlus<T, V> crudMapper) {
|
||||
this.crudMapper = crudMapper;
|
||||
super.setEntityClass(crudMapper.currentModelClass());
|
||||
super.initNeed();
|
||||
this.sqlSet = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 Mapper 级 Lambda CRUD 链式包装器实例。
|
||||
*
|
||||
* @param crudMapper Mapper 对象
|
||||
* @param entity 实体对象
|
||||
* @param entityClass 实体类型
|
||||
* @param sqlSelect 查询字段 SQL 片段
|
||||
* @param sqlSet 更新 set SQL 片段集合
|
||||
* @param paramNameSeq 参数名称序列
|
||||
* @param paramNameValuePairs 参数名称与参数值映射
|
||||
* @param mergeSegments 查询条件表达式
|
||||
* @param paramAlias 参数别名
|
||||
* @param lastSql SQL 尾部片段
|
||||
* @param sqlComment SQL 注释片段
|
||||
* @param sqlFirst SQL 起始片段
|
||||
*/
|
||||
LambdaCrudChainWrapper(BaseMapperPlus<T, V> crudMapper, T entity, Class<T> entityClass, SharedString sqlSelect,
|
||||
List<String> sqlSet, AtomicInteger paramNameSeq, Map<String, Object> paramNameValuePairs,
|
||||
MergeSegments mergeSegments, SharedString paramAlias, SharedString lastSql,
|
||||
SharedString sqlComment, SharedString sqlFirst) {
|
||||
this.crudMapper = crudMapper;
|
||||
super.setEntity(entity);
|
||||
super.setEntityClass(entityClass);
|
||||
this.sqlSelect = sqlSelect == null ? new SharedString() : sqlSelect;
|
||||
this.sqlSet = sqlSet == null ? new ArrayList<>() : sqlSet;
|
||||
this.paramNameSeq = paramNameSeq;
|
||||
this.paramNameValuePairs = paramNameValuePairs;
|
||||
this.expression = mergeSegments;
|
||||
this.paramAlias = paramAlias;
|
||||
this.lastSql = lastSql;
|
||||
this.sqlComment = sqlComment;
|
||||
this.sqlFirst = sqlFirst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件选择查询字段。
|
||||
*
|
||||
* @param condition 是否选择字段
|
||||
* @param columns 查询字段集合
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> select(boolean condition, List<SFunction<T, ?>> columns) {
|
||||
if (condition && CollectionUtils.isNotEmpty(columns)) {
|
||||
this.sqlSelect.setStringValue(columnsToString(false, columns));
|
||||
}
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择查询字段。
|
||||
*
|
||||
* @param columns 查询字段
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
@SafeVarargs
|
||||
public final LambdaCrudChainWrapper<T, V> select(SFunction<T, ?>... columns) {
|
||||
return select(true, CollectionUtils.toList(columns));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件选择查询字段。
|
||||
*
|
||||
* @param condition 是否选择字段
|
||||
* @param columns 查询字段
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
@SafeVarargs
|
||||
public final LambdaCrudChainWrapper<T, V> select(boolean condition, SFunction<T, ?>... columns) {
|
||||
return select(condition, CollectionUtils.toList(columns));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectSum(SFunction<T, ?> column) {
|
||||
return selectSum(column, AggregateSelectUtils.aliasName(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectSum(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.SUM, column, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectSum(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectSum(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectMax(SFunction<T, ?> column) {
|
||||
return selectMax(column, AggregateSelectUtils.aliasName(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectMax(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.MAX, column, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectMax(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectMax(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectMin(SFunction<T, ?> column) {
|
||||
return selectMin(column, AggregateSelectUtils.aliasName(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectMin(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.MIN, column, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectMin(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectMin(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectAvg(SFunction<T, ?> column) {
|
||||
return selectAvg(column, AggregateSelectUtils.aliasName(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectAvg(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.AVG, column, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectAvg(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectAvg(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectCount(SFunction<T, ?> column) {
|
||||
return selectCount(column, AggregateSelectUtils.aliasName(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectCount(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.COUNT, column, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectCount(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectCount(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(*) 聚合查询字段。
|
||||
*
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectCountAll(String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.COUNT, "*", alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(*) 聚合查询字段。
|
||||
*
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectCountAll(SFunction<A, ?> alias) {
|
||||
return selectCountAll(AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(DISTINCT column) 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> selectCountDistinct(SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(SqlAggregateFunction.COUNT, "DISTINCT " + columnToString(column), alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(DISTINCT column) 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <A> LambdaCrudChainWrapper<T, V> selectCountDistinct(SFunction<T, ?> column, SFunction<A, ?> alias) {
|
||||
return selectCountDistinct(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定子查询字段。
|
||||
* <pre>{@code
|
||||
* userMapper.lambda()
|
||||
* .select(SysUser::getUserId, SysUser::getUserName)
|
||||
* .selectSub(SysUserRole.class, sub -> sub
|
||||
* .selectCountAll()
|
||||
* .eqColumn(SysUserRole::getUserId, SysUser::getUserId),
|
||||
* UserStatVo::getRoleCount)
|
||||
* .voList();
|
||||
* }</pre>
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param alias 查询别名
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> selectSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer, String alias) {
|
||||
sqlSelect.setStringValue(AggregateSelectUtils.appendSelect(sqlSelect.getStringValue(),
|
||||
AggregateSelectUtils.subquerySelect(buildSubQuery(entityClass, consumer), alias)));
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定子查询字段。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param alias 查询别名字段
|
||||
* @param <S> 子查询实体类型
|
||||
* @param <A> 查询结果类型
|
||||
* @return this
|
||||
*/
|
||||
public <S, A> LambdaCrudChainWrapper<T, V> selectSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer, SFunction<A, ?> alias) {
|
||||
return selectSub(entityClass, consumer, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等于子查询条件。
|
||||
* <pre>{@code
|
||||
* userMapper.lambda()
|
||||
* .eqSub(SysUser::getDeptId, SysDept.class, sub -> sub
|
||||
* .select(SysDept::getDeptId)
|
||||
* .eq(SysDept::getDeptName, deptName))
|
||||
* .voList();
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> eqSub(SFunction<T, ?> column, Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
return super.eqSql(true, column, buildSubQuery(entityClass, consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 IN 子查询条件。
|
||||
* <pre>{@code
|
||||
* userMapper.lambda()
|
||||
* .inSub(SysUser::getUserId, SysUserRole.class, sub -> sub
|
||||
* .select(SysUserRole::getUserId)
|
||||
* .eq(SysUserRole::getRoleId, roleId))
|
||||
* .voList();
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> inSub(SFunction<T, ?> column, Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
return super.inSql(true, column, buildSubQuery(entityClass, consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 NOT IN 子查询条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> notInSub(SFunction<T, ?> column, Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
return super.notInSql(true, column, buildSubQuery(entityClass, consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 EXISTS 子查询条件。
|
||||
* <pre>{@code
|
||||
* userMapper.lambda()
|
||||
* .existsSub(SysUserRole.class, sub -> sub
|
||||
* .selectCountAll()
|
||||
* .eqColumn(SysUserRole::getUserId, SysUser::getUserId)
|
||||
* .eq(SysUserRole::getRoleId, roleId))
|
||||
* .voList();
|
||||
* }</pre>
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> existsSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
return super.exists(true, buildSubQuery(entityClass, consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 NOT EXISTS 子查询条件。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return this
|
||||
*/
|
||||
public <S> LambdaCrudChainWrapper<T, V> notExistsSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
return super.notExists(true, buildSubQuery(entityClass, consumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按字段过滤条件选择查询字段。
|
||||
*
|
||||
* @param entityClass 实体类型
|
||||
* @param predicate 字段过滤条件
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
|
||||
if (entityClass == null) {
|
||||
entityClass = getEntityClass();
|
||||
} else {
|
||||
setEntityClass(entityClass);
|
||||
}
|
||||
Assert.notNull(entityClass, "entityClass can not be null");
|
||||
this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(entityClass).chooseSelect(predicate));
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询字段 SQL 片段。
|
||||
*
|
||||
* @return 查询字段 SQL 片段
|
||||
*/
|
||||
@Override
|
||||
public String getSqlSelect() {
|
||||
return sqlSelect.getStringValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定聚合查询字段。
|
||||
*
|
||||
* @param function 聚合函数
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
private LambdaCrudChainWrapper<T, V> selectAggregate(SqlAggregateFunction function, SFunction<T, ?> column, String alias) {
|
||||
return selectAggregate(function, columnToString(column), alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定聚合查询字段。
|
||||
*
|
||||
* @param function 聚合函数
|
||||
* @param column 聚合字段 SQL
|
||||
* @param alias 查询别名
|
||||
* @return this
|
||||
*/
|
||||
private LambdaCrudChainWrapper<T, V> selectAggregate(SqlAggregateFunction function, String column, String alias) {
|
||||
sqlSelect.setStringValue(AggregateSelectUtils.appendSelect(sqlSelect.getStringValue(),
|
||||
AggregateSelectUtils.aggregateSelect(function, column, alias)));
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建子查询 SQL。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 子查询实体类型
|
||||
* @return 子查询 SQL
|
||||
*/
|
||||
private <S> String buildSubQuery(Class<S> entityClass, Consumer<SubQuery<S>> consumer) {
|
||||
SubQuery<S> subQuery = SubQuery.of(entityClass, value -> formatParam(null, value));
|
||||
consumer.accept(subQuery);
|
||||
return subQuery.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件设置更新字段。
|
||||
*
|
||||
* @param condition 是否设置该字段
|
||||
* @param column 字段
|
||||
* @param val 字段值
|
||||
* @param mapping 参数映射
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> set(boolean condition, SFunction<T, ?> column, Object val, String mapping) {
|
||||
return maybeDo(condition, () -> {
|
||||
String sql = formatParam(mapping, val);
|
||||
sqlSet.add(columnToString(column) + Constants.EQUALS + sql);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为 null 时设置更新字段。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> setIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return set(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空时设置更新字段。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> setIfText(SFunction<T, ?> column, String value) {
|
||||
return set(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件设置自定义 SQL 更新片段。
|
||||
*
|
||||
* @param condition 是否设置该片段
|
||||
* @param setSql SQL 更新片段
|
||||
* @param params SQL 片段参数
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> setSql(boolean condition, String setSql, Object... params) {
|
||||
return maybeDo(condition && StringUtils.isNotBlank(setSql), () -> sqlSet.add(formatSqlMaybeWithParam(setSql, params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件设置字段自增。
|
||||
*
|
||||
* @param condition 是否设置该字段
|
||||
* @param column 字段
|
||||
* @param val 自增值
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> setIncrBy(boolean condition, SFunction<T, ?> column, Number val) {
|
||||
return maybeDo(condition, () -> {
|
||||
String realColumn = columnToString(column);
|
||||
String realVal = val instanceof BigDecimal ? ((BigDecimal) val).toPlainString() : String.valueOf(val);
|
||||
sqlSet.add(String.format("%s=%s + %s", realColumn, realColumn, realVal));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件设置字段自减。
|
||||
*
|
||||
* @param condition 是否设置该字段
|
||||
* @param column 字段
|
||||
* @param val 自减值
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public LambdaCrudChainWrapper<T, V> setDecrBy(boolean condition, SFunction<T, ?> column, Number val) {
|
||||
return maybeDo(condition, () -> {
|
||||
String realColumn = columnToString(column);
|
||||
String realVal = val instanceof BigDecimal ? ((BigDecimal) val).toPlainString() : String.valueOf(val);
|
||||
sqlSet.add(String.format("%s=%s - %s", realColumn, realColumn, realVal));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新 set SQL 片段。
|
||||
*
|
||||
* @return 更新 set SQL 片段
|
||||
*/
|
||||
@Override
|
||||
public String getSqlSet() {
|
||||
if (CollectionUtils.isEmpty(sqlSet)) {
|
||||
return null;
|
||||
}
|
||||
return String.join(Constants.COMMA, sqlSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件 Wrapper。
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> getWrapper() {
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param value 匹配值
|
||||
* @param column 字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> findInSet(Object value, SFunction<T, ?> column) {
|
||||
return findInSet(true, value, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param value 匹配值
|
||||
* @param column 字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> findInSet(boolean condition, Object value, SFunction<T, ?> column) {
|
||||
return findInSet(condition, value, columnToString(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param value 匹配值
|
||||
* @param column 字段
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> findInSetIfPresent(Object value, SFunction<T, ?> column) {
|
||||
return findInSet(value != null, value, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Wrapper。
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public LambdaCrudChainWrapper<T, V> build() {
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询实体列表。
|
||||
*
|
||||
* @return 实体列表
|
||||
*/
|
||||
public List<T> list() {
|
||||
return crudMapper.selectList(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询实体分页记录。
|
||||
*
|
||||
* @param page 分页条件
|
||||
* @return 实体分页记录
|
||||
*/
|
||||
public List<T> list(IPage<T> page) {
|
||||
return crudMapper.selectList(page, typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 VO 列表。
|
||||
*
|
||||
* @return VO 列表
|
||||
*/
|
||||
public List<V> voList() {
|
||||
return crudMapper.selectVoList(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单列对象列表。
|
||||
*
|
||||
* @return 单列对象列表
|
||||
*/
|
||||
public List<Object> objs() {
|
||||
return crudMapper.selectObjs(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单列对象列表并转换类型。
|
||||
*
|
||||
* @param mapper 转换函数
|
||||
* @param <C> 转换后的类型
|
||||
* @return 单列对象列表
|
||||
*/
|
||||
public <C> List<C> objs(Function<? super Object, C> mapper) {
|
||||
return crudMapper.selectObjs(typedThis, mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个实体。
|
||||
*
|
||||
* @return 实体
|
||||
*/
|
||||
public T one() {
|
||||
return crudMapper.selectOne(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个实体。
|
||||
*
|
||||
* @param throwEx 查询到多条时是否抛异常
|
||||
* @return 实体
|
||||
*/
|
||||
public T one(boolean throwEx) {
|
||||
return crudMapper.selectOne(typedThis, throwEx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个实体 Optional。
|
||||
*
|
||||
* @return Optional 实体
|
||||
*/
|
||||
public Optional<T> oneOpt() {
|
||||
return Optional.ofNullable(one());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个 VO。
|
||||
*
|
||||
* @return VO
|
||||
*/
|
||||
public V voOne() {
|
||||
return crudMapper.selectVoOne(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个 VO。
|
||||
*
|
||||
* @param throwEx 查询到多条时是否抛异常
|
||||
* @return VO
|
||||
*/
|
||||
public V voOne(boolean throwEx) {
|
||||
return crudMapper.selectVoOne(typedThis, throwEx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数量。
|
||||
*
|
||||
* @return 数量
|
||||
*/
|
||||
public Long count() {
|
||||
return crudMapper.selectCount(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在。
|
||||
*
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean exists() {
|
||||
return crudMapper.exists(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询实体分页。
|
||||
*
|
||||
* @param page 分页条件
|
||||
* @param <P> 分页类型
|
||||
* @return 实体分页
|
||||
*/
|
||||
public <P extends IPage<T>> P page(P page) {
|
||||
return crudMapper.selectPage(page, typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 VO 分页。
|
||||
*
|
||||
* @param page 分页条件
|
||||
* @param <P> 分页类型
|
||||
* @return VO 分页
|
||||
*/
|
||||
public <P extends IPage<V>> P voPage(IPage<T> page) {
|
||||
return crudMapper.selectVoPage(page, typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据。
|
||||
*
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
public boolean delete() {
|
||||
return deleteCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据。
|
||||
*
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int deleteCount() {
|
||||
return crudMapper.delete(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 set 片段更新数据。
|
||||
*
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
public boolean update() {
|
||||
return updateCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用实体和查询条件更新数据。
|
||||
*
|
||||
* @param entity 实体
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
public boolean update(T entity) {
|
||||
return updateCount(entity) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 set 片段更新数据。
|
||||
*
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int updateCount() {
|
||||
return crudMapper.update(typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用实体和查询条件更新数据。
|
||||
*
|
||||
* @param entity 实体
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int updateCount(T entity) {
|
||||
return crudMapper.update(entity, typedThis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的链式包装器实例。
|
||||
*
|
||||
* @return 新的链式包装器实例
|
||||
*/
|
||||
@Override
|
||||
protected LambdaCrudChainWrapper<T, V> instance() {
|
||||
return new LambdaCrudChainWrapper<>(crudMapper, getEntity(), getEntityClass(), null, null, paramNameSeq,
|
||||
paramNameValuePairs, new MergeSegments(), paramAlias, SharedString.emptyString(), SharedString.emptyString(),
|
||||
SharedString.emptyString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空当前 Wrapper 状态。
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
sqlSelect.toNull();
|
||||
sqlSet.clear();
|
||||
}
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package org.dromara.common.mybatis.core.page;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.sql.SqlUtil;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页查询实体类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PageQuery implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 当前页数
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 排序列
|
||||
*/
|
||||
private String orderByColumn;
|
||||
|
||||
/**
|
||||
* 排序的方向desc或者asc
|
||||
*/
|
||||
private String isAsc;
|
||||
|
||||
/**
|
||||
* 当前记录起始索引 默认值
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_NUM = 1;
|
||||
|
||||
/**
|
||||
* 每页显示记录数 默认值 默认查全部
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 构建分页对象。
|
||||
*
|
||||
* @param <T> 分页记录类型
|
||||
* @return MyBatis-Plus 分页对象
|
||||
*/
|
||||
public <T> Page<T> build() {
|
||||
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
|
||||
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
|
||||
if (pageNum <= 0) {
|
||||
pageNum = DEFAULT_PAGE_NUM;
|
||||
}
|
||||
Page<T> page = new Page<>(pageNum, pageSize);
|
||||
List<OrderItem> orderItems = buildOrderItem();
|
||||
if (CollUtil.isNotEmpty(orderItems)) {
|
||||
page.addOrder(orderItems);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建排序
|
||||
* <p>
|
||||
* 支持的用法如下:
|
||||
* {isAsc:"asc",orderByColumn:"id"} order by id asc
|
||||
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
|
||||
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
|
||||
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
|
||||
*/
|
||||
private List<OrderItem> buildOrderItem() {
|
||||
if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
|
||||
return List.of();
|
||||
}
|
||||
String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
|
||||
orderBy = StringUtils.toUnderScoreCase(orderBy);
|
||||
|
||||
// 兼容前端排序类型
|
||||
String orderDirection = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
|
||||
|
||||
String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
|
||||
String[] isAscArr = orderDirection.split(StringUtils.SEPARATOR);
|
||||
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
|
||||
throw new ServiceException("排序参数有误");
|
||||
}
|
||||
|
||||
List<OrderItem> list = new ArrayList<>();
|
||||
// 每个字段各自排序
|
||||
for (int i = 0; i < orderByArr.length; i++) {
|
||||
String orderByStr = orderByArr[i];
|
||||
String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
|
||||
if ("asc".equals(isAscStr)) {
|
||||
list.add(OrderItem.asc(orderByStr));
|
||||
} else if ("desc".equals(isAscStr)) {
|
||||
list.add(OrderItem.desc(orderByStr));
|
||||
} else {
|
||||
throw new ServiceException("排序参数有误");
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页起始行号。
|
||||
*
|
||||
* @return 起始行号
|
||||
*/
|
||||
@JsonIgnore
|
||||
public Integer getFirstNum() {
|
||||
Integer currentPageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
|
||||
Integer currentPageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
|
||||
if (currentPageNum <= 0) {
|
||||
currentPageNum = DEFAULT_PAGE_NUM;
|
||||
}
|
||||
return (currentPageNum - 1) * currentPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造分页查询对象。
|
||||
*
|
||||
* @param pageSize 分页大小
|
||||
* @param pageNum 当前页码
|
||||
*/
|
||||
public PageQuery(Integer pageSize, Integer pageNum) {
|
||||
this.pageSize = pageSize;
|
||||
this.pageNum = pageNum;
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
|
||||
/**
|
||||
* 支持追加聚合查询字段的 Lambda 查询包装器。
|
||||
*
|
||||
* @param <T> 实体类型
|
||||
* @author Lion Li
|
||||
*/
|
||||
class AggregateLambdaQueryWrapper<T> extends LambdaQueryWrapper<T> {
|
||||
|
||||
/**
|
||||
* 追加后的聚合查询字段 SQL。
|
||||
*/
|
||||
private String aggregateSqlSelect;
|
||||
|
||||
/**
|
||||
* 构造聚合查询包装器。
|
||||
*
|
||||
* @param entityClass 实体类型
|
||||
*/
|
||||
AggregateLambdaQueryWrapper(Class<T> entityClass) {
|
||||
super(entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加聚合查询字段。
|
||||
*
|
||||
* @param columnSql 查询字段 SQL
|
||||
*/
|
||||
void appendSelectSql(String columnSql) {
|
||||
aggregateSqlSelect = AggregateSelectUtils.appendSelect(getSqlSelect(), columnSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加子查询字段。
|
||||
*
|
||||
* @param subquerySql 子查询 SQL
|
||||
* @param alias 查询别名
|
||||
* @param params 子查询参数
|
||||
*/
|
||||
void appendSelectSub(String subquerySql, String alias, Object... params) {
|
||||
appendSelectSql(AggregateSelectUtils.subquerySelect(formatSqlMaybeWithParam(subquerySql, params), alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化子查询 SQL。
|
||||
*
|
||||
* @param subquerySql 子查询 SQL
|
||||
* @param params 子查询参数
|
||||
* @return 格式化后的子查询 SQL
|
||||
*/
|
||||
String formatSubquerySql(String subquerySql, Object... params) {
|
||||
return formatSqlMaybeWithParam(subquerySql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化子查询参数。
|
||||
*
|
||||
* @param value 参数值
|
||||
* @return MyBatis 参数占位符
|
||||
*/
|
||||
String formatSubqueryParam(Object value) {
|
||||
return formatParam(null, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空聚合查询字段。
|
||||
*/
|
||||
void resetAggregateSelect() {
|
||||
aggregateSqlSelect = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段对应的数据库列名。
|
||||
*
|
||||
* @param column 字段
|
||||
* @return 数据库列名
|
||||
*/
|
||||
String columnName(SFunction<T, ?> column) {
|
||||
return columnToString(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最终查询字段 SQL。
|
||||
*
|
||||
* @return 查询字段 SQL
|
||||
*/
|
||||
@Override
|
||||
public String getSqlSelect() {
|
||||
if (aggregateSqlSelect != null) {
|
||||
return aggregateSqlSelect;
|
||||
}
|
||||
return super.getSqlSelect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空查询条件与聚合查询字段。
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
aggregateSqlSelect = null;
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Assert;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 聚合查询字段 SQL 构造工具。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class AggregateSelectUtils {
|
||||
|
||||
/**
|
||||
* 查询别名合法性匹配规则。
|
||||
*/
|
||||
private static final Pattern ALIAS_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||
|
||||
/**
|
||||
* 追加查询字段 SQL。
|
||||
*
|
||||
* @param current 已有查询字段 SQL
|
||||
* @param fragment 新增查询字段 SQL
|
||||
* @return 合并后的查询字段 SQL
|
||||
*/
|
||||
public static String appendSelect(String current, String fragment) {
|
||||
if (StringUtils.isBlank(current)) {
|
||||
return fragment;
|
||||
}
|
||||
return current + Constants.COMMA + fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成聚合查询字段 SQL。
|
||||
*
|
||||
* @param function 聚合函数
|
||||
* @param column 字段 SQL
|
||||
* @param alias 查询别名
|
||||
* @return 聚合查询字段 SQL
|
||||
*/
|
||||
public static String aggregateSelect(SqlAggregateFunction function, String column, String alias) {
|
||||
return function.format(column) + " AS " + checkAlias(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成子查询字段 SQL。
|
||||
*
|
||||
* @param subquerySql 子查询 SQL
|
||||
* @param alias 查询别名
|
||||
* @return 子查询字段 SQL
|
||||
*/
|
||||
public static String subquerySelect(String subquerySql, String alias) {
|
||||
return "(" + subquerySql + ") AS " + checkAlias(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Lambda Getter 解析属性名作为查询别名。
|
||||
*
|
||||
* @param alias 别名字段 Getter
|
||||
* @return 查询别名
|
||||
*/
|
||||
public static String aliasName(SFunction<?, ?> alias) {
|
||||
LambdaMeta meta = com.baomidou.mybatisplus.core.toolkit.LambdaUtils.extract(alias);
|
||||
return PropertyNamer.methodToProperty(meta.getImplMethodName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查查询别名是否为通用 SQL 标识符。
|
||||
*
|
||||
* @param alias 查询别名
|
||||
* @return 查询别名
|
||||
*/
|
||||
public static String checkAlias(String alias) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(alias) && ALIAS_PATTERN.matcher(alias).matches(),
|
||||
"查询别名只能包含字母、数字、下划线且不能以数字开头: %s", alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
}
|
||||
+946
@@ -0,0 +1,946 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import com.github.yulichang.toolkit.LambdaUtils;
|
||||
import com.github.yulichang.toolkit.support.ColumnCache;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import com.github.yulichang.wrapper.enums.DefaultFuncEnum;
|
||||
import com.github.yulichang.wrapper.segments.SelectCache;
|
||||
import com.github.yulichang.wrapper.segments.SelectNormal;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* MPJ Lambda 联表查询构造辅助类。
|
||||
*
|
||||
* @param <T> 主表实体类型
|
||||
* @author Lion Li
|
||||
*/
|
||||
public final class LambdaJoinQueryBuilder<T> {
|
||||
|
||||
/**
|
||||
* MyBatis-Plus-Join Lambda 查询包装器。
|
||||
*/
|
||||
private final MPJLambdaWrapper<T> wrapper;
|
||||
|
||||
/**
|
||||
* 构造 MPJ Lambda 联表查询构造辅助对象。
|
||||
*
|
||||
* @param wrapper MPJ Lambda 查询包装器
|
||||
*/
|
||||
LambdaJoinQueryBuilder(MPJLambdaWrapper<T> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加去重查询。
|
||||
*
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> distinct() {
|
||||
wrapper.distinct();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定主表查询字段。
|
||||
*
|
||||
* @param columns 查询字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final <E> LambdaJoinQueryBuilder<T> select(SFunction<E, ?>... columns) {
|
||||
wrapper.select(columns);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定带表别名的同名映射查询字段。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param columns 查询字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final <E> LambdaJoinQueryBuilder<T> select(String alias, SFunction<E, ?>... columns) {
|
||||
if (columns == null || columns.length == 0) {
|
||||
return this;
|
||||
}
|
||||
Class<?> entityClass = LambdaUtils.getEntityClass(columns[0]);
|
||||
Map<String, SelectCache> cacheMap = ColumnCache.getMapField(entityClass);
|
||||
for (SFunction<E, ?> column : columns) {
|
||||
wrapper.getSelectColum().add(new SelectNormal(cacheMap.get(LambdaUtils.getName(column)), wrapper.getIndex(), true, alias));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主表全部字段。
|
||||
*
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> selectAll() {
|
||||
wrapper.selectAll();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定实体全部字段。
|
||||
*
|
||||
* @param entityClass 实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> selectAll(Class<?> entityClass) {
|
||||
wrapper.selectAll(entityClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定别名实体全部字段。
|
||||
*
|
||||
* @param entityClass 实体类型
|
||||
* @param alias 表别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> selectAll(Class<?> entityClass, String alias) {
|
||||
wrapper.selectAll(entityClass, alias);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定查询字段并映射到返回对象字段。
|
||||
*
|
||||
* @param column 查询字段
|
||||
* @param alias 返回对象字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
wrapper.selectAs(column, alias);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定带表别名的查询字段并映射到返回对象字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 查询字段
|
||||
* @param alias 返回对象字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectAs(String tableAlias, SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
wrapper.selectAs(tableAlias, column, alias);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SQL 查询片段并映射到返回对象字段。
|
||||
*
|
||||
* @param column SQL 查询片段
|
||||
* @param alias 返回对象字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <X> LambdaJoinQueryBuilder<T> selectAs(String column, SFunction<X, ?> alias) {
|
||||
wrapper.selectAs(column, alias);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定子查询字段。
|
||||
* <pre>{@code
|
||||
* QueryBuilder.lambdaJoin("u", SysUser.class)
|
||||
* .selectAs("u", SysUser::getUserId, UserStatVo::getUserId)
|
||||
* .selectSub(SysUserRole.class, sub -> sub
|
||||
* .selectCountAll()
|
||||
* .eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId),
|
||||
* UserStatVo::getRoleCount)
|
||||
* .list(UserStatVo.class);
|
||||
* }</pre>
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param alias 查询别名
|
||||
* @param <S> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer, String alias) {
|
||||
SubQuery<S> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.selectFunc("(" + subQuery.build() + ")", func -> func.values(subQuery.params()),
|
||||
AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定子查询字段。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param alias 查询别名字段
|
||||
* @param <S> 子查询实体类型
|
||||
* @param <X> 查询结果类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectSub(Class<S> entityClass, Consumer<SubQuery<S>> consumer, SFunction<X, ?> alias) {
|
||||
return selectSub(entityClass, consumer, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectSum(SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectSum(column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectSum(String tableAlias, SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectFunc(DefaultFuncEnum.SUM, tableAlias, column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
return selectSum(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectMax(SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectMax(column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectMax(String tableAlias, SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectFunc(DefaultFuncEnum.MAX, tableAlias, column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
return selectMax(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectMin(SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectMin(column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectMin(String tableAlias, SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectFunc(DefaultFuncEnum.MIN, tableAlias, column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
return selectMin(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectAvg(SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectAvg(column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectAvg(String tableAlias, SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectFunc(DefaultFuncEnum.AVG, tableAlias, column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
return selectAvg(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectCount(SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectCount(column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param tableAlias 表别名
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> selectCount(String tableAlias, SFunction<S, ?> column, String alias) {
|
||||
wrapper.selectFunc(DefaultFuncEnum.COUNT, tableAlias, column, AggregateSelectUtils.checkAlias(alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {
|
||||
return selectCount(column, AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(*) 聚合查询字段。
|
||||
*
|
||||
* @param alias 查询别名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> selectCountAll(String alias) {
|
||||
wrapper.select(AggregateSelectUtils.aggregateSelect(SqlAggregateFunction.COUNT, "*", alias));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(*) 聚合查询字段。
|
||||
*
|
||||
* @param alias 查询别名字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <X> LambdaJoinQueryBuilder<T> selectCountAll(SFunction<X, ?> alias) {
|
||||
return selectCountAll(AggregateSelectUtils.aliasName(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等于子查询条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 字段实体类型
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, Q> LambdaJoinQueryBuilder<T> eqSub(String alias, SFunction<S, ?> column, Class<Q> entityClass,
|
||||
Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.apply(true, qualifiedColumn(alias, column) + " = (" + subQuery.build() + ")", subQuery.params());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 IN 子查询条件。
|
||||
* <pre>{@code
|
||||
* QueryBuilder.lambdaJoin("u", SysUser.class)
|
||||
* .inSub("u", SysUser::getUserId, SysUserRole.class, sub -> sub
|
||||
* .select(SysUserRole::getUserId)
|
||||
* .eq(SysUserRole::getRoleId, roleId))
|
||||
* .list(SysUserVo.class);
|
||||
* }</pre>
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 字段实体类型
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, Q> LambdaJoinQueryBuilder<T> inSub(String alias, SFunction<S, ?> column, Class<Q> entityClass,
|
||||
Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.apply(true, qualifiedColumn(alias, column) + " IN (" + subQuery.build() + ")", subQuery.params());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 NOT IN 子查询条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <S> 字段实体类型
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, Q> LambdaJoinQueryBuilder<T> notInSub(String alias, SFunction<S, ?> column, Class<Q> entityClass,
|
||||
Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.apply(true, qualifiedColumn(alias, column) + " NOT IN (" + subQuery.build() + ")", subQuery.params());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 EXISTS 子查询条件。
|
||||
* <pre>{@code
|
||||
* QueryBuilder.lambdaJoin("u", SysUser.class)
|
||||
* .existsSub(SysUserRole.class, sub -> sub
|
||||
* .selectCountAll()
|
||||
* .eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId)
|
||||
* .eq(SysUserRole::getRoleId, roleId))
|
||||
* .list(SysUserVo.class);
|
||||
* }</pre>
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <Q> LambdaJoinQueryBuilder<T> existsSub(Class<Q> entityClass, Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.apply(true, "EXISTS (" + subQuery.build() + ")", subQuery.params());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 NOT EXISTS 子查询条件。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造函数
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <Q> LambdaJoinQueryBuilder<T> notExistsSub(Class<Q> entityClass, Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = buildPlaceholderSubQuery(entityClass, consumer);
|
||||
wrapper.apply(true, "NOT EXISTS (" + subQuery.build() + ")", subQuery.params());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加左联表。
|
||||
*
|
||||
* @param entityClass 关联实体类型
|
||||
* @param left 关联实体字段
|
||||
* @param right 当前查询字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> leftJoin(Class<S> entityClass, SFunction<S, ?> left, SFunction<X, ?> right) {
|
||||
wrapper.leftJoin(entityClass, left, right);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加带别名的左联表。
|
||||
*
|
||||
* @param entityClass 关联实体类型
|
||||
* @param alias 关联表别名
|
||||
* @param left 关联实体字段
|
||||
* @param right 当前查询字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S, X> LambdaJoinQueryBuilder<T> leftJoin(Class<S> entityClass, String alias, SFunction<S, ?> left, SFunction<X, ?> right) {
|
||||
wrapper.leftJoin(entityClass, alias, left, right);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等于条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> eq(String alias, SFunction<S, ?> column, Object value) {
|
||||
return eq(true, alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> eq(boolean condition, String alias, SFunction<S, ?> column, Object value) {
|
||||
wrapper.eq(condition, alias, column, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加等于条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> eqIfPresent(String alias, SFunction<S, ?> column, Object value) {
|
||||
return eq(value != null, alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加等于条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> eqIfText(String alias, SFunction<S, ?> column, String value) {
|
||||
return eq(StringUtils.isNotBlank(value), alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加不等于条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> ne(String alias, SFunction<S, ?> column, Object value) {
|
||||
return ne(true, alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加不等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> ne(boolean condition, String alias, SFunction<S, ?> column, Object value) {
|
||||
wrapper.ne(condition, alias, column, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加不等于条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> neIfText(String alias, SFunction<S, ?> column, String value) {
|
||||
return ne(StringUtils.isNotBlank(value), alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加模糊匹配条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> like(String alias, SFunction<S, ?> column, Object value) {
|
||||
return like(true, alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加模糊匹配条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> like(boolean condition, String alias, SFunction<S, ?> column, Object value) {
|
||||
wrapper.like(condition, alias, column, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加模糊匹配条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> likeIfText(String alias, SFunction<S, ?> column, String value) {
|
||||
return like(StringUtils.isNotBlank(value), alias, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加区间条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> between(boolean condition, String alias, SFunction<S, ?> column, Object begin, Object end) {
|
||||
wrapper.between(condition, alias, column, begin, end);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 起止值均不为空时添加区间条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> betweenIfPresent(String alias, SFunction<S, ?> column, Object begin, Object end) {
|
||||
return between(begin != null && end != null, alias, column, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数 Map 中读取起止值,均不为空时添加区间条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param params 参数 Map
|
||||
* @param beginKey 起始值参数名
|
||||
* @param endKey 结束值参数名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> betweenParams(String alias, SFunction<S, ?> column, Map<String, Object> params, String beginKey, String endKey) {
|
||||
if (params == null) {
|
||||
return between(false, alias, column, null, null);
|
||||
}
|
||||
Object begin = params.get(beginKey);
|
||||
Object end = params.get(endKey);
|
||||
return between(begin != null && end != null, alias, column, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加包含集合条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> in(boolean condition, String alias, SFunction<S, ?> column, Collection<?> values) {
|
||||
wrapper.in(condition, alias, column, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加包含数组条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> in(boolean condition, String alias, SFunction<S, ?> column, Object... values) {
|
||||
wrapper.in(condition, alias, column, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加包含集合条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> in(String alias, SFunction<S, ?> column, Collection<?> values) {
|
||||
return in(true, alias, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加包含数组条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> in(String alias, SFunction<S, ?> column, Object... values) {
|
||||
return in(true, alias, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合不为空时添加包含条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> inIfNotEmpty(String alias, SFunction<S, ?> column, Collection<?> values) {
|
||||
return in(values != null && !values.isEmpty(), alias, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组不为空时添加包含条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> inIfNotEmpty(String alias, SFunction<S, ?> column, Object... values) {
|
||||
return in(values != null && values.length > 0, alias, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加不包含集合条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> notIn(boolean condition, String alias, SFunction<S, ?> column, Collection<?> values) {
|
||||
wrapper.notIn(condition, alias, column, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合不为空时添加不包含条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> notInIfNotEmpty(String alias, SFunction<S, ?> column, Collection<?> values) {
|
||||
return notIn(values != null && !values.isEmpty(), alias, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字段非空条件。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> isNotNull(String alias, SFunction<S, ?> column) {
|
||||
return isNotNull(true, alias, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字段非空条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> isNotNull(boolean condition, String alias, SFunction<S, ?> column) {
|
||||
wrapper.isNotNull(condition, alias, column);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加升序排序。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> orderByAsc(String alias, SFunction<S, ?> column) {
|
||||
wrapper.orderByAsc(alias, column);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加降序排序。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public <S> LambdaJoinQueryBuilder<T> orderByDesc(String alias, SFunction<S, ?> column) {
|
||||
wrapper.orderByDesc(alias, column);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接 SQL 片段条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param applySql SQL 片段
|
||||
* @param values SQL 片段参数
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> apply(boolean condition, String applySql, Object... values) {
|
||||
wrapper.apply(condition, applySql, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param value 匹配值
|
||||
* @param column 字段名
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> findInSet(boolean condition, Object value, String column) {
|
||||
return apply(condition, DataBaseHelper.findInSet(value, column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用函数式方式追加 MPJ 原生能力。
|
||||
*
|
||||
* @param consumer MPJ 查询包装器消费函数
|
||||
* @return 当前联表查询构造辅助对象
|
||||
*/
|
||||
public LambdaJoinQueryBuilder<T> apply(Consumer<MPJLambdaWrapper<T>> consumer) {
|
||||
consumer.accept(wrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表。
|
||||
*
|
||||
* @param resultClass 返回对象类型
|
||||
* @param <R> 返回对象类型
|
||||
* @return 查询结果
|
||||
*/
|
||||
public <R> List<R> list(Class<R> resultClass) {
|
||||
return wrapper.list(resultClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询。
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param resultClass 返回对象类型
|
||||
* @param <R> 返回对象类型
|
||||
* @param <P> 分页类型
|
||||
* @return 分页结果
|
||||
*/
|
||||
public <R, P extends IPage<R>> P page(P page, Class<R> resultClass) {
|
||||
return wrapper.page(page, resultClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数量。
|
||||
*
|
||||
* @return 数量
|
||||
*/
|
||||
public Long count() {
|
||||
return wrapper.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取底层 MPJ Lambda 查询包装器。
|
||||
*
|
||||
* @return MPJ Lambda 查询包装器
|
||||
*/
|
||||
public MPJLambdaWrapper<T> build() {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建使用占位参数模式的子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param consumer 子查询构造逻辑
|
||||
* @param <Q> 子查询实体类型
|
||||
* @return 子查询构造器
|
||||
*/
|
||||
private <Q> SubQuery<Q> buildPlaceholderSubQuery(Class<Q> entityClass, Consumer<SubQuery<Q>> consumer) {
|
||||
SubQuery<Q> subQuery = SubQuery.ofPlaceholders(entityClass);
|
||||
consumer.accept(subQuery);
|
||||
return subQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析带表别名的数据库列名。
|
||||
*
|
||||
* @param alias 表别名
|
||||
* @param column 字段引用
|
||||
* @param <S> 字段所属实体类型
|
||||
* @return 表别名限定列名
|
||||
*/
|
||||
private <S> String qualifiedColumn(String alias, SFunction<S, ?> column) {
|
||||
return AggregateSelectUtils.checkAlias(alias) + StringPool.DOT + ColumnCache.getMapField(LambdaUtils.getEntityClass(column))
|
||||
.get(LambdaUtils.getName(column)).getColumn();
|
||||
}
|
||||
|
||||
}
|
||||
+1392
File diff suppressed because it is too large
Load Diff
+443
@@ -0,0 +1,443 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lambda 查询常用条件扩展。
|
||||
*
|
||||
* @param <T> 实体类型
|
||||
* @param <Children> 链式返回类型
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface LambdaQueryCondition<T, Children> {
|
||||
|
||||
/**
|
||||
* 等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children eq(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 不等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children ne(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 大于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children gt(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 大于等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children ge(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 小于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children lt(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 小于等于条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children le(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 模糊匹配条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children like(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 非模糊匹配条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children notLike(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 左模糊匹配条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children likeLeft(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 右模糊匹配条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children likeRight(boolean condition, SFunction<T, ?> column, Object value);
|
||||
|
||||
/**
|
||||
* 区间条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children between(boolean condition, SFunction<T, ?> column, Object begin, Object end);
|
||||
|
||||
/**
|
||||
* 非区间条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children notBetween(boolean condition, SFunction<T, ?> column, Object begin, Object end);
|
||||
|
||||
/**
|
||||
* 包含集合条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children in(boolean condition, SFunction<T, ?> column, Collection<?> values);
|
||||
|
||||
/**
|
||||
* 包含数组条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children in(boolean condition, SFunction<T, ?> column, Object... values);
|
||||
|
||||
/**
|
||||
* 不包含集合条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children notIn(boolean condition, SFunction<T, ?> column, Collection<?> values);
|
||||
|
||||
/**
|
||||
* 不包含数组条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children notIn(boolean condition, SFunction<T, ?> column, Object... values);
|
||||
|
||||
/**
|
||||
* 拼接 SQL 片段条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param applySql SQL 片段
|
||||
* @param values SQL 片段参数
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
Children apply(boolean condition, String applySql, Object... values);
|
||||
|
||||
/**
|
||||
* 值不为空时添加等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children eqIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return eq(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children eqIfText(SFunction<T, ?> column, String value) {
|
||||
return eq(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加不等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children neIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return ne(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加不等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children neIfText(SFunction<T, ?> column, String value) {
|
||||
return ne(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加大于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children gtIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return gt(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加大于等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children geIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return ge(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加小于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children ltIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return lt(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加小于等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children leIfPresent(SFunction<T, ?> column, Object value) {
|
||||
return le(value != null, column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加模糊匹配条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children likeIfText(SFunction<T, ?> column, String value) {
|
||||
return like(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加非模糊匹配条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children notLikeIfText(SFunction<T, ?> column, String value) {
|
||||
return notLike(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加左模糊匹配条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children likeLeftIfText(SFunction<T, ?> column, String value) {
|
||||
return likeLeft(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本不为空白时添加右模糊匹配条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children likeRightIfText(SFunction<T, ?> column, String value) {
|
||||
return likeRight(StringUtils.isNotBlank(value), column, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 起止值均不为空时添加区间条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children betweenIfPresent(SFunction<T, ?> column, Object begin, Object end) {
|
||||
return between(begin != null && end != null, column, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数 Map 中读取起止值,均不为空时添加区间条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param params 参数 Map
|
||||
* @param beginKey 起始值参数名
|
||||
* @param endKey 结束值参数名
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children betweenParams(SFunction<T, ?> column, Map<String, Object> params, String beginKey, String endKey) {
|
||||
if (params == null) {
|
||||
return between(false, column, null, null);
|
||||
}
|
||||
Object begin = params.get(beginKey);
|
||||
Object end = params.get(endKey);
|
||||
return between(begin != null && end != null, column, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 起止值均不为空时添加非区间条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children notBetweenIfPresent(SFunction<T, ?> column, Object begin, Object end) {
|
||||
return notBetween(begin != null && end != null, column, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合不为空时添加包含条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children inIfNotEmpty(SFunction<T, ?> column, Collection<?> values) {
|
||||
return in(values != null && !values.isEmpty(), column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组不为空时添加包含条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children inIfNotEmpty(SFunction<T, ?> column, Object... values) {
|
||||
return in(values != null && values.length > 0, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合不为空时添加不包含条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children notInIfNotEmpty(SFunction<T, ?> column, Collection<?> values) {
|
||||
return notIn(values != null && !values.isEmpty(), column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组不为空时添加不包含条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children notInIfNotEmpty(SFunction<T, ?> column, Object... values) {
|
||||
return notIn(values != null && values.length > 0, column, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param value 匹配值
|
||||
* @param column 字段名
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children findInSet(Object value, String column) {
|
||||
return findInSet(true, value, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param condition 是否添加该条件
|
||||
* @param value 匹配值
|
||||
* @param column 字段名
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children findInSet(boolean condition, Object value, String column) {
|
||||
return apply(condition, DataBaseHelper.findInSet(value, column));
|
||||
}
|
||||
|
||||
/**
|
||||
* 值不为空时添加 FIND_IN_SET 条件。
|
||||
*
|
||||
* @param value 匹配值
|
||||
* @param column 字段名
|
||||
* @return 链式返回对象
|
||||
*/
|
||||
default Children findInSetIfPresent(Object value, String column) {
|
||||
return findInSet(value != null, value, column);
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 查询构造器入口。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public final class QueryBuilder {
|
||||
|
||||
/**
|
||||
* 工具入口类不允许实例化。
|
||||
*/
|
||||
private QueryBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Lambda 查询构造辅助对象。
|
||||
*
|
||||
* @param entityClass 实体类型
|
||||
* @param <T> 实体类型
|
||||
* @return Lambda 查询构造辅助对象
|
||||
*/
|
||||
public static <T> LambdaQueryBuilder<T> lambda(Class<T> entityClass) {
|
||||
return new LambdaQueryBuilder<>(new AggregateLambdaQueryWrapper<>(entityClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 MPJ Lambda 联表查询构造辅助对象。
|
||||
*
|
||||
* @param entityClass 主表实体类型
|
||||
* @param <T> 主表实体类型
|
||||
* @return MPJ Lambda 联表查询构造辅助对象
|
||||
*/
|
||||
public static <T> LambdaJoinQueryBuilder<T> lambdaJoin(Class<T> entityClass) {
|
||||
return new LambdaJoinQueryBuilder<>(JoinWrappers.lambda(entityClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带主表别名的 MPJ Lambda 联表查询构造辅助对象。
|
||||
*
|
||||
* @param alias 主表别名
|
||||
* @param entityClass 主表实体类型
|
||||
* @param <T> 主表实体类型
|
||||
* @return MPJ Lambda 联表查询构造辅助对象
|
||||
*/
|
||||
public static <T> LambdaJoinQueryBuilder<T> lambdaJoin(String alias, Class<T> entityClass) {
|
||||
return new LambdaJoinQueryBuilder<>(JoinWrappers.lambda(alias, entityClass));
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
/**
|
||||
* SQL 标准聚合函数。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public enum SqlAggregateFunction {
|
||||
|
||||
/**
|
||||
* 求和。
|
||||
*/
|
||||
SUM("SUM"),
|
||||
|
||||
/**
|
||||
* 最大值。
|
||||
*/
|
||||
MAX("MAX"),
|
||||
|
||||
/**
|
||||
* 最小值。
|
||||
*/
|
||||
MIN("MIN"),
|
||||
|
||||
/**
|
||||
* 平均值。
|
||||
*/
|
||||
AVG("AVG"),
|
||||
|
||||
/**
|
||||
* 计数。
|
||||
*/
|
||||
COUNT("COUNT");
|
||||
|
||||
/**
|
||||
* 聚合函数名称。
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 构造 SQL 聚合函数。
|
||||
*
|
||||
* @param name 聚合函数名称
|
||||
*/
|
||||
SqlAggregateFunction(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成聚合函数 SQL 片段。
|
||||
*
|
||||
* @param expression 函数入参表达式
|
||||
* @return 聚合函数 SQL
|
||||
*/
|
||||
public String format(String expression) {
|
||||
return name + "(" + expression + ")";
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
/**
|
||||
* SQL 参数格式化器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SqlParamFormatter {
|
||||
|
||||
/**
|
||||
* 格式化 SQL 参数。
|
||||
*
|
||||
* @param value 参数值
|
||||
* @return MyBatis 参数占位符
|
||||
*/
|
||||
String format(Object value);
|
||||
|
||||
}
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
package org.dromara.common.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.*;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Lambda 子查询构造器。
|
||||
* <p>
|
||||
* 常用于外层查询的 {@code selectSub}、{@code inSub}、{@code existsSub} 等方法中。
|
||||
* </p>
|
||||
* <p>
|
||||
* 注意:子查询 SQL 由本构造器直接生成,默认会根据 MyBatis-Plus 表元数据追加逻辑删除条件,
|
||||
* 但不会自动追加项目数据权限条件;如果子查询实体也需要数据权限过滤,请在子查询条件中显式添加。
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* userMapper.lambda()
|
||||
* .inSub(SysUser::getUserId, SysUserRole.class, sub -> sub
|
||||
* .select(SysUserRole::getUserId)
|
||||
* .eq(SysUserRole::getRoleId, roleId))
|
||||
* .voList();
|
||||
*
|
||||
* userMapper.lambda()
|
||||
* .select(SysUser::getUserId, SysUser::getUserName)
|
||||
* .selectSub(SysUserRole.class, sub -> sub
|
||||
* .selectCountAll()
|
||||
* .eqColumn(SysUserRole::getUserId, SysUser::getUserId),
|
||||
* UserStatVo::getRoleCount)
|
||||
* .voList();
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> 子查询实体类型
|
||||
* @author Lion Li
|
||||
*/
|
||||
public final class SubQuery<T> {
|
||||
|
||||
/**
|
||||
* 子查询实体类型。
|
||||
*/
|
||||
private final Class<T> entityClass;
|
||||
|
||||
/**
|
||||
* 外层查询传入的 SQL 参数格式化器。
|
||||
*/
|
||||
private final SqlParamFormatter paramFormatter;
|
||||
|
||||
/**
|
||||
* 是否使用 {@code {0}} 形式的占位参数模式。
|
||||
*/
|
||||
private final boolean placeholderParamMode;
|
||||
|
||||
/**
|
||||
* 子查询 SELECT 字段集合。
|
||||
*/
|
||||
private final List<String> selects = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 子查询 WHERE 条件集合。
|
||||
*/
|
||||
private final List<String> conditions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 占位参数模式下收集的参数值集合。
|
||||
*/
|
||||
private final List<Object> params = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 是否追加逻辑删除条件。
|
||||
*/
|
||||
private boolean withLogicDelete = true;
|
||||
|
||||
/**
|
||||
* 构造子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param paramFormatter SQL 参数格式化器
|
||||
*/
|
||||
private SubQuery(Class<T> entityClass, SqlParamFormatter paramFormatter) {
|
||||
this(entityClass, paramFormatter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param paramFormatter SQL 参数格式化器
|
||||
* @param placeholderParamMode 是否使用占位参数模式
|
||||
*/
|
||||
private SubQuery(Class<T> entityClass, SqlParamFormatter paramFormatter, boolean placeholderParamMode) {
|
||||
this.entityClass = entityClass;
|
||||
this.paramFormatter = paramFormatter;
|
||||
this.placeholderParamMode = placeholderParamMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param <T> 子查询实体类型
|
||||
* @return 子查询构造器
|
||||
*/
|
||||
public static <T> SubQuery<T> of(Class<T> entityClass) {
|
||||
return new SubQuery<>(entityClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param paramFormatter SQL 参数格式化器
|
||||
* @param <T> 子查询实体类型
|
||||
* @return 子查询构造器
|
||||
*/
|
||||
public static <T> SubQuery<T> of(Class<T> entityClass, SqlParamFormatter paramFormatter) {
|
||||
return new SubQuery<>(entityClass, paramFormatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建使用 {@code {0}} 参数占位符的子查询。
|
||||
*
|
||||
* @param entityClass 子查询实体类型
|
||||
* @param <T> 子查询实体类型
|
||||
* @return 子查询构造器
|
||||
*/
|
||||
static <T> SubQuery<T> ofPlaceholders(Class<T> entityClass) {
|
||||
return new SubQuery<>(entityClass, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定子查询字段。
|
||||
* <pre>{@code
|
||||
* sub.select(SysUserRole::getUserId)
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 查询字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> select(SFunction<T, ?> column) {
|
||||
selects.add(columnName(column));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT(*) 查询字段。
|
||||
* <pre>{@code
|
||||
* sub.selectCountAll()
|
||||
* }</pre>
|
||||
*
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectCountAll() {
|
||||
selects.add(SqlAggregateFunction.COUNT.format("*"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 SUM 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectSum(SFunction<T, ?> column) {
|
||||
return selectAggregate(SqlAggregateFunction.SUM, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MAX 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectMax(SFunction<T, ?> column) {
|
||||
return selectAggregate(SqlAggregateFunction.MAX, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 MIN 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectMin(SFunction<T, ?> column) {
|
||||
return selectAggregate(SqlAggregateFunction.MIN, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 AVG 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectAvg(SFunction<T, ?> column) {
|
||||
return selectAggregate(SqlAggregateFunction.AVG, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 COUNT 聚合查询字段。
|
||||
*
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> selectCount(SFunction<T, ?> column) {
|
||||
return selectAggregate(SqlAggregateFunction.COUNT, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用子查询逻辑删除条件。
|
||||
* <pre>{@code
|
||||
* sub.disableLogicDelete()
|
||||
* }</pre>
|
||||
*
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> disableLogicDelete() {
|
||||
this.withLogicDelete = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等于条件。
|
||||
* <pre>{@code
|
||||
* sub.eq(SysUserRole::getRoleId, roleId)
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> eq(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, Constants.EQUALS, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加大于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> gt(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, ">", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加大于等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> ge(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, ">=", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加小于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> lt(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, "<", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加小于等于条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> le(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, "<=", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加模糊匹配条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> like(SFunction<T, ?> column, Object value) {
|
||||
return condition(column, "LIKE", "%" + value + "%");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 IN 条件。
|
||||
* <pre>{@code
|
||||
* sub.in(SysUserRole::getRoleId, roleIds)
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值集合
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> in(SFunction<T, ?> column, Collection<?> values) {
|
||||
if (CollectionUtils.isEmpty(values)) {
|
||||
return this;
|
||||
}
|
||||
conditions.add(columnName(column) + " IN (" + values.stream()
|
||||
.map(this::formatParam)
|
||||
.collect(Collectors.joining(Constants.COMMA)) + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 IN 条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param values 条件值数组
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> in(SFunction<T, ?> column, Object... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return this;
|
||||
}
|
||||
return in(column, Arrays.asList(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 BETWEEN 条件。
|
||||
*
|
||||
* @param column 字段
|
||||
* @param begin 起始值
|
||||
* @param end 结束值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> between(SFunction<T, ?> column, Object begin, Object end) {
|
||||
conditions.add(columnName(column) + " BETWEEN " + formatParam(begin) + " AND " + formatParam(end));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字段相等条件,用于关联外层查询字段。
|
||||
* <pre>{@code
|
||||
* sub.eqColumn(SysUserRole::getUserId, SysUser::getUserId)
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 子查询字段
|
||||
* @param otherColumn 其他表字段
|
||||
* @param <O> 其他表实体类型
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public <O> SubQuery<T> eqColumn(SFunction<T, ?> column, SFunction<O, ?> otherColumn) {
|
||||
conditions.add(columnName(column) + Constants.EQUALS + qualifiedColumnName(otherColumn));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字段相等条件,用于关联外层查询字段。
|
||||
* <pre>{@code
|
||||
* sub.eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId)
|
||||
* }</pre>
|
||||
*
|
||||
* @param column 子查询字段
|
||||
* @param tableAlias 其他表别名
|
||||
* @param otherColumn 其他表字段
|
||||
* @param <O> 其他表实体类型
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public <O> SubQuery<T> eqColumn(SFunction<T, ?> column, String tableAlias, SFunction<O, ?> otherColumn) {
|
||||
conditions.add(columnName(column) + Constants.EQUALS + AggregateSelectUtils.checkAlias(tableAlias)
|
||||
+ StringPool.DOT + columnName(otherColumn));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件添加子查询条件。
|
||||
*
|
||||
* @param condition 是否添加
|
||||
* @param consumer 子查询条件
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
public SubQuery<T> when(boolean condition, Consumer<SubQuery<T>> consumer) {
|
||||
if (condition) {
|
||||
consumer.accept(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建子查询 SQL。
|
||||
*
|
||||
* @return 子查询 SQL
|
||||
*/
|
||||
public String build() {
|
||||
Assert.notEmpty(selects, "子查询必须指定查询字段");
|
||||
String sql = "SELECT " + String.join(Constants.COMMA, selects) + " FROM " + tableName();
|
||||
List<String> whereConditions = buildWhereConditions();
|
||||
if (!whereConditions.isEmpty()) {
|
||||
sql += " WHERE " + String.join(" AND ", whereConditions);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子查询参数。
|
||||
*
|
||||
* @return 子查询参数数组
|
||||
*/
|
||||
Object[] params() {
|
||||
return params.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加聚合查询字段。
|
||||
*
|
||||
* @param function 聚合函数
|
||||
* @param column 聚合字段
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
private SubQuery<T> selectAggregate(SqlAggregateFunction function, SFunction<T, ?> column) {
|
||||
selects.add(function.format(columnName(column)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加普通比较条件。
|
||||
*
|
||||
* @param column 条件字段
|
||||
* @param operator 比较操作符
|
||||
* @param value 条件值
|
||||
* @return 当前子查询构造器
|
||||
*/
|
||||
private SubQuery<T> condition(SFunction<T, ?> column, String operator, Object value) {
|
||||
conditions.add(columnName(column) + StringPool.SPACE + operator + StringPool.SPACE + formatParam(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 SQL 参数。
|
||||
*
|
||||
* @param value 参数值
|
||||
* @return SQL 参数占位符
|
||||
*/
|
||||
private String formatParam(Object value) {
|
||||
if (placeholderParamMode) {
|
||||
params.add(value);
|
||||
return "{" + (params.size() - 1) + "}";
|
||||
}
|
||||
Assert.notNull(paramFormatter, "子查询参数需要在外层查询方法中构造");
|
||||
return paramFormatter.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子查询实体表名。
|
||||
*
|
||||
* @return 表名
|
||||
*/
|
||||
private String tableName() {
|
||||
return tableInfo().getTableName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建最终 WHERE 条件集合。
|
||||
*
|
||||
* @return WHERE 条件集合
|
||||
*/
|
||||
private List<String> buildWhereConditions() {
|
||||
List<String> whereConditions = new ArrayList<>();
|
||||
String logicDeleteSql = logicDeleteSql();
|
||||
if (StringUtils.isNotBlank(logicDeleteSql)) {
|
||||
whereConditions.add(logicDeleteSql);
|
||||
}
|
||||
whereConditions.addAll(conditions);
|
||||
return whereConditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取逻辑删除 SQL 条件。
|
||||
*
|
||||
* @return 逻辑删除 SQL 条件,禁用时返回空字符串
|
||||
*/
|
||||
private String logicDeleteSql() {
|
||||
if (!withLogicDelete) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
return tableInfo().getLogicDeleteSql(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子查询实体对应的 MyBatis-Plus 表信息。
|
||||
*
|
||||
* @return 表信息
|
||||
*/
|
||||
private TableInfo tableInfo() {
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
|
||||
Assert.notNull(tableInfo, "无法获取实体表信息: %s", entityClass.getName());
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取外层查询字段的表名限定列名。
|
||||
*
|
||||
* @param column 外层查询字段
|
||||
* @return 表名限定列名
|
||||
*/
|
||||
private String qualifiedColumnName(SFunction<?, ?> column) {
|
||||
Class<?> columnEntityClass = LambdaUtils.extract(column).getInstantiatedClass();
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(columnEntityClass);
|
||||
Assert.notNull(tableInfo, "无法获取实体表信息: %s", columnEntityClass.getName());
|
||||
return tableInfo.getTableName() + StringPool.DOT + columnName(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Lambda 字段引用解析数据库列名。
|
||||
*
|
||||
* @param column 字段引用
|
||||
* @return 数据库列名
|
||||
*/
|
||||
private static String columnName(SFunction<?, ?> column) {
|
||||
LambdaMeta meta = LambdaUtils.extract(column);
|
||||
String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
|
||||
Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap(meta.getInstantiatedClass());
|
||||
Assert.notNull(columnMap, "can not find lambda cache for this entity [%s]", meta.getInstantiatedClass().getName());
|
||||
ColumnCache cache = columnMap.get(LambdaUtils.formatKey(fieldName));
|
||||
Assert.notNull(cache, "can not find lambda cache for this property [%s]", fieldName);
|
||||
return cache.getColumn();
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.common.mybatis.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataBaseType {
|
||||
|
||||
/**
|
||||
* MySQL
|
||||
*/
|
||||
MY_SQL("MySQL"),
|
||||
|
||||
/**
|
||||
* Oracle
|
||||
*/
|
||||
ORACLE("Oracle"),
|
||||
|
||||
/**
|
||||
* PostgreSQL
|
||||
*/
|
||||
POSTGRE_SQL("PostgreSQL"),
|
||||
|
||||
/**
|
||||
* SQL Server
|
||||
*/
|
||||
SQL_SERVER("Microsoft SQL Server");
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* 根据数据库产品名称查找对应的数据库类型
|
||||
*
|
||||
* @param databaseProductName 数据库产品名称
|
||||
* @return 对应的数据库类型枚举值
|
||||
*/
|
||||
public static DataBaseType find(String databaseProductName) {
|
||||
if (StringUtils.isBlank(databaseProductName)) {
|
||||
return MY_SQL;
|
||||
}
|
||||
for (DataBaseType type : values()) {
|
||||
if (type.getType().equals(databaseProductName)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return MY_SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 MySQL 类型
|
||||
*/
|
||||
public boolean isMySql() {
|
||||
return this == MY_SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 Oracle 类型
|
||||
*/
|
||||
public boolean isOracle() {
|
||||
return this == ORACLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 PostgreSQL 类型
|
||||
*/
|
||||
public boolean isPostgreSql() {
|
||||
return this == POSTGRE_SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 SQL Server 类型
|
||||
*/
|
||||
public boolean isSqlServer() {
|
||||
return this == SQL_SERVER;
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package org.dromara.common.mybatis.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
|
||||
/**
|
||||
* 数据权限类型枚举
|
||||
* <p>
|
||||
* 支持使用 SpEL 模板表达式定义 SQL 查询条件
|
||||
* 内置数据:
|
||||
* - {@code user}: 当前登录用户信息,参考 {@link LoginUser}
|
||||
* 内置服务:
|
||||
* - {@code sdss}: 系统数据权限服务,参考 ISysDataScopeService
|
||||
* 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
|
||||
* 如需扩展服务,可以通过 ISysDataScopeService 自行编写
|
||||
* </p>
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataScopeType {
|
||||
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
ALL("1", "", ""),
|
||||
|
||||
/**
|
||||
* 自定数据权限
|
||||
*/
|
||||
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #roleId )} ) ", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 部门数据权限
|
||||
*/
|
||||
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 部门及以下数据权限
|
||||
*/
|
||||
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
|
||||
|
||||
/**
|
||||
* 部门及以下或本人数据权限
|
||||
*/
|
||||
DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
|
||||
|
||||
/**
|
||||
* 数据权限类型编码。
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* SpEL 模板表达式,用于构建 SQL 查询条件
|
||||
*/
|
||||
private final String sqlTemplate;
|
||||
|
||||
/**
|
||||
* 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式
|
||||
*/
|
||||
private final String elseSql;
|
||||
|
||||
/**
|
||||
* 根据枚举代码查找对应的枚举值
|
||||
*
|
||||
* @param code 枚举代码
|
||||
* @return 对应的枚举值,如果未找到则返回 null
|
||||
*/
|
||||
public static DataScopeType findCode(String code) {
|
||||
if (StringUtils.isBlank(code)) {
|
||||
return null;
|
||||
}
|
||||
for (DataScopeType type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package org.dromara.common.mybatis.handler;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MP注入处理器
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021/4/25
|
||||
*/
|
||||
@Slf4j
|
||||
public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 如果用户不存在默认注入-1代表无用户
|
||||
*/
|
||||
private static final Long DEFAULT_USER_ID = -1L;
|
||||
|
||||
/**
|
||||
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
|
||||
*
|
||||
* @param metaObject 元对象,用于获取原始对象并进行填充
|
||||
*/
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
try {
|
||||
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
|
||||
// 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
|
||||
LocalDateTime current = ObjectUtils.notNull(baseEntity.getCreateTime(), LocalDateTime.now());
|
||||
baseEntity.setCreateTime(current);
|
||||
baseEntity.setUpdateTime(current);
|
||||
|
||||
// 如果创建人为空,则填充当前登录用户的信息
|
||||
if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (ObjectUtil.isNotNull(loginUser)) {
|
||||
Long userId = loginUser.getUserId();
|
||||
// 填充创建人、更新人和创建部门信息
|
||||
baseEntity.setCreateBy(userId);
|
||||
baseEntity.setUpdateBy(userId);
|
||||
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
|
||||
} else {
|
||||
// 填充创建人、更新人和创建部门信息
|
||||
baseEntity.setCreateBy(DEFAULT_USER_ID);
|
||||
baseEntity.setUpdateBy(DEFAULT_USER_ID);
|
||||
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LocalDateTime date = LocalDateTime.now();
|
||||
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, date);
|
||||
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, date);
|
||||
Date legacyDate = new Date();
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, legacyDate);
|
||||
this.strictInsertFill(metaObject, "updateTime", Date.class, legacyDate);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
|
||||
*
|
||||
* @param metaObject 元对象,用于获取原始对象并进行填充
|
||||
*/
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
try {
|
||||
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
|
||||
// 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
|
||||
LocalDateTime current = LocalDateTime.now();
|
||||
baseEntity.setUpdateTime(current);
|
||||
|
||||
// 获取当前登录用户的ID,并填充更新人信息
|
||||
LoginUser loginUser = getLoginUser();
|
||||
Long userId = ObjectUtil.isNotNull(loginUser) ? loginUser.getUserId() : DEFAULT_USER_ID;
|
||||
baseEntity.setUpdateBy(userId);
|
||||
} else {
|
||||
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @return 当前登录用户的信息,如果用户未登录则返回 null
|
||||
*/
|
||||
private LoginUser getLoginUser() {
|
||||
LoginUser loginUser;
|
||||
try {
|
||||
loginUser = LoginHelper.getLoginUser();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.dromara.common.mybatis.handler;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.mybatis.spring.MyBatisSystemException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* Mybatis异常处理器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class MybatisExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理主键或唯一索引冲突异常。
|
||||
*
|
||||
* @param e 异常信息
|
||||
* @param request 当前请求
|
||||
* @return 统一失败响应
|
||||
*/
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 MyBatis 系统异常。
|
||||
*
|
||||
* @param e 异常信息
|
||||
* @param request 当前请求
|
||||
* @return 统一失败响应
|
||||
*/
|
||||
@ExceptionHandler(MyBatisSystemException.class)
|
||||
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
Throwable root = getRootCause(e);
|
||||
if (root instanceof NotLoginException) {
|
||||
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, root.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
|
||||
}
|
||||
if (root instanceof CannotFindDataSourceException) {
|
||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||
}
|
||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常的根因(递归查找)
|
||||
*
|
||||
* @param e 当前异常
|
||||
* @return 根因异常(最底层的 cause)
|
||||
* <p>
|
||||
* 逻辑说明:
|
||||
* 1. 如果 e 没有 cause,说明 e 本身就是根因,直接返回
|
||||
* 2. 如果 e 的 cause 和自身相同(防止循环引用),也返回 e
|
||||
* 3. 否则递归调用,继续向下寻找最底层的 cause
|
||||
*/
|
||||
public static Throwable getRootCause(Throwable e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause == null || cause == e) {
|
||||
return e;
|
||||
}
|
||||
return getRootCause(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在异常链中查找指定类型的异常
|
||||
*
|
||||
* @param e 当前异常
|
||||
* @param clazz 目标异常类
|
||||
* @return 找到的指定类型异常,如果没有找到返回 null
|
||||
*/
|
||||
public static Throwable findCause(Throwable e, Class<? extends Throwable> clazz) {
|
||||
Throwable t = e;
|
||||
while (t != null && t != t.getCause()) {
|
||||
if (clazz.isInstance(t)) {
|
||||
return t;
|
||||
}
|
||||
t = t.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
package org.dromara.common.mybatis.handler;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.domain.DataPermissionAccess;
|
||||
import org.dromara.common.mybatis.enums.DataScopeType;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.api.domain.RoleDTO;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.expression.*;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 数据权限过滤
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlusDataPermissionHandler {
|
||||
|
||||
/**
|
||||
* spel 解析器
|
||||
*/
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* SpEL 模板解析上下文。
|
||||
*/
|
||||
private final ParserContext parserContext = new TemplateParserContext();
|
||||
|
||||
/**
|
||||
* bean解析器 用于处理 spel 表达式中对 bean 的调用
|
||||
*/
|
||||
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
|
||||
|
||||
/**
|
||||
* 获取数据过滤条件的 SQL 片段
|
||||
*
|
||||
* @param where 原始的查询条件表达式
|
||||
* @param isSelect 是否为查询语句
|
||||
* @return 数据过滤条件的 SQL 片段
|
||||
*/
|
||||
public Expression getSqlSegment(Expression where, boolean isSelect) {
|
||||
try {
|
||||
LoginUser currentUser = currentUser();
|
||||
// 如果是超级管理员或租户管理员,则不过滤数据
|
||||
if (LoginHelper.isSuperAdmin()) {
|
||||
return where;
|
||||
}
|
||||
// 构造数据过滤条件的 SQL 片段
|
||||
String dataFilterSql = buildDataFilter(getDataPermission(), currentUser, isSelect);
|
||||
if (StringUtils.isBlank(dataFilterSql)) {
|
||||
return where;
|
||||
}
|
||||
Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
|
||||
// 数据权限使用单独的括号 防止与其他条件冲突
|
||||
ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
|
||||
if (ObjectUtil.isNotNull(where)) {
|
||||
return new AndExpression(where, parenthesis);
|
||||
} else {
|
||||
return parenthesis;
|
||||
}
|
||||
} catch (JSQLParserException e) {
|
||||
throw new ServiceException("数据权限解析异常 => " + e.getMessage());
|
||||
} finally {
|
||||
DataPermissionHelper.removePermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建数据过滤条件的 SQL 语句
|
||||
*
|
||||
* @param dataPermission 数据权限注解
|
||||
* @param isSelect 标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
|
||||
* @return 构建的数据过滤条件的 SQL 语句
|
||||
* @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常
|
||||
*/
|
||||
private String buildDataFilter(DataPermission dataPermission, LoginUser user, boolean isSelect) {
|
||||
// 更新或删除需满足所有条件
|
||||
String joinStr = isSelect ? " OR " : " AND ";
|
||||
if (StringUtils.isNotBlank(dataPermission.joinStr())) {
|
||||
joinStr = " " + dataPermission.joinStr() + " ";
|
||||
}
|
||||
Object defaultValue = "-1";
|
||||
NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
|
||||
context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().getFirst(), defaultValue));
|
||||
context.setBeanResolver(beanResolver);
|
||||
DataPermissionHelper.getContext().forEach(context::setVariable);
|
||||
Set<String> conditions = new HashSet<>();
|
||||
DataPermissionAccess access = currentAccess();
|
||||
List<RoleDTO> scopeRoles = scopeRoles(user, access);
|
||||
if (CollUtil.isEmpty(scopeRoles)) {
|
||||
if (access.constrained()) {
|
||||
return " 1 = 0 ";
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
// 优先设置变量
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (DataColumn dataColumn : dataPermission.value()) {
|
||||
if (dataColumn.key().length != dataColumn.value().length) {
|
||||
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
|
||||
}
|
||||
// 设置注解变量 key 为表达式变量 value 为变量值
|
||||
for (int i = 0; i < dataColumn.key().length; i++) {
|
||||
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
|
||||
}
|
||||
keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
|
||||
}
|
||||
|
||||
for (RoleDTO role : scopeRoles) {
|
||||
context.setVariable("roleId", role.getRoleId());
|
||||
// 获取角色权限泛型
|
||||
DataScopeType type = DataScopeType.findCode(role.getDataScope());
|
||||
if (ObjectUtil.isNull(type)) {
|
||||
throw new ServiceException("角色数据范围异常 => " + role.getDataScope());
|
||||
}
|
||||
// 全部数据权限直接返回
|
||||
if (type == DataScopeType.ALL) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
boolean isSuccess = false;
|
||||
for (DataColumn dataColumn : dataPermission.value()) {
|
||||
// 不包含 key 变量 则不处理
|
||||
if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
|
||||
continue;
|
||||
}
|
||||
// 当前注解不满足模板 不处理
|
||||
if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
|
||||
continue;
|
||||
}
|
||||
// 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
|
||||
String sql = DataPermissionHelper.ignore(() ->
|
||||
parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
|
||||
);
|
||||
// 解析sql模板并填充
|
||||
conditions.add(joinStr + sql);
|
||||
isSuccess = true;
|
||||
}
|
||||
// 未处理成功则填充兜底方案
|
||||
if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
|
||||
conditions.add(joinStr + type.getElseSql());
|
||||
}
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(conditions)) {
|
||||
String sql = StreamUtils.join(conditions, Function.identity(), "");
|
||||
return sql.substring(joinStr.length());
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @return 当前登录用户的LoginUser对象,可能为null(如未登录场景)
|
||||
*/
|
||||
private LoginUser currentUser() {
|
||||
// 从数据权限助手缓存中获取当前登录用户
|
||||
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
||||
if (ObjectUtil.isNull(currentUser)) {
|
||||
currentUser = LoginHelper.getLoginUser();
|
||||
DataPermissionHelper.setVariable("user", currentUser);
|
||||
}
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求已解析的数据权限访问控制对象。
|
||||
*
|
||||
* @return 数据权限访问控制对象
|
||||
*/
|
||||
private DataPermissionAccess currentAccess() {
|
||||
DataPermissionAccess access = DataPermissionHelper.getAccess();
|
||||
if (access != null) {
|
||||
return access;
|
||||
}
|
||||
DataPermissionAccess resolvedAccess = resolveAccess();
|
||||
DataPermissionHelper.setAccess(resolvedAccess);
|
||||
return resolvedAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前接口权限约束筛选参与数据权限计算的角色。
|
||||
*
|
||||
* @param user 当前登录用户
|
||||
* @param access 当前接口访问约束
|
||||
* @return 参与数据权限计算的角色集合
|
||||
*/
|
||||
private List<RoleDTO> scopeRoles(LoginUser user, DataPermissionAccess access) {
|
||||
List<RoleDTO> roles = user.getRoles();
|
||||
if (!access.constrained()) {
|
||||
return roles;
|
||||
}
|
||||
Map<Long, RoleDTO> allRoleMap = new LinkedHashMap<>();
|
||||
if (CollUtil.isNotEmpty(roles)) {
|
||||
roles.forEach(role -> allRoleMap.put(role.getRoleId(), role));
|
||||
}
|
||||
Map<Long, RoleDTO> roleMap = new LinkedHashMap<>();
|
||||
Map<String, List<Long>> dataScopeRoleMap = user.getDataScopeRoleMap();
|
||||
if (CollUtil.isNotEmpty(dataScopeRoleMap)) {
|
||||
access.perms().forEach(perm -> {
|
||||
List<Long> roleIds = dataScopeRoleMap.get(perm);
|
||||
if (CollUtil.isNotEmpty(roleIds)) {
|
||||
roleIds.forEach(roleId -> {
|
||||
RoleDTO role = allRoleMap.get(roleId);
|
||||
if (role != null) {
|
||||
roleMap.putIfAbsent(role.getRoleId(), role);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (CollUtil.isNotEmpty(roles) && CollUtil.isNotEmpty(access.roleKeys())) {
|
||||
roles.stream()
|
||||
.filter(role -> StringUtils.isNotBlank(role.getRoleKey()))
|
||||
.filter(role -> StringUtils.splitList(role.getRoleKey()).stream().anyMatch(access.roleKeys()::contains))
|
||||
.forEach(role -> roleMap.putIfAbsent(role.getRoleId(), role));
|
||||
}
|
||||
return new ArrayList<>(roleMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前请求处理器上解析接口权限和角色约束。
|
||||
*
|
||||
* @return 数据权限访问控制对象
|
||||
*/
|
||||
private DataPermissionAccess resolveAccess() {
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (request == null) {
|
||||
return DataPermissionAccess.EMPTY;
|
||||
}
|
||||
Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return DataPermissionAccess.EMPTY;
|
||||
}
|
||||
Set<String> perms = new LinkedHashSet<>();
|
||||
Set<String> roleKeys = new LinkedHashSet<>();
|
||||
SaCheckPermission saCheckPermission = findAnnotation(handlerMethod, SaCheckPermission.class);
|
||||
if (saCheckPermission != null) {
|
||||
perms.addAll(toSet(saCheckPermission.value()));
|
||||
roleKeys.addAll(toSet(saCheckPermission.orRole()));
|
||||
}
|
||||
SaCheckRole saCheckRole = findAnnotation(handlerMethod, SaCheckRole.class);
|
||||
if (saCheckRole != null) {
|
||||
roleKeys.addAll(toSet(saCheckRole.value()));
|
||||
}
|
||||
if (perms.isEmpty() && roleKeys.isEmpty()) {
|
||||
return DataPermissionAccess.EMPTY;
|
||||
}
|
||||
return new DataPermissionAccess(Set.copyOf(perms), Set.copyOf(roleKeys));
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先从方法、再从类上查找指定注解。
|
||||
*
|
||||
* @param handlerMethod 当前请求处理方法
|
||||
* @param annotationType 注解类型
|
||||
* @param <A> 注解类型
|
||||
* @return 注解对象,未配置时返回 null
|
||||
*/
|
||||
private <A extends Annotation> A findAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
|
||||
A annotation = AnnotationUtil.getAnnotation(handlerMethod.getMethod(), annotationType);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
return AnnotationUtil.getAnnotation(handlerMethod.getBeanType(), annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将注解中的字符串数组转换为去空后的集合。
|
||||
*
|
||||
* @param values 注解值数组
|
||||
* @return 字符串集合
|
||||
*/
|
||||
private Set<String> toSet(String[] values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
Arrays.stream(values).filter(StringUtils::isNotBlank).forEach(result::add);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
|
||||
*
|
||||
* @return DataPermission 注解对象,如果不存在则返回 null
|
||||
*/
|
||||
public DataPermission getDataPermission() {
|
||||
return DataPermissionHelper.getPermission();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
|
||||
*
|
||||
* @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
|
||||
*/
|
||||
public boolean invalid() {
|
||||
return getDataPermission() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有null变量找不到的变量返回默认值
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
|
||||
|
||||
/**
|
||||
* 变量值为空时返回的默认值。
|
||||
*/
|
||||
private final Object defaultValue;
|
||||
|
||||
/**
|
||||
* 查找 SpEL 变量,变量为空时返回默认值。
|
||||
*
|
||||
* @param name 变量名
|
||||
* @return 变量值或默认值
|
||||
*/
|
||||
@Override
|
||||
public Object lookupVariable(String name) {
|
||||
Object obj = super.lookupVariable(name);
|
||||
// 如果读取到的值是 null,则返回默认值
|
||||
if (obj == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
private static class NullSafePropertyAccessor implements PropertyAccessor {
|
||||
|
||||
/**
|
||||
* 原始属性访问器。
|
||||
*/
|
||||
private final PropertyAccessor delegate;
|
||||
|
||||
/**
|
||||
* 属性值为空时返回的默认值。
|
||||
*/
|
||||
private final Object defaultValue;
|
||||
|
||||
/**
|
||||
* 获取当前访问器支持的目标类型。
|
||||
*
|
||||
* @return 目标类型数组
|
||||
*/
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return delegate.getSpecificTargetClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定属性是否可读。
|
||||
*
|
||||
* @param context 表达式上下文
|
||||
* @param target 目标对象
|
||||
* @param name 属性名
|
||||
* @return true 可读 false 不可读
|
||||
* @throws AccessException 属性访问异常
|
||||
*/
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return delegate.canRead(context, target, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取属性值,属性值为空时返回默认值。
|
||||
*
|
||||
* @param context 表达式上下文
|
||||
* @param target 目标对象
|
||||
* @param name 属性名
|
||||
* @return 属性值
|
||||
* @throws AccessException 属性访问异常
|
||||
*/
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
TypedValue value = delegate.read(context, target, name);
|
||||
// 如果读取到的值是 null,则返回默认值
|
||||
if (value.getValue() == null) {
|
||||
return new TypedValue(defaultValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定属性是否可写。
|
||||
*
|
||||
* @param context 表达式上下文
|
||||
* @param target 目标对象
|
||||
* @param name 属性名
|
||||
* @return true 可写 false 不可写
|
||||
* @throws AccessException 属性访问异常
|
||||
*/
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return delegate.canWrite(context, target, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入属性值。
|
||||
*
|
||||
* @param context 表达式上下文
|
||||
* @param target 目标对象
|
||||
* @param name 属性名
|
||||
* @param newValue 新属性值
|
||||
* @throws AccessException 属性访问异常
|
||||
*/
|
||||
@Override
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
||||
delegate.write(context, target, name, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.common.mybatis.handler;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
|
||||
/**
|
||||
* 修改表信息初始化方式
|
||||
* 目前用于全局修改是否使用逻辑删除
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
|
||||
|
||||
/**
|
||||
* 表信息初始化后统一调整逻辑删除开关。
|
||||
*
|
||||
* @param tableInfo 表信息
|
||||
* @param configuration MyBatis 配置
|
||||
* @return 调整后的表信息
|
||||
*/
|
||||
@Override
|
||||
public TableInfo postTableInfo(TableInfo tableInfo, Configuration configuration) {
|
||||
String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
|
||||
// 只有关闭时 统一设置false 为true时mp自动判断不处理
|
||||
if (!Convert.toBool(flag)) {
|
||||
ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
|
||||
}
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package org.dromara.common.mybatis.helper;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.sql.SqlUtil;
|
||||
import org.dromara.common.mybatis.enums.DataBaseType;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 数据库助手
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DataBaseHelper {
|
||||
|
||||
/**
|
||||
* 动态数据源路由对象。
|
||||
*/
|
||||
private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
|
||||
|
||||
/**
|
||||
* 数据源对应数据库类型缓存。
|
||||
*/
|
||||
private static final Map<String, DataBaseType> DB_TYPE_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取当前数据源对应的数据库类型
|
||||
* <p>
|
||||
* 通过 DynamicRoutingDataSource 获取当前线程绑定的数据源,
|
||||
* 然后从数据源获取数据库连接,利用连接的元数据获取数据库产品名称,
|
||||
* 最后调用 DataBaseType.find 方法将数据库名称转换为对应的枚举类型
|
||||
*
|
||||
* @return 当前数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL
|
||||
* @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常
|
||||
*/
|
||||
public static DataBaseType getDataBaseType() {
|
||||
DataSource dataSource = DS.determineDataSource();
|
||||
String dsKey = DynamicDataSourceContextHolder.peek();
|
||||
final String key = dsKey != null ? dsKey : "primary";
|
||||
return DB_TYPE_CACHE.computeIfAbsent(key, k -> {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = conn.getMetaData();
|
||||
String databaseProductName = metaData.getDatabaseProductName();
|
||||
return DataBaseType.find(databaseProductName);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("获取数据库类型失败", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定数据源对应的数据库类型
|
||||
*
|
||||
* @param dsName 数据源名称
|
||||
* @return 指定数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL
|
||||
* @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常
|
||||
*/
|
||||
public static DataBaseType getDataBaseType(String dsName) {
|
||||
DataSource dataSource = DS.getDataSource(dsName);
|
||||
return DB_TYPE_CACHE.computeIfAbsent(dsName, k -> {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = conn.getMetaData();
|
||||
String databaseProductName = metaData.getDatabaseProductName();
|
||||
return DataBaseType.find(databaseProductName);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("获取数据库类型失败", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段
|
||||
* <p>
|
||||
* 用于判断指定值是否存在于逗号分隔的字符串列中,SQL写法根据不同数据库方言自动切换:
|
||||
* - Oracle 使用 instr 函数
|
||||
* - PostgreSQL 使用 strpos 函数
|
||||
* - SQL Server 使用 charindex 函数
|
||||
* - 其他默认使用 MySQL 的 find_in_set 函数
|
||||
*
|
||||
* @param var1 要查找的值(支持任意类型,内部会转换成字符串)
|
||||
* @param var2 存储逗号分隔值的数据库列名
|
||||
* @return 适用于当前数据库的 SQL 条件字符串,通常用于 where 或 apply 中拼接
|
||||
*/
|
||||
public static String findInSet(Object var1, String var2) {
|
||||
String var = Convert.toStr(var1);
|
||||
SqlUtil.filterKeyword(var);
|
||||
SqlUtil.filterKeyword(var2);
|
||||
return switch (getDataBaseType()) {
|
||||
// instr(',0,100,101,' , ',100,') <> 0
|
||||
case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
|
||||
// (select strpos(',0,100,101,' , ',100,')) <> 0
|
||||
case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
|
||||
// charindex(',100,' , ',0,100,101,') <> 0
|
||||
case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
|
||||
// find_in_set(100 , '0,100,101')
|
||||
default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前注册的数据源名称列表。
|
||||
*
|
||||
* @return 数据源名称列表
|
||||
*/
|
||||
public static List<String> getDataSourceNameList() {
|
||||
return new ArrayList<>(DS.getDataSources().keySet());
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package org.dromara.common.mybatis.helper;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.domain.DataPermissionAccess;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 数据权限助手
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DataPermissionHelper {
|
||||
|
||||
/**
|
||||
* Sa-Token Storage 中保存数据权限上下文的键。
|
||||
*/
|
||||
private static final String DATA_PERMISSION_KEY = "data:permission";
|
||||
|
||||
/**
|
||||
* 数据权限访问控制对象在上下文中的键。
|
||||
*/
|
||||
private static final String ACCESS_KEY = "data:permission:access";
|
||||
|
||||
/**
|
||||
* 当前线程正在执行的 Mapper 数据权限注解缓存。
|
||||
*/
|
||||
private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获取当前执行mapper权限注解
|
||||
*
|
||||
* @return 返回当前执行mapper权限注解
|
||||
*/
|
||||
public static DataPermission getPermission() {
|
||||
return PERMISSION_CACHE.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前执行mapper权限注解
|
||||
*
|
||||
* @param dataPermission 数据权限注解
|
||||
*/
|
||||
public static void setPermission(DataPermission dataPermission) {
|
||||
PERMISSION_CACHE.set(dataPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前执行mapper权限注解
|
||||
*/
|
||||
public static void removePermission() {
|
||||
PERMISSION_CACHE.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从上下文中获取指定键的变量值,并将其转换为指定的类型
|
||||
*
|
||||
* @param key 变量的键
|
||||
* @param <T> 变量值的类型
|
||||
* @return 指定键的变量值,如果不存在则返回 null
|
||||
*/
|
||||
public static <T> T getVariable(String key) {
|
||||
Map<String, Object> context = getContext();
|
||||
return (T) context.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向上下文中设置指定键的变量值
|
||||
*
|
||||
* @param key 要设置的变量的键
|
||||
* @param value 要设置的变量值
|
||||
*/
|
||||
public static void setVariable(String key, Object value) {
|
||||
Map<String, Object> context = getContext();
|
||||
context.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据权限访问控制对象。
|
||||
*
|
||||
* @return 访问控制对象
|
||||
*/
|
||||
public static DataPermissionAccess getAccess() {
|
||||
return getVariable(ACCESS_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前数据权限访问控制对象。
|
||||
*
|
||||
* @param access 访问控制对象
|
||||
*/
|
||||
public static void setAccess(DataPermissionAccess access) {
|
||||
setVariable(ACCESS_KEY, access);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限上下文
|
||||
*
|
||||
* @return 存储在SaStorage中的Map对象,用于存储数据权限相关的上下文信息
|
||||
* @throws NullPointerException 如果数据权限上下文类型异常,则抛出NullPointerException
|
||||
*/
|
||||
public static Map<String, Object> getContext() {
|
||||
SaStorage saStorage = SaHolder.getStorage();
|
||||
Object attribute = saStorage.get(DATA_PERMISSION_KEY);
|
||||
if (ObjectUtil.isNull(attribute)) {
|
||||
saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
|
||||
attribute = saStorage.get(DATA_PERMISSION_KEY);
|
||||
}
|
||||
if (attribute instanceof Map map) {
|
||||
return map;
|
||||
}
|
||||
throw new IllegalStateException("data permission context type exception");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在忽略数据权限中执行
|
||||
*
|
||||
* @param handle 处理执行方法
|
||||
*/
|
||||
public static void ignore(Runnable handle) {
|
||||
DataPermissionIgnoreContext.enable();
|
||||
try {
|
||||
handle.run();
|
||||
} finally {
|
||||
DataPermissionIgnoreContext.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在忽略数据权限中执行
|
||||
*
|
||||
* @param handle 处理执行方法
|
||||
* @return 执行结果
|
||||
*/
|
||||
public static <T> T ignore(Supplier<T> handle) {
|
||||
DataPermissionIgnoreContext.enable();
|
||||
try {
|
||||
return handle.get();
|
||||
} finally {
|
||||
DataPermissionIgnoreContext.disable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.common.mybatis.helper;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
|
||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* 数据权限忽略状态适配器。
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
final class DataPermissionIgnoreContext {
|
||||
|
||||
/**
|
||||
* 数据权限忽略状态栈,用于支持嵌套忽略并恢复进入前状态。
|
||||
*/
|
||||
private static final ThreadLocal<Deque<Boolean>> DATA_PERMISSION_STACK = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
/**
|
||||
* 开启忽略数据权限。
|
||||
*/
|
||||
static void enable() {
|
||||
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
|
||||
DATA_PERMISSION_STACK.get().push(ignoreStrategy != null && Boolean.TRUE.equals(ignoreStrategy.getDataPermission()));
|
||||
if (ObjectUtil.isNull(ignoreStrategy)) {
|
||||
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
|
||||
} else {
|
||||
ignoreStrategy.setDataPermission(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭忽略数据权限,并恢复进入前的数据权限忽略状态。
|
||||
*/
|
||||
static void disable() {
|
||||
Deque<Boolean> stack = DATA_PERMISSION_STACK.get();
|
||||
boolean previousDataPermission = !stack.isEmpty() && stack.pop();
|
||||
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
|
||||
if (ObjectUtil.isNotNull(ignoreStrategy)) {
|
||||
if (previousDataPermission) {
|
||||
ignoreStrategy.setDataPermission(true);
|
||||
} else if (isOnlyDataPermissionIgnored(ignoreStrategy) && stack.isEmpty()) {
|
||||
InterceptorIgnoreHelper.clearIgnoreStrategy();
|
||||
} else {
|
||||
ignoreStrategy.setDataPermission(false);
|
||||
}
|
||||
}
|
||||
if (stack.isEmpty()) {
|
||||
DATA_PERMISSION_STACK.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MyBatis-Plus 当前线程中的拦截器忽略策略。
|
||||
*
|
||||
* @return 当前忽略策略,未设置时返回 null
|
||||
*/
|
||||
private static IgnoreStrategy getIgnoreStrategy() {
|
||||
Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
|
||||
if (ignoreStrategyLocal instanceof ThreadLocal<?> ignoreStrategyThreadLocal
|
||||
&& ignoreStrategyThreadLocal.get() instanceof IgnoreStrategy ignoreStrategy) {
|
||||
return ignoreStrategy;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前忽略策略是否只忽略了数据权限插件。
|
||||
*
|
||||
* @param ignoreStrategy 忽略策略
|
||||
* @return true 仅忽略数据权限 false 还忽略了其他插件能力
|
||||
*/
|
||||
private static boolean isOnlyDataPermissionIgnored(IgnoreStrategy ignoreStrategy) {
|
||||
return !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
|
||||
&& !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
|
||||
&& !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
|
||||
&& !Boolean.TRUE.equals(ignoreStrategy.getTenantLine())
|
||||
&& CollectionUtil.isEmpty(ignoreStrategy.getOthers());
|
||||
}
|
||||
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package org.dromara.common.mybatis.interceptor;
|
||||
|
||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.delete.Delete;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import net.sf.jsqlparser.statement.select.SetOperationList;
|
||||
import net.sf.jsqlparser.statement.update.Update;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据权限拦截器
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
|
||||
|
||||
/**
|
||||
* 数据权限 SQL 处理器。
|
||||
*/
|
||||
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
|
||||
|
||||
/**
|
||||
* 在执行查询之前,检查并处理数据权限相关逻辑
|
||||
*
|
||||
* @param executor MyBatis 执行器对象
|
||||
* @param ms 映射语句对象
|
||||
* @param parameter 方法参数
|
||||
* @param rowBounds 分页对象
|
||||
* @param resultHandler 结果处理器
|
||||
* @param boundSql 绑定的 SQL 对象
|
||||
* @throws SQLException 如果发生 SQL 异常
|
||||
*/
|
||||
@Override
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
||||
// 检查是否需要忽略数据权限处理
|
||||
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
|
||||
return;
|
||||
}
|
||||
// 检查是否缺少有效的数据权限注解
|
||||
if (dataPermissionHandler.invalid()) {
|
||||
return;
|
||||
}
|
||||
// 解析 sql 分配对应方法
|
||||
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
|
||||
mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑
|
||||
*
|
||||
* @param sh MyBatis StatementHandler 对象
|
||||
* @param connection 数据库连接对象
|
||||
* @param transactionTimeout 事务超时时间
|
||||
*/
|
||||
@Override
|
||||
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
|
||||
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
|
||||
MappedStatement ms = mpSh.mappedStatement();
|
||||
// 获取 SQL 命令类型(增、删、改、查)
|
||||
SqlCommandType sct = ms.getSqlCommandType();
|
||||
|
||||
// 只处理更新和删除操作的 SQL 语句
|
||||
if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
|
||||
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
|
||||
return;
|
||||
}
|
||||
// 检查是否缺少有效的数据权限注解
|
||||
if (dataPermissionHandler.invalid()) {
|
||||
return;
|
||||
}
|
||||
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||
mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SELECT 查询语句中的 WHERE 条件
|
||||
*
|
||||
* @param select SELECT 查询对象
|
||||
* @param index 查询语句的索引
|
||||
* @param sql 查询语句
|
||||
* @param obj WHERE 条件参数
|
||||
*/
|
||||
@Override
|
||||
protected void processSelect(Select select, int index, String sql, Object obj) {
|
||||
if (select instanceof PlainSelect) {
|
||||
this.setWhere((PlainSelect) select, (String) obj);
|
||||
} else if (select instanceof SetOperationList setOperationList) {
|
||||
List<Select> selectBodyList = setOperationList.getSelects();
|
||||
selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 UPDATE 语句中的 WHERE 条件
|
||||
*
|
||||
* @param update UPDATE 查询对象
|
||||
* @param index 查询语句的索引
|
||||
* @param sql 查询语句
|
||||
* @param obj WHERE 条件参数
|
||||
*/
|
||||
@Override
|
||||
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
|
||||
if (null != sqlSegment) {
|
||||
update.setWhere(sqlSegment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 DELETE 语句中的 WHERE 条件
|
||||
*
|
||||
* @param delete DELETE 查询对象
|
||||
* @param index 查询语句的索引
|
||||
* @param sql 查询语句
|
||||
* @param obj WHERE 条件参数
|
||||
*/
|
||||
@Override
|
||||
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
|
||||
if (null != sqlSegment) {
|
||||
delete.setWhere(sqlSegment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 SELECT 语句的 WHERE 条件
|
||||
*
|
||||
* @param plainSelect SELECT 查询对象
|
||||
* @param mappedStatementId 映射语句的 ID
|
||||
*/
|
||||
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
|
||||
if (null != sqlSegment) {
|
||||
plainSelect.setWhere(sqlSegment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建表达式,用于处理表的数据权限
|
||||
*
|
||||
* @param table 表对象
|
||||
* @param where WHERE 条件表达式
|
||||
* @param whereSegment WHERE 条件片段
|
||||
* @return 构建的表达式
|
||||
*/
|
||||
@Override
|
||||
public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
|
||||
// 只有新版数据权限处理器才会执行到这里
|
||||
final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
|
||||
return handler.getSqlSegment(table, where, whereSegment);
|
||||
}
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
package org.dromara.common.mybatis.interceptor;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.ParameterMapping;
|
||||
import org.apache.ibatis.mapping.ParameterMode;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.type.TypeHandlerRegistry;
|
||||
import org.dromara.common.mybatis.config.properties.SqlLogProperties;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.sql.Statement;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 完整 SQL 日志拦截器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j(topic = "SQL_FULL")
|
||||
@Intercepts({
|
||||
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
|
||||
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
|
||||
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),
|
||||
@Signature(type = StatementHandler.class, method = "queryCursor", args = {Statement.class})
|
||||
})
|
||||
public class SqlLogInterceptor implements Interceptor {
|
||||
|
||||
/**
|
||||
* 单条日志分片长度。
|
||||
*/
|
||||
private static final int CHUNK_SIZE = 8000;
|
||||
|
||||
/**
|
||||
* SQL 空白字符匹配。
|
||||
*/
|
||||
private static final String BLANK_REGEX = "\\s+";
|
||||
|
||||
/**
|
||||
* 日期时间格式。
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 日期格式。
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* 时间格式。
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 控制台输出锁,避免多线程 SQL 日志互相穿插。
|
||||
*/
|
||||
private static final ReentrantLock CONSOLE_LOCK = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* SQL 日志配置。
|
||||
*/
|
||||
private final SqlLogProperties sqlLogProperties;
|
||||
|
||||
public SqlLogInterceptor(SqlLogProperties sqlLogProperties) {
|
||||
this.sqlLogProperties = sqlLogProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
|
||||
BoundSql boundSql = statementHandler.getBoundSql();
|
||||
MappedStatement mappedStatement = PluginUtils.mpStatementHandler(statementHandler).mappedStatement();
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = invocation.proceed();
|
||||
printSql(mappedStatement, boundSql, System.currentTimeMillis() - startTime, null);
|
||||
return result;
|
||||
} catch (Throwable e) {
|
||||
printSql(mappedStatement, boundSql, System.currentTimeMillis() - startTime, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出 SQL。
|
||||
*
|
||||
* @param mappedStatement 映射语句
|
||||
* @param boundSql 绑定 SQL
|
||||
* @param elapsedTime 执行耗时
|
||||
* @param throwable 执行异常
|
||||
*/
|
||||
private void printSql(MappedStatement mappedStatement, BoundSql boundSql, long elapsedTime, Throwable throwable) {
|
||||
String fullSql = buildFullSql(mappedStatement.getConfiguration(), boundSql);
|
||||
String message = buildLogMessage(mappedStatement, elapsedTime, fullSql, throwable);
|
||||
printChunk(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建日志内容。
|
||||
*
|
||||
* @param mappedStatement 映射语句
|
||||
* @param elapsedTime 执行耗时
|
||||
* @param fullSql 完整 SQL
|
||||
* @param throwable 执行异常
|
||||
* @return 日志内容
|
||||
*/
|
||||
private String buildLogMessage(MappedStatement mappedStatement, long elapsedTime, String fullSql, Throwable throwable) {
|
||||
if (isLogOutput()) {
|
||||
String message = StrUtil.format("Consume Time:{} ms {} Mapper ID:{} Execute SQL:{}",
|
||||
elapsedTime, DateUtil.now(), mappedStatement.getId(), fullSql);
|
||||
String errorMessage = formatThrowable(throwable);
|
||||
if (StrUtil.isNotBlank(errorMessage)) {
|
||||
message = message + " Execute Error:" + errorMessage;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
String message = StrUtil.format("Consume Time:{} ms {}\nMapper ID:{}\nExecute SQL:{}",
|
||||
elapsedTime, DateUtil.now(), mappedStatement.getId(), fullSql);
|
||||
String errorMessage = formatThrowable(throwable);
|
||||
if (StrUtil.isNotBlank(errorMessage)) {
|
||||
message = message + "\nExecute Error:" + errorMessage;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化执行异常。
|
||||
*
|
||||
* @param throwable 执行异常
|
||||
* @return 异常信息
|
||||
*/
|
||||
private String formatThrowable(Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
Throwable realThrowable = unwrapThrowable(throwable);
|
||||
String message = realThrowable.getMessage();
|
||||
if (StrUtil.isBlank(message)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
return realThrowable.getClass().getName() + ": " + message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包反射调用异常。
|
||||
*
|
||||
* @param throwable 执行异常
|
||||
* @return 实际异常
|
||||
*/
|
||||
private Throwable unwrapThrowable(Throwable throwable) {
|
||||
if (throwable instanceof InvocationTargetException invocationTargetException
|
||||
&& invocationTargetException.getTargetException() != null) {
|
||||
return invocationTargetException.getTargetException();
|
||||
}
|
||||
return throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整 SQL。
|
||||
*
|
||||
* @param configuration MyBatis 配置
|
||||
* @param boundSql 绑定 SQL
|
||||
* @return 完整 SQL
|
||||
*/
|
||||
private String buildFullSql(Configuration configuration, BoundSql boundSql) {
|
||||
String sql = boundSql.getSql().replaceAll(BLANK_REGEX, " ").trim();
|
||||
List<String> parameters = buildParameterValues(configuration, boundSql);
|
||||
return replacePlaceholders(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建参数值集合。
|
||||
*
|
||||
* @param configuration MyBatis 配置
|
||||
* @param boundSql 绑定 SQL
|
||||
* @return 参数值集合
|
||||
*/
|
||||
private List<String> buildParameterValues(Configuration configuration, BoundSql boundSql) {
|
||||
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
|
||||
List<String> parameters = new ArrayList<>(parameterMappings.size());
|
||||
Object parameterObject = boundSql.getParameterObject();
|
||||
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
|
||||
MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
|
||||
for (ParameterMapping parameterMapping : parameterMappings) {
|
||||
if (parameterMapping.getMode() == ParameterMode.OUT) {
|
||||
continue;
|
||||
}
|
||||
String propertyName = parameterMapping.getProperty();
|
||||
Object value;
|
||||
if (boundSql.hasAdditionalParameter(propertyName)) {
|
||||
value = boundSql.getAdditionalParameter(propertyName);
|
||||
} else if (parameterObject == null) {
|
||||
value = null;
|
||||
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
|
||||
value = parameterObject;
|
||||
} else if (metaObject != null && metaObject.hasGetter(propertyName)) {
|
||||
value = metaObject.getValue(propertyName);
|
||||
} else {
|
||||
value = null;
|
||||
}
|
||||
parameters.add(formatParameter(value));
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换 SQL 占位符。
|
||||
*
|
||||
* @param sql SQL 模板
|
||||
* @param parameters 参数集合
|
||||
* @return 完整 SQL
|
||||
*/
|
||||
private String replacePlaceholders(String sql, List<String> parameters) {
|
||||
if (parameters.isEmpty()) {
|
||||
return sql;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(sql.length() + parameters.size() * 8);
|
||||
int parameterIndex = 0;
|
||||
boolean inSingleQuote = false;
|
||||
boolean inDoubleQuote = false;
|
||||
for (int i = 0; i < sql.length(); i++) {
|
||||
char current = sql.charAt(i);
|
||||
if (current == '\'' && !inDoubleQuote) {
|
||||
inSingleQuote = !inSingleQuote;
|
||||
} else if (current == '"' && !inSingleQuote) {
|
||||
inDoubleQuote = !inDoubleQuote;
|
||||
}
|
||||
if (current == '?' && !inSingleQuote && !inDoubleQuote && parameterIndex < parameters.size()) {
|
||||
builder.append(parameters.get(parameterIndex++));
|
||||
} else {
|
||||
builder.append(current);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化参数值。
|
||||
*
|
||||
* @param value 参数值
|
||||
* @return SQL 参数文本
|
||||
*/
|
||||
private String formatParameter(Object value) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof Date date) {
|
||||
return quote(DateUtil.formatDateTime(date));
|
||||
}
|
||||
if (value instanceof LocalDateTime localDateTime) {
|
||||
return quote(localDateTime.format(DATE_TIME_FORMATTER));
|
||||
}
|
||||
if (value instanceof LocalDate localDate) {
|
||||
return quote(localDate.format(DATE_FORMATTER));
|
||||
}
|
||||
if (value instanceof LocalTime localTime) {
|
||||
return quote(localTime.format(TIME_FORMATTER));
|
||||
}
|
||||
if (value instanceof TemporalAccessor) {
|
||||
return quote(value.toString());
|
||||
}
|
||||
if (value instanceof Enum<?> enumValue) {
|
||||
return quote(enumValue.name());
|
||||
}
|
||||
return quote(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装字符串参数。
|
||||
*
|
||||
* @param value 字符串值
|
||||
* @return SQL 字符串参数
|
||||
*/
|
||||
private String quote(String value) {
|
||||
return "'" + value.replace("'", "''") + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* 分片输出日志,避免日志链路截断超长 SQL。
|
||||
*
|
||||
* @param message 日志内容
|
||||
*/
|
||||
private void printChunk(String message) {
|
||||
if (!isLogOutput()) {
|
||||
printConsole(message);
|
||||
return;
|
||||
}
|
||||
if (message.length() <= CHUNK_SIZE) {
|
||||
print(message);
|
||||
return;
|
||||
}
|
||||
String sqlLogId = UUID.randomUUID().toString();
|
||||
int total = (message.length() + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
for (int i = 0; i < total; i++) {
|
||||
int start = i * CHUNK_SIZE;
|
||||
int end = Math.min(start + CHUNK_SIZE, message.length());
|
||||
print(StrUtil.format("sqlLogId={} part={}/{} {}", sqlLogId, i + 1, total, message.substring(start, end)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出 SQL 日志。
|
||||
*
|
||||
* @param message 日志内容
|
||||
*/
|
||||
private void print(String message) {
|
||||
if (isLogOutput()) {
|
||||
log.info(message);
|
||||
return;
|
||||
}
|
||||
printConsole(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出控制台日志。
|
||||
*
|
||||
* @param message 日志内容
|
||||
*/
|
||||
private void printConsole(String message) {
|
||||
CONSOLE_LOCK.lock();
|
||||
try {
|
||||
System.err.println(message);
|
||||
System.err.println();
|
||||
} finally {
|
||||
CONSOLE_LOCK.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用日志系统输出。
|
||||
*
|
||||
* @return true 使用日志系统输出,false 使用控制台输出
|
||||
*/
|
||||
private boolean isLogOutput() {
|
||||
return StrUtil.equalsIgnoreCase("log", sqlLogProperties.getOutput());
|
||||
}
|
||||
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package org.dromara.common.mybatis.utils;
|
||||
|
||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
|
||||
/**
|
||||
* ID 生成工具类
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class IdGeneratorUtil {
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 主键生成器。
|
||||
*/
|
||||
private static final IdentifierGenerator GENERATOR = SpringUtils.getBean(IdentifierGenerator.class);
|
||||
|
||||
/**
|
||||
* 生成字符串类型主键 ID
|
||||
* <p>
|
||||
* 调用 {@link IdentifierGenerator#nextId(Object)},返回 String 格式 ID。
|
||||
* </p>
|
||||
*
|
||||
* @return 字符串格式主键 ID
|
||||
*/
|
||||
public static String nextId() {
|
||||
return GENERATOR.nextId(null).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Long 类型主键 ID
|
||||
* <p>
|
||||
* 自动将生成的数字型主键转换为 Long 类型
|
||||
* </p>
|
||||
*
|
||||
* @return Long 类型主键 ID
|
||||
*/
|
||||
public static Long nextLongId() {
|
||||
return GENERATOR.nextId(null).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Number 类型主键 ID
|
||||
* <p>
|
||||
* 推荐在需要保留原始 Number 类型时使用
|
||||
* </p>
|
||||
*
|
||||
* @return Number 类型主键 ID
|
||||
*/
|
||||
public static Number nextNumberId() {
|
||||
return GENERATOR.nextId(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据实体生成数字型主键 ID
|
||||
* <p>
|
||||
* 若自定义的 {@link IdentifierGenerator} 根据实体内容生成 ID,则可以使用本方法
|
||||
* </p>
|
||||
*
|
||||
* @param entity 实体对象
|
||||
* @return Number 类型主键 ID
|
||||
*/
|
||||
public static Number nextId(Object entity) {
|
||||
return GENERATOR.nextId(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据实体生成字符串主键 ID
|
||||
* <p>
|
||||
* 与 {@link #nextId(Object)} 类似,但返回 String 类型
|
||||
* </p>
|
||||
*
|
||||
* @param entity 实体对象
|
||||
* @return 字符串格式主键 ID
|
||||
*/
|
||||
public static String nextStringId(Object entity) {
|
||||
return GENERATOR.nextId(entity).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 32 位 UUID
|
||||
* <p>
|
||||
* 底层使用 {@link IdWorker#get32UUID()}
|
||||
* </p>
|
||||
*
|
||||
* @return 32 位 UUID 字符串
|
||||
*/
|
||||
public static String nextUUID() {
|
||||
return IdWorker.get32UUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据实体生成 32 位 UUID
|
||||
* <p>
|
||||
* 默认 {@link IdentifierGenerator#nextUUID(Object)} 实现忽略实体,但保留该方法便于扩展。
|
||||
* </p>
|
||||
*
|
||||
* @param entity 实体对象
|
||||
* @return 32 位 UUID 字符串
|
||||
*/
|
||||
public static String nextUUID(Object entity) {
|
||||
return GENERATOR.nextUUID(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成带指定前缀的字符串主键 ID
|
||||
* <p>
|
||||
* 示例:prefix = "ORD",生成结果形如:{@code ORD20251211000123}
|
||||
* </p>
|
||||
*
|
||||
* @param prefix 自定义前缀
|
||||
* @return 带前缀的字符串主键 ID
|
||||
*/
|
||||
public static String nextIdWithPrefix(String prefix) {
|
||||
return prefix + nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成带前缀的 UUID
|
||||
*
|
||||
* @param prefix 前缀
|
||||
* @return prefix + UUID
|
||||
*/
|
||||
public static String nextUUIDWithPrefix(String prefix) {
|
||||
return prefix + nextUUID();
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.dromara.common.mybatis.config.MybatisPlusConfig
|
||||
@@ -0,0 +1,31 @@
|
||||
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
mybatis-plus:
|
||||
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
|
||||
checkConfigLocation: false
|
||||
configuration:
|
||||
# 自动驼峰命名规则(camel case)映射
|
||||
mapUnderscoreToCamelCase: true
|
||||
# MyBatis 自动映射策略
|
||||
# NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
|
||||
autoMappingBehavior: FULL
|
||||
# MyBatis 自动映射时未知列或未知属性处理策
|
||||
# NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
|
||||
autoMappingUnknownColumnBehavior: NONE
|
||||
# 关闭默认日志输出 org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
global-config:
|
||||
# 是否打印 Logo banner
|
||||
banner: true
|
||||
dbConfig:
|
||||
# 主键类型
|
||||
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
|
||||
idType: ASSIGN_ID
|
||||
# 逻辑已删除值(可按需求随意修改)
|
||||
logicDeleteValue: 1
|
||||
# 逻辑未删除值
|
||||
logicNotDeleteValue: 0
|
||||
insertStrategy: NOT_NULL
|
||||
updateStrategy: NOT_NULL
|
||||
whereStrategy: NOT_NULL
|
||||
Reference in New Issue
Block a user