From 810f0c6fd65022e215dcd390127f8da39ff05f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BB=96=E9=87=91=E9=BE=99?= Date: Mon, 29 Jun 2020 18:19:13 +0800 Subject: [PATCH] =?UTF-8?q?#=20=E6=8A=9B=E5=BC=83=20=E6=94=B9=E9=80=A0=20M?= =?UTF-8?q?ybatisPlus=20=E6=BA=90=E7=A0=81=E6=96=B9=E5=BC=8F=20=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81JPA=20=E6=B3=A8=E8=A7=A3=EF=BC=8C=E5=9C=A8EL?= =?UTF-8?q?=20=E4=B8=AD=20=E8=BF=9B=E8=A1=8CMybatisPlus=20=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/metadata/ElTableInfoHelper.java | 556 ++++++++++++++ .../zhengjie/base/{ => mybatis}/BaseDao.java | 4 +- .../me/zhengjie/base/mybatis/ServiceImpl.java | 277 +++++++ .../zhengjie/mybatis/AbstractSqlInjector.java | 105 +++ .../zhengjie/mybatis/DefaultSqlInjector.java | 60 ++ .../annotation/impl/TableFieldImp.java | 269 +++++++ .../mybatis/annotation/impl/TableIdImp.java | 75 ++ .../mybatis/annotation/impl/TableNameImp.java | 139 ++++ .../autoconfig/ConfigurationCustomizer.java | 38 + .../MybatisPlusAutoConfiguration.java | 321 +++++++++ .../autoconfig/MybatisPlusProperties.java | 135 ++++ .../MybatisPlusPropertiesCustomizer.java | 36 + .../autoconfig/SafetyEncryptProcessor.java | 78 ++ .../mybatis/autoconfig/SpringBootVFS.java | 67 ++ .../mybatis/core/GlobalConfigUtils.java | 100 +++ .../mybatis/core/MybatisConfiguration.java | 172 +++++ .../core/MybatisMapperAnnotationBuilder.java | 653 +++++++++++++++++ .../mybatis/core/MybatisMapperRegistry.java | 100 +++ .../core/MybatisSqlSessionFactoryBean.java | 681 ++++++++++++++++++ .../core/MybatisSqlSessionFactoryBuilder.java | 110 +++ .../me/zhengjie/mybatis/package-info.java | 19 + .../me/zhengjie/repository/ColumnInfoDao.java | 21 +- .../repository/mp/ColumnInfoMapper.java | 15 + .../repository/mp/ColumnInfoService.java | 17 +- .../src/main/java/me/zhengjie/AppRun.java | 2 + .../me/zhengjie/config/MybatisPlusConfig.java | 10 - ...itional-spring-configuration-metadata.json | 88 +++ .../main/resources/META-INF/spring.factories | 5 + pom.xml | 4 +- 29 files changed, 4140 insertions(+), 17 deletions(-) create mode 100644 eladmin-common/src/main/java/com/baomidou/mybatisplus/core/metadata/ElTableInfoHelper.java rename eladmin-common/src/main/java/me/zhengjie/base/{ => mybatis}/BaseDao.java (97%) create mode 100644 eladmin-common/src/main/java/me/zhengjie/base/mybatis/ServiceImpl.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/AbstractSqlInjector.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/DefaultSqlInjector.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableFieldImp.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableIdImp.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableNameImp.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/ConfigurationCustomizer.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusAutoConfiguration.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusProperties.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusPropertiesCustomizer.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SafetyEncryptProcessor.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SpringBootVFS.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/GlobalConfigUtils.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisConfiguration.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperAnnotationBuilder.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperRegistry.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBean.java create mode 100644 eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBuilder.java create mode 100755 eladmin-common/src/main/java/me/zhengjie/mybatis/package-info.java delete mode 100644 eladmin-system/src/main/java/me/zhengjie/config/MybatisPlusConfig.java create mode 100755 eladmin-system/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100755 eladmin-system/src/main/resources/META-INF/spring.factories diff --git a/eladmin-common/src/main/java/com/baomidou/mybatisplus/core/metadata/ElTableInfoHelper.java b/eladmin-common/src/main/java/com/baomidou/mybatisplus/core/metadata/ElTableInfoHelper.java new file mode 100644 index 00000000..39e92a66 --- /dev/null +++ b/eladmin-common/src/main/java/com/baomidou/mybatisplus/core/metadata/ElTableInfoHelper.java @@ -0,0 +1,556 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baomidou.mybatisplus.core.metadata; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; +import com.baomidou.mybatisplus.core.toolkit.*; +import me.zhengjie.mybatis.annotation.impl.TableFieldImp; +import me.zhengjie.mybatis.annotation.impl.TableIdImp; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.executor.keygen.KeyGenerator; +import org.apache.ibatis.executor.keygen.SelectKeyGenerator; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.reflection.Reflector; +import org.apache.ibatis.reflection.ReflectorFactory; +import org.apache.ibatis.session.Configuration; + +import javax.persistence.*; +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.stream.Collectors.toList; + +/** + *

+ * 针对ElAdmin 原有JPA 注解 定向改造 支持基本的JPA 注解 + * 实体类反射表辅助类 + *

+ * + * @author hubin sjy + * @author liaojinlong + * @since 2020/6/29 18:07 + */ +public class ElTableInfoHelper { + + private static final Log logger = LogFactory.getLog(ElTableInfoHelper.class); + + /** + * 储存反射类表信息 + */ + private static final Map, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap<>(); + + /** + * 默认表主键名称 + */ + private static final String DEFAULT_ID_NAME = "id"; + + /** + *

+ * 获取实体映射表信息 + *

+ * + * @param clazz 反射实体类 + * @return 数据库表反射信息 + */ + public static TableInfo getTableInfo(Class clazz) { + if (clazz == null + || ReflectionKit.isPrimitiveOrWrapper(clazz) + || clazz == String.class) { + return null; + } + // https://github.com/baomidou/mybatis-plus/issues/299 + TableInfo tableInfo = TABLE_INFO_CACHE.get(ClassUtils.getUserClass(clazz)); + if (null != tableInfo) { + return tableInfo; + } + //尝试获取父类缓存 + Class currentClass = clazz; + while (null == tableInfo && Object.class != currentClass) { + currentClass = currentClass.getSuperclass(); + tableInfo = TABLE_INFO_CACHE.get(ClassUtils.getUserClass(currentClass)); + } + if (tableInfo != null) { + TABLE_INFO_CACHE.put(ClassUtils.getUserClass(clazz), tableInfo); + } + return tableInfo; + } + + /** + *

+ * 获取所有实体映射表信息 + *

+ * + * @return 数据库表反射信息集合 + */ + @SuppressWarnings("unused") + public static List getTableInfos() { + return new ArrayList<>(TABLE_INFO_CACHE.values()); + } + + /** + *

+ * 实体类反射获取表信息【初始化】 + *

+ * + * @param clazz 反射实体类 + * @return 数据库表反射信息 + */ + public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class clazz) { + TableInfo tableInfo = TABLE_INFO_CACHE.get(clazz); + if (tableInfo != null) { + if (builderAssistant != null) { + tableInfo.setConfiguration(builderAssistant.getConfiguration()); + } + return tableInfo; + } + + /* 没有获取到缓存信息,则初始化 */ + tableInfo = new TableInfo(clazz); + GlobalConfig globalConfig; + if (null != builderAssistant) { + tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace()); + tableInfo.setConfiguration(builderAssistant.getConfiguration()); + globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration()); + } else { + // 兼容测试场景 + globalConfig = GlobalConfigUtils.defaults(); + } + + /* 初始化表名相关 */ + final String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo); + + List excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList(); + + /* 初始化字段相关 */ + initTableFields(clazz, globalConfig, tableInfo, excludePropertyList); + + /* 放入缓存 */ + TABLE_INFO_CACHE.put(clazz, tableInfo); + + /* 缓存 lambda */ + LambdaUtils.installCache(tableInfo); + + /* 自动构建 resultMap */ + tableInfo.initResultMapIfNeed(); + + return tableInfo; + } + + /** + *

+ * 初始化 表数据库类型,表名,resultMap + *

+ * + * @param clazz 实体类 + * @param globalConfig 全局配置 + * @param tableInfo 数据库表反射信息 + * @return 需要排除的字段名 + */ + private static String[] initTableName(Class clazz, GlobalConfig globalConfig, TableInfo tableInfo) { + /* 数据库全局配置 */ + GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); + /** + * JPA 注解 + */ + final Table jpaTableAnnotation = clazz.getAnnotation(Table.class); + /** + * MP 注解 + */ + final TableName mpTableAnnotation = clazz.getAnnotation(TableName.class); + + String tableName = clazz.getSimpleName(); + String tablePrefix = dbConfig.getTablePrefix(); + String schema = dbConfig.getSchema(); + boolean tablePrefixEffect = true; + String[] excludeProperty = null; + /** + * JPA 注解优先加载,后期 MP 可以覆盖 + */ + if (Objects.nonNull(jpaTableAnnotation)) { + if (StringUtils.isNotBlank(jpaTableAnnotation.name())) { + tableName = jpaTableAnnotation.name(); + } else { + tableName = initTableNameWithDbConfig(tableName, dbConfig); + } + if (StringUtils.isNotBlank(jpaTableAnnotation.schema())) { + schema = jpaTableAnnotation.schema(); + } + } + /** + * MP table 注解 + */ + if (mpTableAnnotation != null) { + if (StringUtils.isNotBlank(mpTableAnnotation.value())) { + tableName = mpTableAnnotation.value(); + if (StringUtils.isNotBlank(tablePrefix) && !mpTableAnnotation.keepGlobalPrefix()) { + tablePrefixEffect = false; + } + } else if (Objects.isNull(jpaTableAnnotation)) { + tableName = initTableNameWithDbConfig(tableName, dbConfig); + } + if (StringUtils.isNotBlank(mpTableAnnotation.schema())) { + schema = mpTableAnnotation.schema(); + } + /* 表结果集映射 */ + if (StringUtils.isNotBlank(mpTableAnnotation.resultMap())) { + tableInfo.setResultMap(mpTableAnnotation.resultMap()); + } + tableInfo.setAutoInitResultMap(mpTableAnnotation.autoResultMap()); + excludeProperty = mpTableAnnotation.excludeProperty(); + } else if (Objects.isNull(jpaTableAnnotation)) { + tableName = initTableNameWithDbConfig(tableName, dbConfig); + } + + String targetTableName = tableName; + if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) { + targetTableName = tablePrefix + targetTableName; + } + if (StringUtils.isNotBlank(schema)) { + targetTableName = schema + StringPool.DOT + targetTableName; + } + + tableInfo.setTableName(targetTableName); + + /* 开启了自定义 KEY 生成器 */ + if (null != dbConfig.getKeyGenerator()) { + tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class)); + } + return excludeProperty; + } + + /** + * 根据 DbConfig 初始化 表名 + * + * @param className 类名 + * @param dbConfig DbConfig + * @return 表名 + */ + private static String initTableNameWithDbConfig(String className, GlobalConfig.DbConfig dbConfig) { + String tableName = className; + // 开启表名下划线申明 + if (dbConfig.isTableUnderline()) { + tableName = StringUtils.camelToUnderline(tableName); + } + // 大写命名判断 + if (dbConfig.isCapitalMode()) { + tableName = tableName.toUpperCase(); + } else { + // 首字母小写 + tableName = StringUtils.firstToLowerCase(tableName); + } + return tableName; + } + + /** + *

+ * 初始化 表主键,表字段 + *

+ * + * @param clazz 实体类 + * @param globalConfig 全局配置 + * @param tableInfo 数据库表反射信息 + */ + public static void initTableFields(Class clazz, GlobalConfig globalConfig, TableInfo tableInfo, List excludeProperty) { + /* 数据库全局配置 */ + GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); + ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory(); + //TODO @咩咩 有空一起来撸完这反射模块. + Reflector reflector = reflectorFactory.findForClass(clazz); + List list = getAllFields(clazz); + // 标记是否读取到主键 + boolean isReadPK = false; + /** + * 使用JPA 初始化 + */ + boolean jpaReadPK = false; + // 是否存在 @TableId 注解 + boolean existTableId = isExistTableId(list); + + List fieldList = new ArrayList<>(list.size()); + for (Field field : list) { + /** + * 手动排除或者使用 transient注解的 排除 + */ + final boolean exclude = excludeProperty.contains(field.getName()); + final boolean transientField = Objects.nonNull(field.getAnnotation(Transient.class)); + if (exclude || transientField) { + continue; + } + + /* 主键ID 初始化 */ + if (existTableId) { + Id id = field.getAnnotation(Id.class); + TableId tableId = field.getAnnotation(TableId.class); + if (tableId != null || id != null) { + if (isReadPK) { + if (jpaReadPK) { + throw ExceptionUtils.mpe("JPA @Id has been Init: \"%s\".", clazz.getName()); + } else { + throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName()); + } + } else { + if (Objects.isNull(tableId)) { + TableIdImp tableIdImp = new TableIdImp(); + Column column = field.getAnnotation(Column.class); + if (Objects.nonNull(column)) { + tableIdImp.setValue(column.name()); + } + GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class); + if (Objects.nonNull(generatedValue)) { + tableIdImp.setType(IdType.ASSIGN_ID); + logger.warn("JPA compatible mode, []com.baomidou.mybatisplus.annotation.IdType.ASSIGN_ID] is the only way to generate primary key"); + } + tableId = tableIdImp; + jpaReadPK = true; + + } + initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector); + isReadPK = true; + continue; + } + } + } else if (!isReadPK) { + isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector); + if (isReadPK) { + continue; + } + } + TableField tableField = field.getAnnotation(TableField.class); + + /* 有 @TableField 注解的字段初始化 */ + if (tableField != null) { + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField)); + continue; + } else { + final Column column = field.getAnnotation(Column.class); + if (Objects.nonNull(column)) { + TableFieldImp tableFieldImp = new TableFieldImp(); + tableFieldImp.setValue(column.name()); + tableField = tableFieldImp; + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField)); + continue; + } + } + + /* 无 @TableField 注解的字段初始化 */ + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field)); + } + + /* 检查逻辑删除字段只能有最多一个 */ + Assert.isTrue(fieldList.parallelStream().filter(com.baomidou.mybatisplus.core.metadata.TableFieldInfo::isLogicDelete).count() < 2L, + String.format("@TableLogic can't more than one in Class: \"%s\".", clazz.getName())); + + /* 字段列表,不可变集合 */ + tableInfo.setFieldList(Collections.unmodifiableList(fieldList)); + + /* 未发现主键注解,提示警告信息 */ + if (!isReadPK) { + logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName())); + } + } + + /** + *

+ * 判断主键注解是否存在 + *

+ * + * @param list 字段列表 + * @return true 为存在 @TableId 注解; + */ + public static boolean isExistTableId(List list) { + return list.stream().anyMatch(field -> + field.isAnnotationPresent(TableId.class) || + field.isAnnotationPresent(Id.class)); + } + + /** + *

+ * 主键属性初始化 + *

+ * + * @param dbConfig 全局配置信息 + * @param tableInfo 表信息 + * @param field 字段 + * @param tableId 注解 + * @param reflector Reflector + */ + private static void initTableIdWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, + Field field, TableId tableId, Reflector reflector) { + boolean underCamel = tableInfo.isUnderCamel(); + final String property = field.getName(); + if (field.getAnnotation(TableField.class) != null) { + logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!", + property, tableInfo.getEntityType().getName())); + } + /* 主键策略( 注解 > 全局 ) */ + // 设置 Sequence 其他策略无效 + if (IdType.NONE == tableId.type()) { + tableInfo.setIdType(dbConfig.getIdType()); + } else { + tableInfo.setIdType(tableId.type()); + } + + /* 字段 */ + String column = property; + if (StringUtils.isNotBlank(tableId.value())) { + column = tableId.value(); + } else { + // 开启字段下划线申明 + if (underCamel) { + column = StringUtils.camelToUnderline(column); + } + // 全局大写命名 + if (dbConfig.isCapitalMode()) { + column = column.toUpperCase(); + } + } + final Class keyType = reflector.getGetterType(property); + if (keyType.isPrimitive()) { + logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", + property, tableInfo.getEntityType().getName())); + } + tableInfo.setKeyRelated(checkRelated(underCamel, property, column)) + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(keyType); + } + + + /** + *

+ * 主键属性初始化 + *

+ * + * @param tableInfo 表信息 + * @param field 字段 + * @param reflector Reflector + * @return true 继续下一个属性判断,返回 continue; + */ + private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, + Field field, Reflector reflector) { + final String property = field.getName(); + if (DEFAULT_ID_NAME.equalsIgnoreCase(property)) { + if (field.getAnnotation(TableField.class) != null) { + logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!", + property, tableInfo.getEntityType().getName())); + } + String column = property; + if (dbConfig.isCapitalMode()) { + column = column.toUpperCase(); + } + tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column)) + .setIdType(dbConfig.getIdType()) + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(reflector.getGetterType(property)); + return true; + } + return false; + } + + /** + *

+ * 字段属性初始化 + *

+ * + * @param dbConfig 数据库全局配置 + * @param tableInfo 表信息 + * @param fieldList 字段列表 + * @return true 继续下一个属性判断,返回 continue; + */ + private static boolean initTableFieldWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, + List fieldList, Field field) { + /* 获取注解属性,自定义字段 */ + TableField tableField = field.getAnnotation(TableField.class); + if (null == tableField) { + return false; + } + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField)); + return true; + } + + /** + *

+ * 判定 related 的值 + *

+ * + * @param underCamel 驼峰命名 + * @param property 属性名 + * @param column 字段名 + * @return related + */ + public static boolean checkRelated(boolean underCamel, String property, String column) { + if (StringUtils.isNotColumnName(column)) { + // 首尾有转义符,手动在注解里设置了转义符,去除掉转义符 + column = column.substring(1, column.length() - 1); + } + String propertyUpper = property.toUpperCase(Locale.ENGLISH); + String columnUpper = column.toUpperCase(Locale.ENGLISH); + if (underCamel) { + // 开启了驼峰并且 column 包含下划线 + return !(propertyUpper.equals(columnUpper) || + propertyUpper.equals(columnUpper.replace(StringPool.UNDERSCORE, StringPool.EMPTY))); + } else { + // 未开启驼峰,直接判断 property 是否与 column 相同(全大写) + return !propertyUpper.equals(columnUpper); + } + } + + /** + *

+ * 获取该类的所有属性列表 + *

+ * + * @param clazz 反射类 + * @return 属性集合 + */ + public static List getAllFields(Class clazz) { + List fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz)); + return fieldList.stream() + .filter(field -> { + /* 过滤注解非表字段属性 */ + TableField tableField = field.getAnnotation(TableField.class); + return (tableField == null || tableField.exist()); + }).collect(toList()); + } + + public static KeyGenerator genKeyGenerator(String baseStatementId, TableInfo tableInfo, MapperBuilderAssistant builderAssistant) { + IKeyGenerator keyGenerator = GlobalConfigUtils.getKeyGenerator(builderAssistant.getConfiguration()); + if (null == keyGenerator) { + throw new IllegalArgumentException("not configure IKeyGenerator implementation class."); + } + Configuration configuration = builderAssistant.getConfiguration(); + //TODO 这里不加上builderAssistant.getCurrentNamespace()的会导致com.baomidou.mybatisplus.core.parser.SqlParserHelper.getSqlParserInfo越(chu)界(gui) + String id = builderAssistant.getCurrentNamespace() + StringPool.DOT + baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX; + ResultMap resultMap = new ResultMap.Builder(builderAssistant.getConfiguration(), id, tableInfo.getKeyType(), new ArrayList<>()).build(); + MappedStatement mappedStatement = new MappedStatement.Builder(builderAssistant.getConfiguration(), id, + new StaticSqlSource(configuration, keyGenerator.executeSql(tableInfo.getKeySequence().value())), SqlCommandType.SELECT) + .keyProperty(tableInfo.getKeyProperty()) + .resultMaps(Collections.singletonList(resultMap)) + .build(); + configuration.addMappedStatement(mappedStatement); + return new SelectKeyGenerator(mappedStatement, true); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/base/BaseDao.java b/eladmin-common/src/main/java/me/zhengjie/base/mybatis/BaseDao.java similarity index 97% rename from eladmin-common/src/main/java/me/zhengjie/base/BaseDao.java rename to eladmin-common/src/main/java/me/zhengjie/base/mybatis/BaseDao.java index ea81ab99..cb1a49a3 100644 --- a/eladmin-common/src/main/java/me/zhengjie/base/BaseDao.java +++ b/eladmin-common/src/main/java/me/zhengjie/base/mybatis/BaseDao.java @@ -1,4 +1,4 @@ -package me.zhengjie.base; +package me.zhengjie.base.mybatis; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.IService; @@ -20,7 +20,7 @@ import java.util.List; public class BaseDao, J extends JpaRepository, T, ID extends Serializable> { protected I mpService; protected J jpaRepository; - @Value("${db.type.switch:true}") + @Value("${db.type.switch:false}") protected boolean dbSwitch; public BaseDao(I mpService, J jpaRepository) { diff --git a/eladmin-common/src/main/java/me/zhengjie/base/mybatis/ServiceImpl.java b/eladmin-common/src/main/java/me/zhengjie/base/mybatis/ServiceImpl.java new file mode 100644 index 00000000..5398125e --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/base/mybatis/ServiceImpl.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package me.zhengjie.base.mybatis; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.enums.SqlMethod; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.ElTableInfoHelper; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.toolkit.*; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; +import org.apache.ibatis.binding.MapperMethod; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.reflection.ExceptionUtil; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.MyBatisExceptionTranslator; +import org.mybatis.spring.SqlSessionHolder; +import org.mybatis.spring.SqlSessionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 ) + * + * @author hubin + * @since 2018-06-23 + */ +@SuppressWarnings("unchecked") +public class ServiceImpl, T> implements IService { + + protected Log log = LogFactory.getLog(getClass()); + + @Autowired + protected M baseMapper; + + @Override + public M getBaseMapper() { + return baseMapper; + } + + protected Class entityClass = currentModelClass(); + + /** + * 判断数据库操作是否成功 + * + * @param result 数据库操作返回影响条数 + * @return boolean + * @deprecated 3.3.1 + */ + @Deprecated + protected boolean retBool(Integer result) { + return SqlHelper.retBool(result); + } + + protected Class currentModelClass() { + return (Class) ReflectionKit.getSuperClassGenericType(getClass(), 1); + } + + /** + * 批量操作 SqlSession + * + * @deprecated 3.3.0 + */ + @Deprecated + protected SqlSession sqlSessionBatch() { + return SqlHelper.sqlSessionBatch(entityClass); + } + + /** + * 释放sqlSession + * + * @param sqlSession session + * @deprecated 3.3.0 + */ + @Deprecated + protected void closeSqlSession(SqlSession sqlSession) { + SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(entityClass)); + } + + /** + * 获取 SqlStatement + * + * @param sqlMethod ignore + * @return ignore + */ + protected String sqlStatement(SqlMethod sqlMethod) { + return SqlHelper.table(entityClass).getSqlStatement(sqlMethod.getMethod()); + } + + /** + * 批量插入 + * + * @param entityList ignore + * @param batchSize ignore + * @return ignore + */ + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveBatch(Collection entityList, int batchSize) { + String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE); + return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity)); + } + + /** + * TableId 注解存在更新记录,否插入一条记录 + * + * @param entity 实体对象 + * @return boolean + */ + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveOrUpdate(T entity) { + if (null != entity) { + Class cls = entity.getClass(); + TableInfo tableInfo = ElTableInfoHelper.getTableInfo(cls); + Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!"); + String keyProperty = tableInfo.getKeyProperty(); + Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!"); + Object idVal = ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty()); + return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity); + } + return false; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean saveOrUpdateBatch(Collection entityList, int batchSize) { + TableInfo tableInfo = ElTableInfoHelper.getTableInfo(entityClass); + Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!"); + String keyProperty = tableInfo.getKeyProperty(); + Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!"); + return executeBatch(entityList, batchSize, (sqlSession, entity) -> { + Object idVal = ReflectionKit.getFieldValue(entity, keyProperty); + if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) { + sqlSession.insert(tableInfo.getSqlStatement(SqlMethod.INSERT_ONE.getMethod()), entity); + } else { + MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); + param.put(Constants.ENTITY, entity); + sqlSession.update(tableInfo.getSqlStatement(SqlMethod.UPDATE_BY_ID.getMethod()), param); + } + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean updateBatchById(Collection entityList, int batchSize) { + String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID); + return executeBatch(entityList, batchSize, (sqlSession, entity) -> { + MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); + param.put(Constants.ENTITY, entity); + sqlSession.update(sqlStatement, param); + }); + } + + @Override + public T getOne(Wrapper queryWrapper, boolean throwEx) { + if (throwEx) { + return baseMapper.selectOne(queryWrapper); + } + return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper)); + } + + @Override + public Map getMap(Wrapper queryWrapper) { + return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper)); + } + + @Override + public V getObj(Wrapper queryWrapper, Function mapper) { + return SqlHelper.getObject(log, listObjs(queryWrapper, mapper)); + } + + /** + * 执行批量操作 + * + * @param consumer consumer + * @since 3.3.0 + * @deprecated 3.3.1 后面我打算移除掉 {@link #executeBatch(Collection, int, BiConsumer)} }. + */ + @Deprecated + protected boolean executeBatch(Consumer consumer) { + SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityClass); + SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory); + boolean transaction = TransactionSynchronizationManager.isSynchronizationActive(); + if (sqlSessionHolder != null) { + SqlSession sqlSession = sqlSessionHolder.getSqlSession(); + //原生无法支持执行器切换,当存在批量操作时,会嵌套两个session的,优先commit上一个session + //按道理来说,这里的值应该一直为false。 + sqlSession.commit(!transaction); + } + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + if (!transaction) { + log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional"); + } + try { + consumer.accept(sqlSession); + //非事物情况下,强制commit。 + sqlSession.commit(!transaction); + return true; + } catch (Throwable t) { + sqlSession.rollback(); + Throwable unwrapped = ExceptionUtil.unwrapThrowable(t); + if (unwrapped instanceof RuntimeException) { + MyBatisExceptionTranslator myBatisExceptionTranslator + = new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true); + throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped)); + } + throw ExceptionUtils.mpe(unwrapped); + } finally { + sqlSession.close(); + } + } + + /** + * 执行批量操作 + * + * @param list 数据集合 + * @param batchSize 批量大小 + * @param consumer 执行方法 + * @param 泛型 + * @return 操作结果 + * @since 3.3.1 + */ + protected boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) { + Assert.isFalse(batchSize < 1, "batchSize must not be less than one"); + return !CollectionUtils.isEmpty(list) && executeBatch(sqlSession -> { + int size = list.size(); + int i = 1; + for (E element : list) { + consumer.accept(sqlSession, element); + if ((i % batchSize == 0) || i == size) { + sqlSession.flushStatements(); + } + i++; + } + }); + } + + /** + * 执行批量操作(默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE}) + * + * @param list 数据集合 + * @param consumer 执行方法 + * @param 泛型 + * @return 操作结果 + * @since 3.3.1 + */ + protected boolean executeBatch(Collection list, BiConsumer consumer) { + return executeBatch(list, DEFAULT_BATCH_SIZE, consumer); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/AbstractSqlInjector.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/AbstractSqlInjector.java new file mode 100644 index 00000000..65a0ea6f --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/AbstractSqlInjector.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.ISqlInjector; +import com.baomidou.mybatisplus.core.metadata.ElTableInfoHelper; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.toolkit.ArrayUtils; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Set; + +/** + * SQL 自动注入器 + * + * @author hubin + * @author liaojinlong + * @since 2020/6/29 18:14 + */ +public abstract class AbstractSqlInjector implements ISqlInjector { + + private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class); + + @Override + public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { + Class modelClass = extractModelClass(mapperClass); + if (modelClass != null) { + String className = mapperClass.toString(); + Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); + if (!mapperRegistryCache.contains(className)) { + List methodList = this.getMethodList(mapperClass); + if (CollectionUtils.isNotEmpty(methodList)) { + TableInfo tableInfo = ElTableInfoHelper.initTableInfo(builderAssistant, modelClass); + // 循环注入自定义方法 + methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); + } else { + logger.debug(mapperClass.toString() + ", No effective injection method was found."); + } + mapperRegistryCache.add(className); + } + } + } + + /** + *

+ * 获取 注入的方法 + *

+ * + * @param mapperClass 当前mapper + * @return 注入的方法集合 + * @since 3.1.2 add mapperClass + */ + public abstract List getMethodList(Class mapperClass); + + /** + * 提取泛型模型,多泛型的时候请将泛型T放在第一位 + * + * @param mapperClass mapper 接口 + * @return mapper 泛型 + */ + protected Class extractModelClass(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]; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/DefaultSqlInjector.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/DefaultSqlInjector.java new file mode 100644 index 00000000..127d5ac2 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/DefaultSqlInjector.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.methods.*; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; + +/** + * SQL 默认注入器 + * + * @author hubin + * /** + * @author liaojinlong + * @since 2020/6/29 18:14 + */ +@Component +public class DefaultSqlInjector extends AbstractSqlInjector { + + @Override + public List getMethodList(Class mapperClass) { + return Stream.of( + new Insert(), + new Delete(), + new DeleteByMap(), + new DeleteById(), + new DeleteBatchByIds(), + new Update(), + new UpdateById(), + new SelectById(), + new SelectBatchByIds(), + new SelectByMap(), + new SelectOne(), + new SelectCount(), + new SelectMaps(), + new SelectMapsPage(), + new SelectObjs(), + new SelectList(), + new SelectPage() + ).collect(toList()); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableFieldImp.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableFieldImp.java new file mode 100644 index 00000000..98fff17f --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableFieldImp.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package me.zhengjie.mybatis.annotation.impl; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.UnknownTypeHandler; + +import java.lang.annotation.Annotation; + +/** + * JPA 转换使用 javax.persistence.Column + * + * @author liaojinlong + * @since 2020/6/28 14:11 + */ +public class TableFieldImp implements TableField { + private String value = ""; + private Class annotationType; + private boolean exist = true; + private String condition = ""; + private FieldStrategy insertStrategy = FieldStrategy.DEFAULT; + private String update = ""; + private FieldStrategy updateStrategy = FieldStrategy.DEFAULT; + private FieldStrategy whereStrategy = FieldStrategy.DEFAULT; + private FieldFill fill = FieldFill.DEFAULT; + private boolean select = true; + private boolean keepGlobalFormat = false; + private JdbcType jdbcType = JdbcType.UNDEFINED; + private Class typeHandler = UnknownTypeHandler.class; + private String numericScale = ""; + + /** + * 数据库字段值, + * 不需要配置该值的情况: + *

  • 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 true 时, + * (mp下默认是true,mybatis默认是false), 数据库字段值.replace("_","").toUpperCase() == 实体属性名.toUpperCase()
  • + *
  • 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 false 时, + * 数据库字段值.toUpperCase() == 实体属性名.toUpperCase()
  • + */ + @Override + public String value() { + return value; + } + + /** + * 是否为数据库表字段 + * 默认 true 存在,false 不存在 + */ + @Override + public boolean exist() { + return exist; + } + + /** + * 字段 where 实体查询比较条件 + * 默认 `=` 等值 + */ + @Override + public String condition() { + return condition; + } + + /** + * 字段 update set 部分注入, 该注解优于 el 注解使用 + *

    + * 例1:@TableField(.. , update="%s+1") 其中 %s 会填充为字段 + * 输出 SQL 为:update 表 set 字段=字段+1 where ... + *

    + * 例2:@TableField(.. , update="now()") 使用数据库时间 + * 输出 SQL 为:update 表 set 字段=now() where ... + */ + @Override + public String update() { + return update; + } + + /** + * 字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略 + * IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty}); + * NOT_NULL: insert into table_a(column) values (#{columnProperty}) + * NOT_EMPTY: insert into table_a(column) values (#{columnProperty}) + * + * @since 3.1.2 + */ + @Override + public FieldStrategy insertStrategy() { + return insertStrategy; + } + + /** + * 字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略 + * IGNORED: 直接拼接 update table_a set column=#{columnProperty}, 属性为null/空string都会被set进去 + * NOT_NULL: update table_a set column=#{columnProperty} + * NOT_EMPTY: update table_a set column=#{columnProperty} + * + * @since 3.1.2 + */ + @Override + public FieldStrategy updateStrategy() { + return updateStrategy; + } + + /** + * 字段验证策略之 where: 表示该字段在拼接where条件时的策略 + * IGNORED: 直接拼接 column=#{columnProperty} + * NOT_NULL: column=#{columnProperty} + * NOT_EMPTY: column=#{columnProperty} + * + * @since 3.1.2 + */ + @Override + public FieldStrategy whereStrategy() { + return whereStrategy; + } + + /** + * 字段自动填充策略 + */ + @Override + public FieldFill fill() { + return fill; + } + + /** + * 是否进行 select 查询 + *

    大字段可设置为 false 不加入 select 查询范围

    + */ + @Override + public boolean select() { + return select; + } + + /** + * 是否保持使用全局的 Format 的值 + *

    只生效于 既设置了全局的 Format 也设置了上面 {@link #value()} 的值

    + *
  • 如果是 false , 全局的 Format 不生效
  • + * + * @since 3.1.1 + */ + @Override + public boolean keepGlobalFormat() { + return keepGlobalFormat; + } + + /** + * JDBC类型 (该默认值不代表会按照该值生效), + * 只生效与 mp 自动注入的 method, + * 建议配合 {@link TableName#autoResultMap()} 一起使用 + *

    + * {@link ResultMapping#jdbcType} and {@link ParameterMapping#jdbcType} + * + * @since 3.1.2 + */ + @Override + public JdbcType jdbcType() { + return jdbcType; + } + + /** + * 类型处理器 (该默认值不代表会按照该值生效), + * 只生效与 mp 自动注入的 method, + * 建议配合 {@link TableName#autoResultMap()} 一起使用 + *

    + * {@link ResultMapping#typeHandler} and {@link ParameterMapping#typeHandler} + * + * @since 3.1.2 + */ + @Override + public Class typeHandler() { + return typeHandler; + } + + /** + * 指定小数点后保留的位数, + * 只生效与 mp 自动注入的 method, + * 建议配合 {@link TableName#autoResultMap()} 一起使用 + *

    + * {@link ParameterMapping#numericScale} + * + * @since 3.1.2 + */ + @Override + public String numericScale() { + return numericScale; + } + + /** + * Returns the annotation type of this annotation. + * + * @return the annotation type of this annotation + */ + @Override + public Class annotationType() { + return annotationType; + } + + public void setValue(String value) { + this.value = value; + } + + public void setAnnotationType(Class annotationType) { + this.annotationType = annotationType; + } + + public void setExist(boolean exist) { + this.exist = exist; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public void setInsertStrategy(FieldStrategy insertStrategy) { + this.insertStrategy = insertStrategy; + } + + public void setUpdate(String update) { + this.update = update; + } + + public void setUpdateStrategy(FieldStrategy updateStrategy) { + this.updateStrategy = updateStrategy; + } + + public void setWhereStrategy(FieldStrategy whereStrategy) { + this.whereStrategy = whereStrategy; + } + + public void setFill(FieldFill fill) { + this.fill = fill; + } + + public void setSelect(boolean select) { + this.select = select; + } + + public void setKeepGlobalFormat(boolean keepGlobalFormat) { + this.keepGlobalFormat = keepGlobalFormat; + } + + public void setJdbcType(JdbcType jdbcType) { + this.jdbcType = jdbcType; + } + + public void setTypeHandler(Class typeHandler) { + this.typeHandler = typeHandler; + } + + public void setNumericScale(String numericScale) { + this.numericScale = numericScale; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableIdImp.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableIdImp.java new file mode 100644 index 00000000..79859ad6 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableIdImp.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

    + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

    + * https://www.apache.org/licenses/LICENSE-2.0 + *

    + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package me.zhengjie.mybatis.annotation.impl; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.lang.annotation.Annotation; + +/** + * JPA 转换 ID + * javax.persistence.Id + * javax.persistence.Column + * + * @author liaojinlong + * @since 2020/6/28 14:01 + */ +public class TableIdImp implements TableId { + private String value = ""; + private IdType type = IdType.NONE; + private Class annotationType; + + + /** + * 字段值(驼峰命名方式,该值可无) + */ + @Override + public String value() { + return value; + } + + /** + * 主键ID + * {@link IdType} + */ + @Override + public IdType type() { + return type; + } + + /** + * Returns the annotation type of this annotation. + * + * @return the annotation type of this annotation + */ + @Override + public Class annotationType() { + return annotationType; + } + + public void setValue(String value) { + this.value = value; + } + + public void setType(IdType type) { + this.type = type; + } + + public void setAnnotationType(Class annotationType) { + this.annotationType = annotationType; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableNameImp.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableNameImp.java new file mode 100644 index 00000000..6a52211e --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/annotation/impl/TableNameImp.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

    + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

    + * https://www.apache.org/licenses/LICENSE-2.0 + *

    + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package me.zhengjie.mybatis.annotation.impl; + +import com.baomidou.mybatisplus.annotation.TableName; + +import java.lang.annotation.Annotation; + +/** + * JAP 转换使用 + * + * @author liaojinlong + * javax.persistence.Table + * @since 2020/6/28 13:56 + */ +public class TableNameImp implements TableName { + private String value = ""; + private String schema = ""; + private String resultMap = ""; + private boolean keepGlobalPrefix = false; + private String[] excludeProperty = new String[0]; + private boolean autoResultMap = false; + private Class annotationType; + + /** + * 实体对应的表名 + */ + @Override + public String value() { + return value; + } + + /** + * schema + * + * @since 3.1.1 + */ + @Override + public String schema() { + return schema; + } + + + /** + * 是否保持使用全局的 tablePrefix 的值 + *

    只生效于 既设置了全局的 tablePrefix 也设置了上面 {@link #value()} 的值

    + *
  • 如果是 false , 全局的 tablePrefix 不生效
  • + * + * @since 3.1.1 + */ + @Override + public boolean keepGlobalPrefix() { + return keepGlobalPrefix; + } + + /** + * 实体映射结果集, + * 只生效与 mp 自动注入的 method + */ + @Override + public String resultMap() { + return resultMap; + } + + /** + * 是否自动构建 resultMap 并使用, + * 只生效与 mp 自动注入的 method, + * 如果设置 resultMap 则不会进行 resultMap 的自动构建并注入, + * 只适合个别字段 设置了 typeHandler 或 jdbcType 的情况 + * + * @since 3.1.2 + */ + @Override + public boolean autoResultMap() { + return autoResultMap; + } + + + /** + * 需要排除的属性名 + * + * @since 3.3.1 + */ + @Override + public String[] excludeProperty() { + return excludeProperty; + } + + /** + * Returns the annotation type of this annotation. + * + * @return the annotation type of this annotation + */ + @Override + public Class annotationType() { + return annotationType; + } + + public void setValue(String value) { + this.value = value; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public void setResultMap(String resultMap) { + this.resultMap = resultMap; + } + + public void setKeepGlobalPrefix(boolean keepGlobalPrefix) { + this.keepGlobalPrefix = keepGlobalPrefix; + } + + public void setExcludeProperty(String[] excludeProperty) { + this.excludeProperty = excludeProperty; + } + + public void setAutoResultMap(boolean autoResultMap) { + this.autoResultMap = autoResultMap; + } + + public void setAnnotationType(Class annotationType) { + this.annotationType = annotationType; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/ConfigurationCustomizer.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/ConfigurationCustomizer.java new file mode 100644 index 00000000..1477dc4b --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/ConfigurationCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.zhengjie.mybatis.autoconfig; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import org.apache.ibatis.session.Configuration; + +/** + * Callback interface that can be customized a {@link MybatisConfiguration} object generated on auto-configuration. + * + * @author Kazuki Shimizu + * @author liaojinlong + * @since 2020/6/29 18:08 + */ +@FunctionalInterface +public interface ConfigurationCustomizer { + + /** + * Customize the given a {@link MybatisConfiguration} object. + * + * @param configuration the configuration object to customize + */ + void customize(Configuration configuration); +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusAutoConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusAutoConfiguration.java new file mode 100644 index 00000000..07acb7e7 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusAutoConfiguration.java @@ -0,0 +1,321 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.zhengjie.mybatis.autoconfig; + + +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.injector.ISqlInjector; +import me.zhengjie.mybatis.core.MybatisConfiguration; +import me.zhengjie.mybatis.core.MybatisSqlSessionFactoryBean; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.mapping.DatabaseIdProvider; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.type.TypeHandler; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.mybatis.spring.mapper.MapperScannerConfigurer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a + * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}. + *

    + * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a + * configuration file is specified as a property, those will be considered, + * otherwise this auto-configuration will attempt to register mappers based on + * the interface definitions in or under the root auto-configuration package. + *

    + *

    copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}

    + * + * @author Eddú Meléndez + * @author Josh Long + * @author Kazuki Shimizu + * @author Eduardo Macarrón + * @author liaojinlong + * @since 2020/6/29 18:10 + */ +@Configuration +@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) +@ConditionalOnSingleCandidate(DataSource.class) +@EnableConfigurationProperties(MybatisPlusProperties.class) +@AutoConfigureAfter({DataSourceAutoConfiguration.class,}) +public class MybatisPlusAutoConfiguration implements InitializingBean { + + private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class); + + private final MybatisPlusProperties properties; + + private final Interceptor[] interceptors; + + private final TypeHandler[] typeHandlers; + + private final LanguageDriver[] languageDrivers; + + private final ResourceLoader resourceLoader; + + private final DatabaseIdProvider databaseIdProvider; + + private final List configurationCustomizers; + + private final List mybatisPlusPropertiesCustomizers; + + private final ApplicationContext applicationContext; + + + public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, + ObjectProvider interceptorsProvider, + ObjectProvider typeHandlersProvider, + ObjectProvider languageDriversProvider, + ResourceLoader resourceLoader, + ObjectProvider databaseIdProvider, + ObjectProvider> configurationCustomizersProvider, + ObjectProvider> mybatisPlusPropertiesCustomizerProvider, + ApplicationContext applicationContext) { + this.properties = properties; + this.interceptors = interceptorsProvider.getIfAvailable(); + this.typeHandlers = typeHandlersProvider.getIfAvailable(); + this.languageDrivers = languageDriversProvider.getIfAvailable(); + this.resourceLoader = resourceLoader; + this.databaseIdProvider = databaseIdProvider.getIfAvailable(); + this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); + this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable(); + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() { + if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) { + mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties)); + } + checkConfigFileExists(); + } + + private void checkConfigFileExists() { + if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { + Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); + Assert.state(resource.exists(), + "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); + } + } + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Bean + @ConditionalOnMissingBean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean + MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); + factory.setDataSource(dataSource); + factory.setVfs(SpringBootVFS.class); + if (StringUtils.hasText(this.properties.getConfigLocation())) { + factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); + } + applyConfiguration(factory); + if (this.properties.getConfigurationProperties() != null) { + factory.setConfigurationProperties(this.properties.getConfigurationProperties()); + } + if (!ObjectUtils.isEmpty(this.interceptors)) { + factory.setPlugins(this.interceptors); + } + if (this.databaseIdProvider != null) { + factory.setDatabaseIdProvider(this.databaseIdProvider); + } + if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { + factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); + } + if (this.properties.getTypeAliasesSuperType() != null) { + factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); + } + if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { + factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); + } + if (!ObjectUtils.isEmpty(this.typeHandlers)) { + factory.setTypeHandlers(this.typeHandlers); + } + Resource[] mapperLocations = this.properties.resolveMapperLocations(); + if (!ObjectUtils.isEmpty(mapperLocations)) { + factory.setMapperLocations(mapperLocations); + } + // TODO 修改源码支持定义 TransactionFactory + this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory); + + // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配) + Class defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); + if (!ObjectUtils.isEmpty(this.languageDrivers)) { + factory.setScriptingLanguageDrivers(this.languageDrivers); + } + Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver); + + // TODO 自定义枚举包 + if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { + factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); + } + // TODO 此处必为非 NULL + GlobalConfig globalConfig = this.properties.getGlobalConfig(); + // TODO 注入填充器 + this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler); + // TODO 注入主键生成器 + this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i)); + // TODO 注入sql注入器 + this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector); + // TODO 注入ID生成器 + this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator); + // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean + factory.setGlobalConfig(globalConfig); + return factory.getObject(); + } + + /** + * 检查spring容器里是否有对应的bean,有则进行消费 + * + * @param clazz class + * @param consumer 消费 + * @param 泛型 + */ + private void getBeanThen(Class clazz, Consumer consumer) { + if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) { + consumer.accept(this.applicationContext.getBean(clazz)); + } + } + + // TODO 入参使用 MybatisSqlSessionFactoryBean + private void applyConfiguration(MybatisSqlSessionFactoryBean factory) { + // TODO 使用 MybatisConfiguration + MybatisConfiguration configuration = this.properties.getConfiguration(); + if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { + configuration = new MybatisConfiguration(); + } + if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { + for (ConfigurationCustomizer customizer : this.configurationCustomizers) { + customizer.customize(configuration); + } + } + factory.setConfiguration(configuration); + } + + @Bean + @ConditionalOnMissingBean + public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { + ExecutorType executorType = this.properties.getExecutorType(); + if (executorType != null) { + return new SqlSessionTemplate(sqlSessionFactory, executorType); + } else { + return new SqlSessionTemplate(sqlSessionFactory); + } + } + + + /** + * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use + * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, + * similar to using Spring Data JPA repositories. + */ + public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { + + private BeanFactory beanFactory; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + + if (!AutoConfigurationPackages.has(this.beanFactory)) { + logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); + return; + } + + logger.debug("Searching for mappers annotated with @Mapper"); + + List packages = AutoConfigurationPackages.get(this.beanFactory); + if (logger.isDebugEnabled()) { + packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); + } + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); + builder.addPropertyValue("processPropertyPlaceHolders", true); + builder.addPropertyValue("annotationClass", Mapper.class); + builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); + BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); + Stream.of(beanWrapper.getPropertyDescriptors()) + // Need to mybatis-spring 2.0.2+ + .filter(x -> x.getName().equals("lazyInitialization")).findAny() + .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); + registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + } + + /** + * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan + * mappers based on the same component-scanning path as Spring Boot itself. + */ + @Configuration + @Import(AutoConfiguredMapperScannerRegistrar.class) + @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) + public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { + + @Override + public void afterPropertiesSet() { + logger.debug( + "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusProperties.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusProperties.java new file mode 100644 index 00000000..4e0ec6dc --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusProperties.java @@ -0,0 +1,135 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.autoconfig; + + +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; +import lombok.Data; +import lombok.experimental.Accessors; +import me.zhengjie.mybatis.core.MybatisConfiguration; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.ExecutorType; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; +import java.util.stream.Stream; + +/** + * Configuration properties for MyBatis. + * + * @author Eddú Meléndez + * @author Kazuki Shimizu + * @author liaojinlong + * @since 2020/6/29 18:11 + */ +@Data +@Accessors(chain = true) +@ConfigurationProperties(prefix = Constants.MYBATIS_PLUS) +public class MybatisPlusProperties { + + private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + + /** + * Location of MyBatis xml config file. + */ + private String configLocation; + + /** + * Locations of MyBatis mapper files. + * + * @since 3.1.2 add default value + */ + private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"}; + + /** + * Packages to search type aliases. (Package delimiters are ",; \t\n") + */ + private String typeAliasesPackage; + + /** + * The super class for filtering type alias. + * If this not specifies, the MyBatis deal as type alias all classes that searched from typeAliasesPackage. + */ + private Class typeAliasesSuperType; + + /** + * Packages to search for type handlers. (Package delimiters are ",; \t\n") + */ + private String typeHandlersPackage; + + /** + * Indicates whether perform presence check of the MyBatis xml config file. + */ + private boolean checkConfigLocation = false; + + /** + * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}. + */ + private ExecutorType executorType; + + /** + * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+) + *

    + * 如果设置了这个,你会至少失去几乎所有 mp 提供的功能 + */ + private Class defaultScriptingLanguageDriver; + + /** + * Externalized properties for MyBatis configuration. + */ + private Properties configurationProperties; + + /** + * A Configuration object for customize default settings. If {@link #configLocation} + * is specified, this property is not used. + * TODO 使用 MybatisConfiguration + */ + @NestedConfigurationProperty + private MybatisConfiguration configuration; + + /** + * TODO 枚举包扫描 + */ + private String typeEnumsPackage; + + /** + * TODO 全局配置 + */ + @NestedConfigurationProperty + private GlobalConfig globalConfig = GlobalConfigUtils.defaults(); + + + public Resource[] resolveMapperLocations() { + return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0])) + .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new); + } + + private Resource[] getResources(String location) { + try { + return resourceResolver.getResources(location); + } catch (IOException e) { + return new Resource[0]; + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusPropertiesCustomizer.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusPropertiesCustomizer.java new file mode 100644 index 00000000..f3d07ab4 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/MybatisPlusPropertiesCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.autoconfig; + +/** + * Callback interface that can be customized a {@link MybatisPlusProperties} object generated on auto-configuration. + * + *

    慎用

    + * + * @author miemie + * @author liaojinlong + * @since 2020/6/29 18:11 + */ +@FunctionalInterface +public interface MybatisPlusPropertiesCustomizer { + + /** + * Customize the given a {@link MybatisPlusProperties} object. + * + * @param properties the MybatisPlusProperties object to customize + */ + void customize(MybatisPlusProperties properties); +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SafetyEncryptProcessor.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SafetyEncryptProcessor.java new file mode 100644 index 00000000..d7e3cae8 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SafetyEncryptProcessor.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.autoconfig; + +import com.baomidou.mybatisplus.core.toolkit.AES; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.SimpleCommandLinePropertySource; + +import java.util.HashMap; + +/** + * 安全加密处理器 + * + * @author hubin + * @author liaojinlong + * @since 2020/6/29 18:12 + */ +public class SafetyEncryptProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + /** + * 命令行中获取密钥 + */ + String mpwKey = null; + for (PropertySource ps : environment.getPropertySources()) { + if (ps instanceof SimpleCommandLinePropertySource) { + SimpleCommandLinePropertySource source = (SimpleCommandLinePropertySource) ps; + mpwKey = source.getProperty("mpw.key"); + break; + } + } + /** + * 处理加密内容 + */ + if (StringUtils.isNotBlank(mpwKey)) { + HashMap map = new HashMap<>(); + for (PropertySource ps : environment.getPropertySources()) { + if (ps instanceof OriginTrackedMapPropertySource) { + OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource) ps; + for (String name : source.getPropertyNames()) { + Object value = source.getProperty(name); + if (value instanceof String) { + String str = (String) value; + if (str.startsWith("mpw:")) { + map.put(name, AES.decrypt(str.substring(4), mpwKey)); + } + } + } + } + } + // 将解密的数据放入环境变量,并处于第一优先级上 + if (CollectionUtils.isNotEmpty(map)) { + environment.getPropertySources().addFirst(new MapPropertySource("custom-encrypt", map)); + } + } + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SpringBootVFS.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SpringBootVFS.java new file mode 100644 index 00000000..cd9de5d8 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/autoconfig/SpringBootVFS.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.autoconfig; + + +import org.apache.ibatis.io.VFS; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Hans Westerbeek + * @author Eddú Meléndez + * @author Kazuki Shimizu + */ +public class SpringBootVFS extends VFS { + + private final ResourcePatternResolver resourceResolver; + + public SpringBootVFS() { + this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); + } + + @Override + public boolean isValid() { + return true; + } + + private static String preserveSubpackageName(final String baseUrlString, final Resource resource, + final String rootPath) { + try { + return rootPath + (rootPath.endsWith("/") ? "" : "/") + + resource.getURL().toString().substring(baseUrlString.length()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + protected List list(URL url, String path) throws IOException { + String urlString = url.toString(); + String baseUrlString = urlString.endsWith("/") ? urlString : urlString.concat("/"); + Resource[] resources = resourceResolver.getResources(baseUrlString + "**/*.class"); + return Stream.of(resources).map(resource -> preserveSubpackageName(baseUrlString, resource, path)) + .collect(Collectors.toList()); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/GlobalConfigUtils.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/GlobalConfigUtils.java new file mode 100644 index 00000000..8d488098 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/GlobalConfigUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.core.MybatisConfiguration; + +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; +import com.baomidou.mybatisplus.core.injector.ISqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.ClassUtils; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; + +import java.util.Optional; +import java.util.Set; + +/** + * Mybatis全局缓存工具类 + * + * @author Caratacus + * @author liaojinlong + * @since 2020/6/29 18:12 + */ +public class GlobalConfigUtils { + + /** + * 获取当前的SqlSessionFactory + * + * @param clazz 实体类 + */ + public static SqlSessionFactory currentSessionFactory(Class clazz) { + TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz); + Assert.notNull(tableInfo, ClassUtils.getUserClass(clazz).getName() + " Not Found TableInfoCache."); + return getGlobalConfig(tableInfo.getConfiguration()).getSqlSessionFactory(); + } + + /** + * 获取默认 MybatisGlobalConfig + *

    FIXME 这可能是一个伪装成单例模式的原型模式,暂时不确定

    + */ + public static GlobalConfig defaults() { + return new GlobalConfig().setDbConfig(new GlobalConfig.DbConfig()); + } + + /** + * 获取MybatisGlobalConfig (统一所有入口) + * + * @param configuration Mybatis 容器配置对象 + */ + public static GlobalConfig getGlobalConfig(Configuration configuration) { + Assert.notNull(configuration, "Error: You need Initialize MybatisConfiguration !"); + return ((MybatisConfiguration) configuration).getGlobalConfig(); + } + + public static IKeyGenerator getKeyGenerator(Configuration configuration) { + return getGlobalConfig(configuration).getDbConfig().getKeyGenerator(); + } + + public static IdType getIdType(Configuration configuration) { + return getGlobalConfig(configuration).getDbConfig().getIdType(); + } + + public static ISqlInjector getSqlInjector(Configuration configuration) { + return getGlobalConfig(configuration).getSqlInjector(); + } + + public static Optional getMetaObjectHandler(Configuration configuration) { + return Optional.ofNullable(getGlobalConfig(configuration).getMetaObjectHandler()); + } + + public static Class getSuperMapperClass(Configuration configuration) { + return getGlobalConfig(configuration).getSuperMapperClass(); + } + + public static boolean isSupperMapperChildren(Configuration configuration, Class mapperClass) { + return getSuperMapperClass(configuration).isAssignableFrom(mapperClass); + } + + public static Set getMapperRegistryCache(Configuration configuration) { + return getGlobalConfig(configuration).getMapperRegistryCache(); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisConfiguration.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisConfiguration.java new file mode 100644 index 00000000..1fbf971b --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisConfiguration.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2011-2020, baomidou (jobob@qq.com). + *

    + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

    + * https://www.apache.org/licenses/LICENSE-2.0 + *

    + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver; + +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.executor.MybatisBatchExecutor; +import com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor; +import com.baomidou.mybatisplus.core.executor.MybatisReuseExecutor; +import com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor; +import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; +import lombok.Getter; +import lombok.Setter; +import org.apache.ibatis.binding.MapperRegistry; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.transaction.Transaction; + +/** + * replace default Configuration class + *

    Caratacus 2016/9/25 replace mapperRegistry

    + * 适配 + * + * @author liaojinlong + * @since 2020/6/29 13:54 + */ +public class MybatisConfiguration extends com.baomidou.mybatisplus.core.MybatisConfiguration { + private static final Log logger = LogFactory.getLog(MybatisConfiguration.class); + /** + * Mapper 注册 + */ + protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this); + + public MybatisConfiguration(Environment environment) { + this(); + this.environment = environment; + } + + @Setter + @Getter + private GlobalConfig globalConfig = GlobalConfigUtils.defaults(); + + /** + * 初始化调用 + */ + public MybatisConfiguration() { + super(); + this.mapUnderscoreToCamelCase = true; + languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class); + } + + /** + * MybatisPlus 加载 SQL 顺序: + *

    1、加载XML中的SQL

    + *

    2、加载sqlProvider中的SQL

    + *

    3、xmlSql 与 sqlProvider不能包含相同的SQL

    + *

    调整后的SQL优先级:xmlSql > sqlProvider > curdSql

    + */ + @Override + public void addMappedStatement(MappedStatement ms) { + logger.debug("addMappedStatement: " + ms.getId()); + if (mappedStatements.containsKey(ms.getId())) { + /* + * 说明已加载了xml中的节点; 忽略mapper中的SqlProvider数据 + */ + logger.error("mapper[" + ms.getId() + "] is ignored, because it exists, maybe from xml file"); + return; + } + super.addMappedStatement(ms); + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public MapperRegistry getMapperRegistry() { + return mybatisMapperRegistry; + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public void addMapper(Class type) { + mybatisMapperRegistry.addMapper(type); + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public void addMappers(String packageName, Class superType) { + mybatisMapperRegistry.addMappers(packageName, superType); + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public void addMappers(String packageName) { + mybatisMapperRegistry.addMappers(packageName); + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public T getMapper(Class type, SqlSession sqlSession) { + return mybatisMapperRegistry.getMapper(type, sqlSession); + } + + /** + * 使用自己的 MybatisMapperRegistry + */ + @Override + public boolean hasMapper(Class type) { + return mybatisMapperRegistry.hasMapper(type); + } + + /** + * 指定动态SQL生成的默认语言 + * + * @param driver LanguageDriver + */ + @Override + public void setDefaultScriptingLanguage(Class driver) { + if (driver == null) { + //todo 替换动态SQL生成的默认语言为自己的。 + driver = MybatisXMLLanguageDriver.class; + } + getLanguageRegistry().setDefaultDriverClass(driver); + } + + @Override + public Executor newExecutor(Transaction transaction, ExecutorType executorType) { + executorType = executorType == null ? defaultExecutorType : executorType; + executorType = executorType == null ? ExecutorType.SIMPLE : executorType; + Executor executor; + if (ExecutorType.BATCH == executorType) { + executor = new MybatisBatchExecutor(this, transaction); + } else if (ExecutorType.REUSE == executorType) { + executor = new MybatisReuseExecutor(this, transaction); + } else { + executor = new MybatisSimpleExecutor(this, transaction); + } + if (cacheEnabled) { + executor = new MybatisCachingExecutor(executor); + } + executor = (Executor) interceptorChain.pluginAll(executor); + return executor; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperAnnotationBuilder.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperAnnotationBuilder.java new file mode 100644 index 00000000..e07247a9 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperAnnotationBuilder.java @@ -0,0 +1,653 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.core.MybatisMethodResolver; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.parser.SqlParserHelper; +import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; +import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.ResultMap; +import org.apache.ibatis.annotations.Options.FlushCachePolicy; +import org.apache.ibatis.binding.BindingException; +import org.apache.ibatis.binding.MapperMethod; +import org.apache.ibatis.builder.BuilderException; +import org.apache.ibatis.builder.CacheRefResolver; +import org.apache.ibatis.builder.IncompleteElementException; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder; +import org.apache.ibatis.builder.annotation.MethodResolver; +import org.apache.ibatis.builder.annotation.ProviderSqlSource; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.cursor.Cursor; +import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator; +import org.apache.ibatis.executor.keygen.KeyGenerator; +import org.apache.ibatis.executor.keygen.NoKeyGenerator; +import org.apache.ibatis.executor.keygen.SelectKeyGenerator; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.mapping.*; +import org.apache.ibatis.parsing.PropertyParser; +import org.apache.ibatis.reflection.TypeParameterResolver; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.UnknownTypeHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; + + +/** + * 继承 + *

    + * 只重写了 {@link MapperAnnotationBuilder#parse} 和 #getReturnType + * 没有XML配置文件注入基础CRUD方法 + *

    + * + * @author Caratacus + * @author liaojinlong + * @since 2020/6/29 18:13 + */ +public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder { + + private static final Set> SQL_ANNOTATION_TYPES = new HashSet<>(); + private static final Set> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>(); + + static { + SQL_ANNOTATION_TYPES.add(Select.class); + SQL_ANNOTATION_TYPES.add(Insert.class); + SQL_ANNOTATION_TYPES.add(Update.class); + SQL_ANNOTATION_TYPES.add(Delete.class); + + SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class); + SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class); + SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class); + SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class); + } + + private final MybatisConfiguration configuration; + private final MapperBuilderAssistant assistant; + private final Class type; + + public MybatisMapperAnnotationBuilder(MybatisConfiguration configuration, Class type) { + super(configuration, type); + String resource = type.getName().replace('.', '/') + ".java (best guess)"; + this.assistant = new MapperBuilderAssistant(configuration, resource); + this.configuration = configuration; + this.type = type; + } + + @Override + public void parse() { + String resource = type.toString(); + if (!configuration.isResourceLoaded(resource)) { + loadXmlResource(); + configuration.addLoadedResource(resource); + final String typeName = type.getName(); + assistant.setCurrentNamespace(typeName); + parseCache(); + parseCacheRef(); + SqlParserHelper.initSqlParserInfoCache(type); + Method[] methods = type.getMethods(); + for (Method method : methods) { + try { + // issue #237 + if (!method.isBridge()) { + parseStatement(method); + SqlParserHelper.initSqlParserInfoCache(typeName, method); + } + } catch (IncompleteElementException e) { + // TODO 使用 MybatisMethodResolver 而不是 MethodResolver + configuration.addIncompleteMethod(new MybatisMethodResolver(this, method)); + } + } + // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql + if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { + GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); + } + } + parsePendingMethods(); + } + + private void parsePendingMethods() { + Collection incompleteMethods = configuration.getIncompleteMethods(); + synchronized (incompleteMethods) { + Iterator iter = incompleteMethods.iterator(); + while (iter.hasNext()) { + try { + iter.next().resolve(); + iter.remove(); + } catch (IncompleteElementException e) { + // This method is still missing a resource + } + } + } + } + + private void loadXmlResource() { + // Spring may not know the real resource name so we check a flag + // to prevent loading again a resource twice + // this flag is set at XMLMapperBuilder#bindMapperForNamespace + if (!configuration.isResourceLoaded("namespace:" + type.getName())) { + String xmlResource = type.getName().replace('.', '/') + ".xml"; + // #1347 + InputStream inputStream = type.getResourceAsStream("/" + xmlResource); + if (inputStream == null) { + // Search XML mapper that is not in the module but in the classpath. + try { + inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); + } catch (IOException e2) { + // ignore, resource is not required + } + } + if (inputStream != null) { + XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); + xmlParser.parse(); + } + } + } + + private void parseCache() { + CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); + if (cacheDomain != null) { + Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size(); + Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval(); + Properties props = convertToProperties(cacheDomain.properties()); + assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props); + } + } + + private Properties convertToProperties(Property[] properties) { + if (properties.length == 0) { + return null; + } + Properties props = new Properties(); + for (Property property : properties) { + props.setProperty(property.name(), + PropertyParser.parse(property.value(), configuration.getVariables())); + } + return props; + } + + private void parseCacheRef() { + CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class); + if (cacheDomainRef != null) { + Class refType = cacheDomainRef.value(); + String refName = cacheDomainRef.name(); + if (refType == void.class && refName.isEmpty()) { + throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef"); + } + if (refType != void.class && !refName.isEmpty()) { + throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef"); + } + String namespace = (refType != void.class) ? refType.getName() : refName; + try { + assistant.useCacheRef(namespace); + } catch (IncompleteElementException e) { + configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace)); + } + } + } + + private String parseResultMap(Method method) { + Class returnType = getReturnType(method); + Arg[] args = method.getAnnotationsByType(Arg.class); + Result[] results = method.getAnnotationsByType(Result.class); + TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class); + String resultMapId = generateResultMapName(method); + applyResultMap(resultMapId, returnType, args, results, typeDiscriminator); + return resultMapId; + } + + private String generateResultMapName(Method method) { + Results results = method.getAnnotation(Results.class); + if (results != null && !results.id().isEmpty()) { + return type.getName() + "." + results.id(); + } + StringBuilder suffix = new StringBuilder(); + for (Class c : method.getParameterTypes()) { + suffix.append("-"); + suffix.append(c.getSimpleName()); + } + if (suffix.length() < 1) { + suffix.append("-void"); + } + return type.getName() + "." + method.getName() + suffix; + } + + private void applyResultMap(String resultMapId, Class returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) { + List resultMappings = new ArrayList<>(); + applyConstructorArgs(args, returnType, resultMappings); + applyResults(results, returnType, resultMappings); + Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator); + // TODO add AutoMappingBehaviour + assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null); + createDiscriminatorResultMaps(resultMapId, returnType, discriminator); + } + + private void createDiscriminatorResultMaps(String resultMapId, Class resultType, TypeDiscriminator discriminator) { + if (discriminator != null) { + for (Case c : discriminator.cases()) { + String caseResultMapId = resultMapId + "-" + c.value(); + List resultMappings = new ArrayList<>(); + // issue #136 + applyConstructorArgs(c.constructArgs(), resultType, resultMappings); + applyResults(c.results(), resultType, resultMappings); + // TODO add AutoMappingBehaviour + assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null); + } + } + } + + private Discriminator applyDiscriminator(String resultMapId, Class resultType, TypeDiscriminator discriminator) { + if (discriminator != null) { + String column = discriminator.column(); + Class javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType(); + JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType(); + @SuppressWarnings("unchecked") + Class> typeHandler = (Class>) + (discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler()); + Case[] cases = discriminator.cases(); + Map discriminatorMap = new HashMap<>(); + for (Case c : cases) { + String value = c.value(); + String caseResultMapId = resultMapId + "-" + value; + discriminatorMap.put(value, caseResultMapId); + } + return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap); + } + return null; + } + + void parseStatement(Method method) { + Class parameterTypeClass = getParameterType(method); + LanguageDriver languageDriver = getLanguageDriver(method); + SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); + if (sqlSource != null) { + Options options = method.getAnnotation(Options.class); + final String mappedStatementId = type.getName() + "." + method.getName(); + Integer fetchSize = null; + Integer timeout = null; + StatementType statementType = StatementType.PREPARED; + ResultSetType resultSetType = configuration.getDefaultResultSetType(); + SqlCommandType sqlCommandType = getSqlCommandType(method); + boolean isSelect = sqlCommandType == SqlCommandType.SELECT; + boolean flushCache = !isSelect; + boolean useCache = isSelect; + + KeyGenerator keyGenerator; + String keyProperty = null; + String keyColumn = null; + if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { + // first check for SelectKey annotation - that overrides everything else + SelectKey selectKey = method.getAnnotation(SelectKey.class); + if (selectKey != null) { + keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); + keyProperty = selectKey.keyProperty(); + } else if (options == null) { + keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; + } else { + keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; + keyProperty = options.keyProperty(); + keyColumn = options.keyColumn(); + } + } else { + keyGenerator = NoKeyGenerator.INSTANCE; + } + + if (options != null) { + if (FlushCachePolicy.TRUE.equals(options.flushCache())) { + flushCache = true; + } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { + flushCache = false; + } + useCache = options.useCache(); + fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348 + timeout = options.timeout() > -1 ? options.timeout() : null; + statementType = options.statementType(); + if (options.resultSetType() != ResultSetType.DEFAULT) { + resultSetType = options.resultSetType(); + } + } + + String resultMapId = null; + ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); + if (resultMapAnnotation != null) { + resultMapId = String.join(",", resultMapAnnotation.value()); + } else if (isSelect) { + resultMapId = parseResultMap(method); + } + + assistant.addMappedStatement( + mappedStatementId, + sqlSource, + statementType, + sqlCommandType, + fetchSize, + timeout, + // ParameterMapID + null, + parameterTypeClass, + resultMapId, + getReturnType(method), + resultSetType, + flushCache, + useCache, + // TODO gcode issue #577 + false, + keyGenerator, + keyProperty, + keyColumn, + // DatabaseID + null, + languageDriver, + // ResultSets + options != null ? nullOrEmpty(options.resultSets()) : null); + } + } + + private LanguageDriver getLanguageDriver(Method method) { + Lang lang = method.getAnnotation(Lang.class); + Class langClass = null; + if (lang != null) { + langClass = lang.value(); + } + return configuration.getLanguageDriver(langClass); + } + + private Class getParameterType(Method method) { + Class parameterType = null; + Class[] parameterTypes = method.getParameterTypes(); + for (Class currentParameterType : parameterTypes) { + if (!RowBounds.class.isAssignableFrom(currentParameterType) && !ResultHandler.class.isAssignableFrom(currentParameterType)) { + if (parameterType == null) { + parameterType = currentParameterType; + } else { + // issue #135 + parameterType = MapperMethod.ParamMap.class; + } + } + } + return parameterType; + } + + private Class getReturnType(Method method) { + Class returnType = method.getReturnType(); + Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type); + if (resolvedReturnType instanceof Class) { + returnType = (Class) resolvedReturnType; + if (returnType.isArray()) { + returnType = returnType.getComponentType(); + } + // gcode issue #508 + if (void.class.equals(returnType)) { + ResultType rt = method.getAnnotation(ResultType.class); + if (rt != null) { + returnType = rt.value(); + } + } + } else if (resolvedReturnType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType; + Class rawType = (Class) parameterizedType.getRawType(); + if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments != null && actualTypeArguments.length == 1) { + Type returnTypeParameter = actualTypeArguments[0]; + if (returnTypeParameter instanceof Class) { + returnType = (Class) returnTypeParameter; + } else if (returnTypeParameter instanceof ParameterizedType) { + // (gcode issue #443) actual type can be a also a parameterized type + returnType = (Class) ((ParameterizedType) returnTypeParameter).getRawType(); + } else if (returnTypeParameter instanceof GenericArrayType) { + Class componentType = (Class) ((GenericArrayType) returnTypeParameter).getGenericComponentType(); + // (gcode issue #525) support List + returnType = Array.newInstance(componentType, 0).getClass(); + } + } + } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) { + // (gcode issue 504) Do not look into Maps if there is not MapKey annotation + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments != null && actualTypeArguments.length == 2) { + Type returnTypeParameter = actualTypeArguments[1]; + if (returnTypeParameter instanceof Class) { + returnType = (Class) returnTypeParameter; + } else if (returnTypeParameter instanceof ParameterizedType) { + // (gcode issue 443) actual type can be a also a parameterized type + returnType = (Class) ((ParameterizedType) returnTypeParameter).getRawType(); + } + } + } else if (Optional.class.equals(rawType)) { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Type returnTypeParameter = actualTypeArguments[0]; + if (returnTypeParameter instanceof Class) { + returnType = (Class) returnTypeParameter; + } + } + // TODO 下面是支援 IPage 及其子类作为返回值的 + else if (IPage.class.isAssignableFrom(rawType)) { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Type returnTypeParameter = actualTypeArguments[0]; + if (returnTypeParameter instanceof Class) { + returnType = (Class) returnTypeParameter; + } else if (returnTypeParameter instanceof ParameterizedType) { + returnType = (Class) ((ParameterizedType) returnTypeParameter).getRawType(); + } + } + // TODO 上面是支援 IPage 及其子类作为返回值的 + } + + return returnType; + } + + private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterType, LanguageDriver languageDriver) { + try { + Class sqlAnnotationType = getSqlAnnotationType(method); + Class sqlProviderAnnotationType = getSqlProviderAnnotationType(method); + if (sqlAnnotationType != null) { + if (sqlProviderAnnotationType != null) { + throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); + } + Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); + final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); + return buildSqlSourceFromStrings(strings, parameterType, languageDriver); + } else if (sqlProviderAnnotationType != null) { + Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); + return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); + } + return null; + } catch (Exception e) { + throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); + } + } + + private SqlSource buildSqlSourceFromStrings(String[] strings, Class parameterTypeClass, LanguageDriver languageDriver) { + final StringBuilder sql = new StringBuilder(); + for (String fragment : strings) { + sql.append(fragment); + sql.append(" "); + } + return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass); + } + + private SqlCommandType getSqlCommandType(Method method) { + Class type = getSqlAnnotationType(method); + + if (type == null) { + type = getSqlProviderAnnotationType(method); + + if (type == null) { + return SqlCommandType.UNKNOWN; + } + + if (type == SelectProvider.class) { + type = Select.class; + } else if (type == InsertProvider.class) { + type = Insert.class; + } else if (type == UpdateProvider.class) { + type = Update.class; + } else if (type == DeleteProvider.class) { + type = Delete.class; + } + } + + return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH)); + } + + private Class getSqlAnnotationType(Method method) { + return chooseAnnotationType(method, SQL_ANNOTATION_TYPES); + } + + private Class getSqlProviderAnnotationType(Method method) { + return chooseAnnotationType(method, SQL_PROVIDER_ANNOTATION_TYPES); + } + + private Class chooseAnnotationType(Method method, Set> types) { + for (Class type : types) { + Annotation annotation = method.getAnnotation(type); + if (annotation != null) { + return type; + } + } + return null; + } + + private void applyResults(Result[] results, Class resultType, List resultMappings) { + for (Result result : results) { + List flags = new ArrayList<>(); + if (result.id()) { + flags.add(ResultFlag.ID); + } + @SuppressWarnings("unchecked") + Class> typeHandler = (Class>) + ((result.typeHandler() == UnknownTypeHandler.class) ? null : result.typeHandler()); + ResultMapping resultMapping = assistant.buildResultMapping( + resultType, + nullOrEmpty(result.property()), + nullOrEmpty(result.column()), + result.javaType() == void.class ? null : result.javaType(), + result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(), + hasNestedSelect(result) ? nestedSelectId(result) : null, + null, + null, + null, + typeHandler, + flags, + null, + null, + isLazy(result)); + resultMappings.add(resultMapping); + } + } + + private String nestedSelectId(Result result) { + String nestedSelect = result.one().select(); + if (nestedSelect.length() < 1) { + nestedSelect = result.many().select(); + } + if (!nestedSelect.contains(".")) { + nestedSelect = type.getName() + "." + nestedSelect; + } + return nestedSelect; + } + + private boolean isLazy(Result result) { + boolean isLazy = configuration.isLazyLoadingEnabled(); + if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) { + isLazy = result.one().fetchType() == FetchType.LAZY; + } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) { + isLazy = result.many().fetchType() == FetchType.LAZY; + } + return isLazy; + } + + private boolean hasNestedSelect(Result result) { + if (result.one().select().length() > 0 && result.many().select().length() > 0) { + throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result"); + } + return result.one().select().length() > 0 || result.many().select().length() > 0; + } + + private void applyConstructorArgs(Arg[] args, Class resultType, List resultMappings) { + for (Arg arg : args) { + List flags = new ArrayList<>(); + flags.add(ResultFlag.CONSTRUCTOR); + if (arg.id()) { + flags.add(ResultFlag.ID); + } + @SuppressWarnings("unchecked") + Class> typeHandler = (Class>) + (arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler()); + ResultMapping resultMapping = assistant.buildResultMapping( + resultType, + nullOrEmpty(arg.name()), + nullOrEmpty(arg.column()), + arg.javaType() == void.class ? null : arg.javaType(), + arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), + nullOrEmpty(arg.select()), + nullOrEmpty(arg.resultMap()), + null, + nullOrEmpty(arg.columnPrefix()), + typeHandler, + flags, + null, + null, + false); + resultMappings.add(resultMapping); + } + } + + private String nullOrEmpty(String value) { + return value == null || value.trim().length() == 0 ? null : value; + } + + private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class parameterTypeClass, LanguageDriver languageDriver) { + String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX; + Class resultTypeClass = selectKeyAnnotation.resultType(); + StatementType statementType = selectKeyAnnotation.statementType(); + String keyProperty = selectKeyAnnotation.keyProperty(); + String keyColumn = selectKeyAnnotation.keyColumn(); + boolean executeBefore = selectKeyAnnotation.before(); + + // defaults + boolean useCache = false; + KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; + Integer fetchSize = null; + Integer timeout = null; + boolean flushCache = false; + String parameterMap = null; + String resultMap = null; + ResultSetType resultSetTypeEnum = null; + + SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver); + SqlCommandType sqlCommandType = SqlCommandType.SELECT; + + assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, + flushCache, useCache, false, + keyGenerator, keyProperty, keyColumn, null, languageDriver, null); + + id = assistant.applyCurrentNamespace(id, false); + + MappedStatement keyStatement = configuration.getMappedStatement(id, false); + SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore); + configuration.addKeyGenerator(id, answer); + return answer; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperRegistry.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperRegistry.java new file mode 100644 index 00000000..07f685cd --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisMapperRegistry.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.core.override.MybatisMapperProxyFactory; +import org.apache.ibatis.binding.BindingException; +import org.apache.ibatis.binding.MapperRegistry; +import org.apache.ibatis.session.SqlSession; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * 继承至MapperRegistry + * + * @author Caratacus hubin + * @author liaojinlong + * @since 2020/6/29 18:13 + */ +public class MybatisMapperRegistry extends MapperRegistry { + + private final Map, MybatisMapperProxyFactory> knownMappers = new HashMap<>(); + private final MybatisConfiguration config; + + public MybatisMapperRegistry(MybatisConfiguration config) { + super(config); + this.config = config; + } + + @SuppressWarnings("unchecked") + @Override + public T getMapper(Class type, SqlSession sqlSession) { + // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory + final MybatisMapperProxyFactory mapperProxyFactory = (MybatisMapperProxyFactory) knownMappers.get(type); + if (mapperProxyFactory == null) { + throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."); + } + try { + return mapperProxyFactory.newInstance(sqlSession); + } catch (Exception e) { + throw new BindingException("Error getting mapper instance. Cause: " + e, e); + } + } + + @Override + public boolean hasMapper(Class type) { + return knownMappers.containsKey(type); + } + + @Override + public void addMapper(Class type) { + if (type.isInterface()) { + if (hasMapper(type)) { + // TODO 如果之前注入 直接返回 + return; + // TODO 这里就不抛异常了 +// throw new BindingException("Type " + type + " is already known to the MapperRegistry."); + } + boolean loadCompleted = false; + try { + // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory + knownMappers.put(type, new MybatisMapperProxyFactory<>(type)); + // It's important that the type is added before the parser is run + // otherwise the binding may automatically be attempted by the + // mapper parser. If the type is already known, it won't try. + // TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder + MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); + parser.parse(); + loadCompleted = true; + } finally { + if (!loadCompleted) { + knownMappers.remove(type); + } + } + } + } + + /** + * 使用自己的 knownMappers + */ + @Override + public Collection> getMappers() { + return Collections.unmodifiableCollection(knownMappers.keySet()); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBean.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBean.java new file mode 100644 index 00000000..d4415410 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBean.java @@ -0,0 +1,681 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.core.MybatisPlusVersion; +import com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder; + +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.handlers.MybatisEnumTypeHandler; +import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; +import lombok.Setter; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.cache.Cache; +import org.apache.ibatis.executor.ErrorContext; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.mapping.DatabaseIdProvider; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.reflection.factory.ObjectFactory; +import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.mybatis.logging.Logger; +import org.mybatis.logging.LoggerFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.transaction.SpringManagedTransactionFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.NestedIOException; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; +import org.springframework.util.ClassUtils; + +import javax.sql.DataSource; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Stream; + +import static org.springframework.util.Assert.notNull; +import static org.springframework.util.Assert.state; +import static org.springframework.util.ObjectUtils.isEmpty; +import static org.springframework.util.StringUtils.hasLength; +import static org.springframework.util.StringUtils.tokenizeToStringArray; + +/** + * 拷贝类 {@link SqlSessionFactoryBean} 修改方法 buildSqlSessionFactory() 加载自定义 + *

    MybatisXmlConfigBuilder

    + *

    移除 sqlSessionFactoryBuilder 属性,强制使用 `new MybatisSqlSessionFactoryBuilder()`

    + *

    移除 environment 属性,强制使用 `MybatisSqlSessionFactoryBean.class.getSimpleName()`

    + * + * @author hubin + * @author liaojinlong + * @since 2020/6/29 18:13 + */ +public class MybatisSqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(MybatisSqlSessionFactoryBean.class); + + private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver(); + private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory(); + + private Resource configLocation; + + // TODO 使用 MybatisConfiguration + private MybatisConfiguration configuration; + + private Resource[] mapperLocations; + + private DataSource dataSource; + + private TransactionFactory transactionFactory; + + private Properties configurationProperties; + + private SqlSessionFactory sqlSessionFactory; + + private boolean failFast; + + private Interceptor[] plugins; + + private TypeHandler[] typeHandlers; + + private String typeHandlersPackage; + + private Class[] typeAliases; + + private String typeAliasesPackage; + + private Class typeAliasesSuperType; + + private LanguageDriver[] scriptingLanguageDrivers; + + private Class defaultScriptingLanguageDriver; + + // issue #19. No default provider. + private DatabaseIdProvider databaseIdProvider; + + private Class vfs; + + private Cache cache; + + private ObjectFactory objectFactory; + + private ObjectWrapperFactory objectWrapperFactory; + + // TODO 自定义枚举包 + @Setter + private String typeEnumsPackage; + + // TODO 自定义全局配置 + @Setter + private GlobalConfig globalConfig; + + /** + * Sets the ObjectFactory. + * + * @param objectFactory a custom ObjectFactory + * @since 1.1.2 + */ + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + /** + * Sets the ObjectWrapperFactory. + * + * @param objectWrapperFactory a specified ObjectWrapperFactory + * @since 1.1.2 + */ + public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) { + this.objectWrapperFactory = objectWrapperFactory; + } + + /** + * Gets the DatabaseIdProvider + * + * @return a specified DatabaseIdProvider + * @since 1.1.0 + */ + public DatabaseIdProvider getDatabaseIdProvider() { + return databaseIdProvider; + } + + /** + * Sets the DatabaseIdProvider. As of version 1.2.2 this variable is not initialized by default. + * + * @param databaseIdProvider a DatabaseIdProvider + * @since 1.1.0 + */ + public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) { + this.databaseIdProvider = databaseIdProvider; + } + + /** + * Gets the VFS. + * + * @return a specified VFS + */ + public Class getVfs() { + return this.vfs; + } + + /** + * Sets the VFS. + * + * @param vfs a VFS + */ + public void setVfs(Class vfs) { + this.vfs = vfs; + } + + /** + * Gets the Cache. + * + * @return a specified Cache + */ + public Cache getCache() { + return this.cache; + } + + /** + * Sets the Cache. + * + * @param cache a Cache + */ + public void setCache(Cache cache) { + this.cache = cache; + } + + /** + * Mybatis plugin list. + * + * @param plugins list of plugins + * @since 1.0.1 + */ + public void setPlugins(Interceptor... plugins) { + this.plugins = plugins; + } + + /** + * Packages to search for type aliases. + * + *

    + * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}. + * + * @param typeAliasesPackage package to scan for domain objects + * @since 1.0.1 + */ + public void setTypeAliasesPackage(String typeAliasesPackage) { + this.typeAliasesPackage = typeAliasesPackage; + } + + /** + * Super class which domain objects have to extend to have a type alias created. No effect if there is no package to + * scan configured. + * + * @param typeAliasesSuperType super class for domain objects + * @since 1.1.2 + */ + public void setTypeAliasesSuperType(Class typeAliasesSuperType) { + this.typeAliasesSuperType = typeAliasesSuperType; + } + + /** + * Packages to search for type handlers. + * + *

    + * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}. + * + * @param typeHandlersPackage package to scan for type handlers + * @since 1.0.1 + */ + public void setTypeHandlersPackage(String typeHandlersPackage) { + this.typeHandlersPackage = typeHandlersPackage; + } + + /** + * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes} + * + * @param typeHandlers Type handler list + * @since 1.0.1 + */ + public void setTypeHandlers(TypeHandler... typeHandlers) { + this.typeHandlers = typeHandlers; + } + + /** + * List of type aliases to register. They can be annotated with {@code Alias} + * + * @param typeAliases Type aliases list + * @since 1.0.1 + */ + public void setTypeAliases(Class... typeAliases) { + this.typeAliases = typeAliases; + } + + /** + * If true, a final check is done on Configuration to assure that all mapped statements are fully loaded and there is + * no one still pending to resolve includes. Defaults to false. + * + * @param failFast enable failFast + * @since 1.0.1 + */ + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } + + /** + * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is + * "WEB-INF/mybatis-configuration.xml". + * + * @param configLocation a location the MyBatis config file + */ + public void setConfigLocation(Resource configLocation) { + this.configLocation = configLocation; + } + + /** + * Set a customized MyBatis configuration. + * TODO 这里的入参使用 MybatisConfiguration 而不是 Configuration + * + * @param configuration MyBatis configuration + * @since 1.3.0 + */ + public void setConfiguration(MybatisConfiguration configuration) { + this.configuration = configuration; + } + + public MybatisConfiguration getConfiguration() { + return this.configuration; + } + + /** + * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration + * at runtime. + *

    + * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. This property being + * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g. + * "classpath*:sqlmap/*-mapper.xml". + * + * @param mapperLocations location of MyBatis mapper files + */ + public void setMapperLocations(Resource... mapperLocations) { + this.mapperLocations = mapperLocations; + } + + /** + * Set optional properties to be passed into the SqlSession configuration, as alternative to a + * {@code <properties>} tag in the configuration xml file. This will be used to resolve placeholders in the + * config file. + * + * @param sqlSessionFactoryProperties optional properties for the SqlSessionFactory + */ + public void setConfigurationProperties(Properties sqlSessionFactoryProperties) { + this.configurationProperties = sqlSessionFactoryProperties; + } + + /** + * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should + * match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for + * both. + *

    + * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing + * this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. + *

    + * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a + * {@code TransactionAwareDataSourceProxy}. Only data access code may work with + * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target + * {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be + * unwrapped to extract its target {@code DataSource}. + * + * @param dataSource a JDBC {@code DataSource} + */ + public void setDataSource(DataSource dataSource) { + if (dataSource instanceof TransactionAwareDataSourceProxy) { + // If we got a TransactionAwareDataSourceProxy, we need to perform + // transactions for its underlying target DataSource, else data + // access code won't see properly exposed transactions (i.e. + // transactions for the target DataSource). + this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); + } else { + this.dataSource = dataSource; + } + } + + /** + * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory} + *

    + * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: + * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction, + * SqlSession operations will execute SQL statements non-transactionally. + * + * It is strongly recommended to use the default {@code TransactionFactory}. If not used, any + * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if + * a transaction is active. + * + * @param transactionFactory the MyBatis TransactionFactory + * @see SpringManagedTransactionFactory + */ + public void setTransactionFactory(TransactionFactory transactionFactory) { + this.transactionFactory = transactionFactory; + } + + /** + * Set scripting language drivers. + * + * @param scriptingLanguageDrivers scripting language drivers + * @since 2.0.2 + */ + public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) { + this.scriptingLanguageDrivers = scriptingLanguageDrivers; + } + + /** + * Set a default scripting language driver class. + * + * @param defaultScriptingLanguageDriver A default scripting language driver class + * @since 2.0.2 + */ + public void setDefaultScriptingLanguageDriver(Class defaultScriptingLanguageDriver) { + this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver; + } + + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() throws Exception { + notNull(dataSource, "Property 'dataSource' is required"); + state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), + "Property 'configuration' and 'configLocation' can not specified with together"); + + this.sqlSessionFactory = buildSqlSessionFactory(); + } + + /** + * Build a {@code SqlSessionFactory} instance. + *

    + * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a + * {@code SqlSessionFactory} instance based on an Reader. Since 1.3.0, it can be specified a + * {@link Configuration} instance directly(without config file). + *

    + * + * @return SqlSessionFactory + * @throws IOException if loading the config file failed + */ + protected SqlSessionFactory buildSqlSessionFactory() throws Exception { + + final com.baomidou.mybatisplus.core.MybatisConfiguration targetConfiguration; + + // TODO 使用 MybatisXmlConfigBuilder 而不是 XMLConfigBuilder + MybatisXMLConfigBuilder xmlConfigBuilder = null; + if (this.configuration != null) { + targetConfiguration = this.configuration; + if (targetConfiguration.getVariables() == null) { + targetConfiguration.setVariables(this.configurationProperties); + } else if (this.configurationProperties != null) { + targetConfiguration.getVariables().putAll(this.configurationProperties); + } + } else if (this.configLocation != null) { + // TODO 使用 MybatisXMLConfigBuilder + xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); + targetConfiguration = xmlConfigBuilder.getConfiguration(); + } else { + LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); + // TODO 使用 MybatisConfiguration + targetConfiguration = new MybatisConfiguration(); + Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); + } + + // TODO 无配置启动所必须的 + this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults); + this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new)); + + // TODO 初始化 id-work 以及 打印骚东西 + targetConfiguration.setGlobalConfig(this.globalConfig); + + // TODO 自定义枚举类扫描处理 + if (hasLength(this.typeEnumsPackage)) { + Set> classes; + if (typeEnumsPackage.contains(StringPool.STAR) && !typeEnumsPackage.contains(StringPool.COMMA) + && !typeEnumsPackage.contains(StringPool.SEMICOLON)) { + classes = scanClasses(typeEnumsPackage, null); + if (classes.isEmpty()) { + LOGGER.warn(() -> "Can't find class in '[" + typeEnumsPackage + "]' package. Please check your configuration."); + } + } else { + classes = new HashSet<>(); + String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage, + ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + Assert.notNull(typeEnumsPackageArray, "not find typeEnumsPackage:" + typeEnumsPackage); + Stream.of(typeEnumsPackageArray).forEach(typePackage -> { + try { + Set> scanTypePackage = scanClasses(typePackage, null); + if (scanTypePackage.isEmpty()) { + LOGGER.warn(() -> "Can't find class in '[" + typePackage + "]' package. Please check your configuration."); + } else { + classes.addAll(scanTypePackage); + } + } catch (IOException e) { + throw new MybatisPlusException("Cannot scan class in '[" + typePackage + "]' package", e); + } + }); + } + // 取得类型转换注册器 + TypeHandlerRegistry typeHandlerRegistry = targetConfiguration.getTypeHandlerRegistry(); + classes.stream() + .filter(Class::isEnum) + .filter(MybatisEnumTypeHandler::isMpEnums) + .forEach(cls -> typeHandlerRegistry.register(cls, MybatisEnumTypeHandler.class)); + } + + Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory); + Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); + Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); + + if (hasLength(this.typeAliasesPackage)) { + scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() + .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) + .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); + } + + if (!isEmpty(this.typeAliases)) { + Stream.of(this.typeAliases).forEach(typeAlias -> { + targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); + LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); + }); + } + + if (!isEmpty(this.plugins)) { + Stream.of(this.plugins).forEach(plugin -> { + targetConfiguration.addInterceptor(plugin); + LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); + }); + } + + if (hasLength(this.typeHandlersPackage)) { + scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) + .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) + .forEach(targetConfiguration.getTypeHandlerRegistry()::register); + } + + if (!isEmpty(this.typeHandlers)) { + Stream.of(this.typeHandlers).forEach(typeHandler -> { + targetConfiguration.getTypeHandlerRegistry().register(typeHandler); + LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); + }); + } + + if (!isEmpty(this.scriptingLanguageDrivers)) { + Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { + targetConfiguration.getLanguageRegistry().register(languageDriver); + LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'"); + }); + } + + Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage); + + if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls + try { + targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); + } catch (SQLException e) { + throw new NestedIOException("Failed getting a databaseId", e); + } + } + + Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); + + if (xmlConfigBuilder != null) { + try { + xmlConfigBuilder.parse(); + LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'"); + } catch (Exception ex) { + throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); + } finally { + ErrorContext.instance().reset(); + } + } + + targetConfiguration.setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(), + this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, + this.dataSource)); + + if (this.mapperLocations != null) { + if (this.mapperLocations.length == 0) { + LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); + } else { + for (Resource mapperLocation : this.mapperLocations) { + if (mapperLocation == null) { + continue; + } + try { + XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), + targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); + xmlMapperBuilder.parse(); + } catch (Exception e) { + throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); + } finally { + ErrorContext.instance().reset(); + } + LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); + } + } + } else { + LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); + } + + final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration); + + // TODO SqlRunner + SqlHelper.FACTORY = sqlSessionFactory; + + // TODO 打印骚东西 Banner + if (globalConfig.isBanner()) { + System.out.println(" | | o | / | | o \\ "); + System.out.println(",-.-., .|---.,---.|--- . ,---.,---.| . .,---.| ,---.| ,---.,---|,-.-..,---. |"); + System.out.println("| | || || |,---|| |---`---.| || | |`---.| |---'| ,---|| || | ||| | |"); + System.out.println("` ' '`---|`---'`---^`---'` `---'|---'`---'`---'`---'| `---'`---'`---^`---'` ' '`` ' |"); + System.out.println(" `---' | \\ / "); + System.out.println(" " + MybatisPlusVersion.getVersion() + " "); + } + + return sqlSessionFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public SqlSessionFactory getObject() throws Exception { + if (this.sqlSessionFactory == null) { + afterPropertiesSet(); + } + + return this.sqlSessionFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getObjectType() { + return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSingleton() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (failFast && event instanceof ContextRefreshedEvent) { + // fail-fast -> check all statements are completed + this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); + } + } + + private Set> scanClasses(String packagePatterns, Class assignableType) throws IOException { + Set> classes = new HashSet<>(); + String[] packagePatternArray = tokenizeToStringArray(packagePatterns, + ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + for (String packagePattern : packagePatternArray) { + Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class"); + for (Resource resource : resources) { + try { + ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata(); + Class clazz = Resources.classForName(classMetadata.getClassName()); + if (assignableType == null || assignableType.isAssignableFrom(clazz)) { + classes.add(clazz); + } + } catch (Throwable e) { + LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString()); + } + } + } + return classes; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBuilder.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBuilder.java new file mode 100644 index 00000000..70e430b5 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/core/MybatisSqlSessionFactoryBuilder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.mybatis.core; + +import com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.injector.SqlRunnerInjector; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import org.apache.ibatis.exceptions.ExceptionFactory; +import org.apache.ibatis.executor.ErrorContext; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Properties; + +/** + * 重写SqlSessionFactoryBuilder + * + * @author nieqiurong 2019/2/23. + */ +public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder { + + @SuppressWarnings("Duplicates") + @Override + public SqlSessionFactory build(Reader reader, String environment, Properties properties) { + try { + //TODO 这里换成 MybatisXMLConfigBuilder 而不是 XMLConfigBuilder + MybatisXMLConfigBuilder parser = new MybatisXMLConfigBuilder(reader, environment, properties); + return build(parser.parse()); + } catch (Exception e) { + throw ExceptionFactory.wrapException("Error building SqlSession.", e); + } finally { + ErrorContext.instance().reset(); + try { + reader.close(); + } catch (IOException e) { + // Intentionally ignore. Prefer previous error. + } + } + } + + @SuppressWarnings("Duplicates") + @Override + public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { + try { + //TODO 这里换成 MybatisXMLConfigBuilder 而不是 XMLConfigBuilder + MybatisXMLConfigBuilder parser = new MybatisXMLConfigBuilder(inputStream, environment, properties); + return build(parser.parse()); + } catch (Exception e) { + throw ExceptionFactory.wrapException("Error building SqlSession.", e); + } finally { + ErrorContext.instance().reset(); + try { + inputStream.close(); + } catch (IOException e) { + // Intentionally ignore. Prefer previous error. + } + } + } + + // TODO 使用自己的逻辑,注入必须组件 + @Override + public SqlSessionFactory build(Configuration config) { + MybatisConfiguration configuration = (MybatisConfiguration) config; + GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration); + final IdentifierGenerator identifierGenerator; + if (globalConfig.getIdentifierGenerator() == null) { + if (null != globalConfig.getWorkerId() && null != globalConfig.getDatacenterId()) { + identifierGenerator = new DefaultIdentifierGenerator(globalConfig.getWorkerId(), globalConfig.getDatacenterId()); + } else { + identifierGenerator = new DefaultIdentifierGenerator(); + } + globalConfig.setIdentifierGenerator(identifierGenerator); + } else { + identifierGenerator = globalConfig.getIdentifierGenerator(); + } + //TODO 这里只是为了兼容下,并没多大重要,方法标记过时了. + IdWorker.setIdentifierGenerator(identifierGenerator); + + if (globalConfig.isEnableSqlRunner()) { + new SqlRunnerInjector().inject(configuration); + } + + SqlSessionFactory sqlSessionFactory = super.build(configuration); + + // 缓存 sqlSessionFactory + globalConfig.setSqlSessionFactory(sqlSessionFactory); + + return sqlSessionFactory; + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/mybatis/package-info.java b/eladmin-common/src/main/java/me/zhengjie/mybatis/package-info.java new file mode 100755 index 00000000..5a8d0649 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/mybatis/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Spring Boot Stater + */ +package me.zhengjie.mybatis; diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoDao.java b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoDao.java index 687aed9c..f2afb400 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoDao.java +++ b/eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoDao.java @@ -1,7 +1,22 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package me.zhengjie.repository; import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import me.zhengjie.base.BaseDao; +import me.zhengjie.base.mybatis.BaseDao; import me.zhengjie.domain.ColumnInfo; import me.zhengjie.repository.jpa.ColumnInfoRepository; import me.zhengjie.repository.mp.ColumnInfoService; @@ -10,13 +25,15 @@ import org.springframework.stereotype.Component; import java.util.List; /** + * JPA 与Mybatis 适配DAO + * * @author liaojinlong * @since 2020/6/28 14:59 */ @Component public class ColumnInfoDao extends BaseDao { - public ColumnInfoDao(ColumnInfoService baseService, ColumnInfoRepository jpaRepository ) { + public ColumnInfoDao(ColumnInfoService baseService, ColumnInfoRepository jpaRepository) { super(baseService, jpaRepository); } diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoMapper.java b/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoMapper.java index 3f489f9f..3682e91a 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoMapper.java +++ b/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoMapper.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package me.zhengjie.repository.mp; import com.baomidou.mybatisplus.core.mapper.BaseMapper; diff --git a/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoService.java b/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoService.java index 266b0a5b..b93623cc 100644 --- a/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoService.java +++ b/eladmin-generator/src/main/java/me/zhengjie/repository/mp/ColumnInfoService.java @@ -1,6 +1,21 @@ +/* + * Copyright 2019-2020 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package me.zhengjie.repository.mp; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import me.zhengjie.base.mybatis.ServiceImpl; import me.zhengjie.domain.ColumnInfo; import org.springframework.stereotype.Repository; diff --git a/eladmin-system/src/main/java/me/zhengjie/AppRun.java b/eladmin-system/src/main/java/me/zhengjie/AppRun.java index ed244064..f39c425d 100644 --- a/eladmin-system/src/main/java/me/zhengjie/AppRun.java +++ b/eladmin-system/src/main/java/me/zhengjie/AppRun.java @@ -18,6 +18,7 @@ package me.zhengjie; import io.swagger.annotations.Api; import me.zhengjie.annotation.rest.AnonymousGetMapping; import me.zhengjie.utils.SpringContextHolder; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; @@ -39,6 +40,7 @@ import org.springframework.web.bind.annotation.RestController; @Api(hidden = true) @SpringBootApplication @EnableTransactionManagement +@MapperScan("me.zhengjie.repository.mp") @EnableJpaAuditing(auditorAwareRef = "auditorAware") public class AppRun { diff --git a/eladmin-system/src/main/java/me/zhengjie/config/MybatisPlusConfig.java b/eladmin-system/src/main/java/me/zhengjie/config/MybatisPlusConfig.java deleted file mode 100644 index 225ed7f8..00000000 --- a/eladmin-system/src/main/java/me/zhengjie/config/MybatisPlusConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.zhengjie.config; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@MapperScan("me.zhengjie.repository.mp") -public class MybatisPlusConfig { - -} diff --git a/eladmin-system/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/eladmin-system/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100755 index 00000000..a920bccd --- /dev/null +++ b/eladmin-system/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,88 @@ +{ + "groups": [ + { + "sourceType": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "name": "mybatis-plus", + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties" + }, + { + "sourceType": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "name": "mybatis-plus.configuration", + "sourceMethod": "getConfiguration()", + "type": "com.baomidou.mybatisplus.core.MybatisConfiguration" + }, + { + "sourceType": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "name": "mybatis-plus.global-config", + "sourceMethod": "getGlobalConfig()", + "type": "com.baomidou.mybatisplus.core.config.GlobalConfig" + }, + { + "sourceType": "com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig", + "name": "mybatis-plus.global-config.db-config", + "sourceMethod": "getDbConfig()", + "type": "com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig" + } + ], + "properties": [ + { + "sourceType": "com.baomidou.mybatisplus.core.config.GlobalConfig", + "name": "mybatis-plus.global-config.identifier-generator", + "deprecation": { + "level": "error", + "reason": "请使用@Bean的方式注入至Spring容器." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.core.config.GlobalConfig", + "name": "mybatis-plus.global-config.meta-object-handler", + "deprecation": { + "level": "error", + "reason": "3.0开始废除此属性,请使用@Bean的方式注入至Spring容器." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.core.config.GlobalConfig", + "name": "mybatis-plus.global-config.sql-injector", + "deprecation": { + "level": "error", + "reason": "3.0开始废除此属性,请使用@Bean的方式注入至Spring容器." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig", + "name": "mybatis-plus.global-config.db-config.key-generator", + "deprecation": { + "level": "error", + "reason": "3.0开始废除此属性,请使用@Bean的方式注入至Spring容器." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "name": "mybatis-plus.default-scripting-language-driver", + "defaultValue": "com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver", + "type": "java.lang.Class", + "deprecation": { + "level": "error", + "reason": "如果修改了该值,你会至少失去几乎所有 mp 提供的功能." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.core.MybatisConfiguration", + "name": "mybatis-plus.configuration.default-scripting-language", + "defaultValue": "com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver", + "type": "java.lang.Class", + "deprecation": { + "level": "error", + "reason": "设置无效." + } + }, + { + "sourceType": "com.baomidou.mybatisplus.core.MybatisConfiguration", + "name": "mybatis-plus.configuration.default-enum-type-handler", + "defaultValue": "org.apache.ibatis.type.EnumTypeHandler", + "description": "A default TypeHandler class for Enum.", + "type": "java.lang.Class" + } + ] +} diff --git a/eladmin-system/src/main/resources/META-INF/spring.factories b/eladmin-system/src/main/resources/META-INF/spring.factories new file mode 100755 index 00000000..8dfa5984 --- /dev/null +++ b/eladmin-system/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +# Auto Configure +org.springframework.boot.env.EnvironmentPostProcessor=\ + me.zhengjie.mybatis.autoconfig.SafetyEncryptProcessor +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + me.zhengjie.mybatis.autoconfig.MybatisPlusAutoConfiguration diff --git a/pom.xml b/pom.xml index 54794b27..132b233c 100644 --- a/pom.xml +++ b/pom.xml @@ -209,8 +209,8 @@ com.baomidou - mybatis-plus-boot-starter - 3.3.3.3-SNAPSHOT + mybatis-plus + 3.3.2