This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
<?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>
<artifactId>common</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-mybatis</artifactId>
<description>common-mybatis相关依赖</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dameng.version>8.1.2.141</dameng.version>
</properties>
<dependencies>
<!-- Java Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.oracle</groupId>-->
<!-- <artifactId>ojdbc6</artifactId>-->
<!-- <version>11.2.0.4</version>-->
<!-- </dependency>-->
<!-- 达梦驱动包 必须次版本才能适配sharding-jdbc-->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dameng.version}</version>
</dependency>
<!--mybatis相关配置-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--pagehelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!-- Common Core 核心依赖 -->
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,5 @@
# common-mybatis
1. 引入mybatis依赖
2. 引入mybatis分页插件
3. 配置mybatis的扫描路径和驼峰等
4. 配置全局分页插件

View File

@@ -0,0 +1,54 @@
package cn.fateverse.common.mybatis;
import cn.fateverse.common.mybatis.interceptor.AutoSetValueInterceptor;
import cn.fateverse.common.mybatis.interceptor.DynamicTableInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @author Clay
* @date 2022/11/19
*/
@MapperScan("${mybatis.mapperPackage}")
public class MybatisAutoConfiguration implements ApplicationRunner {
/**
* 按照表格数据隔离
*/
@Value("${mybatis.tableSaas}")
private Boolean tableSaas = false;
private final List<SqlSessionFactory> sqlSessionFactoryList;
private final DynamicTableInterceptor dynamicTableInterceptor = new DynamicTableInterceptor();
private final AutoSetValueInterceptor autoSetValueInterceptor = new AutoSetValueInterceptor();
public MybatisAutoConfiguration(List<SqlSessionFactory> sqlSessionFactoryList) {
this.sqlSessionFactoryList = sqlSessionFactoryList;
}
@PostConstruct
public void init() {
//向sqlSession中添加拦截器
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(autoSetValueInterceptor);
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
if (tableSaas) {
//向sqlSession中添加拦截器
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(dynamicTableInterceptor);
}
}
}
}

View File

@@ -0,0 +1,15 @@
package cn.fateverse.common.mybatis.annotaion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicField {
String value() default "tableName";
}

View File

@@ -0,0 +1,35 @@
package cn.fateverse.common.mybatis.annotaion;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 动态表注解
* 可以放在方法上,也可以放在类上,先判断方法上是否存在,如果不存在则判断类
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicTable {
/**
* 表名
*
* @return 表名
*/
@AliasFor("value")
String tableName() default "";
/**
* 表名
*
* @return 表名
*/
@AliasFor("tableName")
String value() default "";
}

View File

@@ -0,0 +1,16 @@
package cn.fateverse.common.mybatis.entity;
import lombok.Data;
@Data
public class DynamicWrapper {
private String tableName;
private String fieldName;
private String keyName;
}

View File

@@ -0,0 +1,14 @@
package cn.fateverse.common.mybatis.handler;
import cn.fateverse.common.core.exception.CustomException;
public class DynamicException extends CustomException {
public DynamicException(String message) {
super(message);
}
public DynamicException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,31 @@
package cn.fateverse.common.mybatis.handler;
import cn.fateverse.common.core.enums.ResultEnum;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.core.utils.ObjectUtils;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class MyBatisExceptionHandler {
@ExceptionHandler(MyBatisSystemException.class)
public Result<String> handleMyBatisException(MyBatisSystemException e) {
Throwable throwable = e.getCause();
if (throwable instanceof PersistenceException){
PersistenceException exception = (PersistenceException) throwable;
Throwable cause = exception.getCause();
if (cause instanceof DynamicException){
DynamicException dynamicException = (DynamicException) cause;
return ObjectUtils.isEmpty(dynamicException.getCode()) ? Result.error(dynamicException.getMessage()) : Result.error(dynamicException.getCode(), dynamicException.getMessage());
};
}
return Result.error(ResultEnum.ERROR);
}
}

View File

@@ -0,0 +1,160 @@
package cn.fateverse.common.mybatis.interceptor;
import cn.fateverse.common.core.annotaion.EnableAutoField;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.fateverse.common.core.utils.AutoSetValueUtils;
import cn.fateverse.common.core.entity.BaseEntity;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.utils.uuid.IdUtils;
import cn.fateverse.common.core.annotaion.AutoTime;
import cn.fateverse.common.core.annotaion.AutoUser;
import cn.fateverse.common.core.annotaion.GenerateId;
import cn.fateverse.common.core.enums.MethodEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
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 java.lang.reflect.Field;
import java.util.*;
/**
* @author Clay
* @date 2022/11/19
*/
@Slf4j
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class AutoSetValueInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
//获取到本次sql执行类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
//获取到当前方法的类型
MethodEnum methodEnum = null;
if (SqlCommandType.INSERT == sqlCommandType) {
methodEnum = MethodEnum.INSERT;
} else if (SqlCommandType.UPDATE == sqlCommandType) {
methodEnum = MethodEnum.UPDATE;
} else {
return invocation.proceed();
}
Object arg = args[1];
//是否映射对象
if (arg instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap parametersMap = (MapperMethod.ParamMap) arg;
Set<Object> paramSet = new HashSet<>();
Set keySet = parametersMap.keySet();
for (Object key : keySet) {
Object param = parametersMap.get(key);
paramSet.add(param);
}
for (Object param : paramSet) {
autoSetValue(param, methodEnum);
}
} else {
autoSetValue(arg, methodEnum);
}
return invocation.proceed();
}
private void autoSetValue(Object param, MethodEnum methodEnum) {
Class<?> target = param.getClass();
if (param instanceof AbstractList) {
AbstractList<Object> list = (AbstractList<Object>) param;
if (list == null && list.isEmpty()) {
return;
}
target = list.get(0).getClass();
}
EnableAutoField enable = target.getAnnotation(EnableAutoField.class);
if (null == enable) {
return;
}
Set<Field> generateIdSet = new HashSet<>();
Set<Field> autoUserSet = new HashSet<>();
Set<Field> autoTimeSet = new HashSet<>();
if (param instanceof AbstractList) {
AbstractList<Object> list = (AbstractList<Object>) param;
checkAnnotation(list.get(0), generateIdSet, autoUserSet, autoTimeSet, methodEnum);
for (Object data : list) {
generateId(data, generateIdSet);
}
AutoSetValueUtils.autoUserList(list, methodEnum, autoUserSet);
AutoSetValueUtils.autoTimeList(list, methodEnum, autoTimeSet);
} else {
checkAnnotation(param, generateIdSet, autoUserSet, autoTimeSet, methodEnum);
generateId(param, generateIdSet);
AutoSetValueUtils.autoUserNew(param, methodEnum, autoTimeSet);
AutoSetValueUtils.autoTimeNew(param, methodEnum, autoTimeSet);
}
}
private void checkAnnotation(Object data, Set<Field> generateIdSet, Set<Field> autoUserSet, Set<Field> autoTimeSet, MethodEnum methodEnum) {
//获取到所有的字
Class<?> targetClass = data.getClass();
List<Field> fields = new ArrayList<>(Arrays.asList(targetClass.getDeclaredFields()));
if (data instanceof BaseEntity) {
Class<?> superclass = targetClass.getSuperclass();
List<Field> superField = Arrays.asList(superclass.getDeclaredFields());
fields.addAll(superField);
}
for (Field field : fields) {
GenerateId generateId = field.getAnnotation(GenerateId.class);
if (null != generateId && methodEnum == MethodEnum.INSERT) {
generateIdSet.add(field);
}
AutoUser autoUser = field.getAnnotation(AutoUser.class);
if (autoUser != null) {
autoUserSet.add(field);
}
AutoTime autoTime = field.getAnnotation(AutoTime.class);
if (autoTime != null) {
autoTimeSet.add(field);
}
}
}
/**
* 自动生成id
*
* @param parameter 参数
* @param fields 字段
*/
private void generateId(Object parameter, Set<Field> fields) {
for (Field field : fields) {
GenerateId generateId = field.getAnnotation(GenerateId.class);
try {
Class<?> type = field.getType();
switch (generateId.idType()) {
case UUID:
Object uuid = Convert.convert(type, IdUtils.randomUUID());
field.setAccessible(true);
field.set(parameter, uuid);
break;
case SNOWFLAKE:
Object snoId = Convert.convert(type, IdUtil.getSnowflake(1).nextId());
field.setAccessible(true);
field.set(parameter, snoId);
break;
default:
throw new ClassCastException("GenerateId未指定数据类型或者数据类型不存在");
}
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new CustomException("Id自动生成失败");
}
}
}
}

View File

@@ -0,0 +1,134 @@
package cn.fateverse.common.mybatis.interceptor;
import cn.fateverse.common.mybatis.annotaion.DynamicField;
import cn.fateverse.common.mybatis.annotaion.DynamicTable;
import cn.fateverse.common.mybatis.entity.DynamicWrapper;
import cn.fateverse.common.mybatis.handler.DynamicException;
import cn.hutool.core.collection.ConcurrentHashSet;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Intercepts(value = {
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class DynamicTableInterceptor implements Interceptor {
private final Map<String, DynamicWrapper> dynamicTableMap = new ConcurrentHashMap<>(8);
private final Set<String> ignoreStatementSet = new ConcurrentHashSet<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取当前的查询参数
Object[] args = invocation.getArgs();
// 获取MappedStatement对象
MappedStatement mappedStatement = (MappedStatement) args[0];
//获取到当前mybatis的id,mybatis中不允许重载方法存在
String mappedStatementId = mappedStatement.getId();
if (ignoreStatementSet.contains(mappedStatementId)) {
return invocation.proceed();
}
DynamicWrapper dynamicWrapper = dynamicTableMap.get(mappedStatementId);
if (null == dynamicWrapper) {
synchronized (dynamicTableMap) {
dynamicWrapper = dynamicTableMap.get(mappedStatementId);
if (null == dynamicWrapper) {
//获取到当前的class信息
String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
//获取到方法名称
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
//获取到mapper类对象
Class<?> mapperClass = this.getClass().getClassLoader().loadClass(className);
//获取到类对象上注解信息
DynamicTable classAnnotation = mapperClass.getAnnotation(DynamicTable.class);
//类上并没有表述当前注解,认为不需要SaaS处理,直接方法,后续添加到忽略的set对象中,并执行程序
if (classAnnotation == null) {
ignoreStatementSet.add(mappedStatementId);
return invocation.proceed();
}
//是否映射对象
if (!(args[1] instanceof HashMap)) {
ignoreStatementSet.add(mappedStatementId);
return invocation.proceed();
}
//获取到表格名称
String tableName = classAnnotation.value();
if (ObjectUtils.isEmpty(tableName)) {
throw new DynamicException("DynamicTable注解必须指定明确的表名称!");
}
//获取到其中的所有方法信息
Method[] declaredMethods = mapperClass.getDeclaredMethods();
//解析到当前执行的方法
Method method = null;
for (Method declaredMethod : declaredMethods) {
if (methodName.startsWith(declaredMethod.getName())) {
method = declaredMethod;
}
}
//方法上是否存在类注解信息,优先级为先使用方法上的
assert method != null;
DynamicTable methodAnnotation = method.getAnnotation(DynamicTable.class);
if (methodAnnotation != null && !ObjectUtils.isEmpty(methodAnnotation.value())) {
tableName = methodAnnotation.value();
}
//获取到方法上的参数信息
Parameter[] methodParameters = method.getParameters();
//初始化包装类
dynamicWrapper = new DynamicWrapper();
dynamicWrapper.setTableName(tableName);
int count = 0;
for (int i = 0; i < methodParameters.length; i++) {
Parameter methodParameter = methodParameters[i];
DynamicField dynamicField = methodParameter.getAnnotation(DynamicField.class);
//获取当前参数上是否存在注解,如果存在,则表示当前为SaaS的动态参数
if (dynamicField != null) {
String fieldName = dynamicField.value();
if (ObjectUtils.isEmpty(fieldName)) {
throw new DynamicException("DynamicField注解必须指定明确的字段名称!");
}
String keyName = "param" + (i + 1);
Param param = methodParameter.getAnnotation(Param.class);
if (null != param && !ObjectUtils.isEmpty(param.value())) {
fieldName = param.value();
keyName = fieldName;
}
count++;
dynamicWrapper.setKeyName(keyName);
dynamicWrapper.setFieldName(fieldName);
}
}
if (count != 1) {
throw new DynamicException("配置的动态参数为 : " + count + ",不合法,应该只存在1个动态参数");
}
dynamicTableMap.put(mappedStatementId, dynamicWrapper);
}
}
}
HashMap<String, Object> parametersMap = (HashMap<String,Object>) args[1];
parametersMap.put(dynamicWrapper.getFieldName(), dynamicWrapper.getTableName() + "_" + parametersMap.get(dynamicWrapper.getKeyName()));
return invocation.proceed();
}
}

View File

@@ -0,0 +1,120 @@
package cn.fateverse.common.mybatis.utils;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.core.utils.ObjectUtils;
import cn.fateverse.common.core.utils.TableSupport;
import cn.fateverse.common.core.utils.sql.SqlUtil;
import cn.fateverse.common.core.entity.PageInfo;
import com.github.pagehelper.PageHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 分页工具类
*
* @author Clay
* @date 2022/10/30
*/
public class PageUtils extends PageHelper {
/**
* 设置请求分页
*/
public static void startPage() {
PageInfo pageInfo = TableSupport.buildPageRequest();
Integer pageNum = pageInfo.getPageNum();
Integer pageSize = pageInfo.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageInfo.getOrderBy());
Boolean reasonable = pageInfo.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}
/**
* 开始的Size
*
* @param pageInfo
* @return
*/
public static Integer getStartSize(PageInfo pageInfo) {
return (pageInfo.getPageNum() - 1) * pageInfo.getPageSize();
}
public static Integer getStartSize() {
PageInfo pageInfo = TableSupport.buildPageRequest();
return (pageInfo.getPageNum() - 1) * pageInfo.getPageSize();
}
/**
* 获取到分页后的数据信息
*
* @param list
* @param <T>
* @return
*/
public static <T> TableDataInfo<T> getDataTable(List<T> list) {
if (null == list) {
return new TableDataInfo<>(new ArrayList<>(), 0);
}
TableDataInfo<T> tableDataInfo = new TableDataInfo<>();
tableDataInfo.setRows(list);
tableDataInfo.setTotal(getTotal(list));
return tableDataInfo;
}
/**
* 获取到分页的总数
*
* @param list
* @return
*/
public static Long getTotal(List<?> list) {
long total = new com.github.pagehelper.PageInfo<>(list).getTotal();
clearPage();
return total;
}
/**
* 转换为TableDataInfo对象
*
* @param list
* @param count
* @param <T>
* @return
*/
public static <T> TableDataInfo<T> convertDataTable(List<T> list, Long count) {
if (null == list) {
return new TableDataInfo<>(new ArrayList<>(), 0);
}
TableDataInfo<T> tableDataInfo = new TableDataInfo<>();
tableDataInfo.setRows(list);
tableDataInfo.setTotal(count);
return tableDataInfo;
}
/**
* 转换为TableDataInfo对象
*
* @param list 源对象
* @param map 转换方法
* @param <T> 转换后的对象类型
* @param <R> 需要转换的对象类型
* @return 转换后的对象
*/
public static <T, R> TableDataInfo<T> convertDataTable(List<R> list, Function<R, T> map) {
Long total = PageUtils.getTotal(list);
List<T> convertList = list.stream().filter(item -> !ObjectUtils.isEmpty(item)).map(map).collect(Collectors.toList());
return convertDataTable(convertList, total);
}
/**
* 清理分页的线程变量
*/
public static void clearPage() {
PageHelper.clearPage();
}
}

View File

@@ -0,0 +1 @@
cn.fateverse.common.mybatis.MybatisAutoConfiguration