# 抛弃 改造 MybatisPlus 源码方式 ,支持JPA 注解,在EL 中 进行MybatisPlus 适配

pull/432/head
廖金龙 2020-06-29 18:19:13 +08:00
parent b8b23c351e
commit 810f0c6fd6
29 changed files with 4140 additions and 17 deletions

View File

@ -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;
/**
* <p>
* ElAdmin JPA JPA
*
* </p>
*
* @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<Class<?>, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap<>();
/**
*
*/
private static final String DEFAULT_ID_NAME = "id";
/**
* <p>
*
* </p>
*
* @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;
}
/**
* <p>
*
* </p>
*
* @return
*/
@SuppressWarnings("unused")
public static List<TableInfo> getTableInfos() {
return new ArrayList<>(TABLE_INFO_CACHE.values());
}
/**
* <p>
*
* </p>
*
* @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<String> 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;
}
/**
* <p>
* ,,resultMap
* </p>
*
* @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;
}
/**
* <p>
* ,
* </p>
*
* @param clazz
* @param globalConfig
* @param tableInfo
*/
public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
/* 数据库全局配置 */
GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory();
//TODO @咩咩 有空一起来撸完这反射模块.
Reflector reflector = reflectorFactory.findForClass(clazz);
List<Field> list = getAllFields(clazz);
// 标记是否读取到主键
boolean isReadPK = false;
/**
* 使JPA
*/
boolean jpaReadPK = false;
// 是否存在 @TableId 注解
boolean existTableId = isExistTableId(list);
List<TableFieldInfo> 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()));
}
}
/**
* <p>
*
* </p>
*
* @param list
* @return true @TableId ;
*/
public static boolean isExistTableId(List<Field> list) {
return list.stream().anyMatch(field ->
field.isAnnotationPresent(TableId.class) ||
field.isAnnotationPresent(Id.class));
}
/**
* <p>
*
* </p>
*
* @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);
}
/**
* <p>
*
* </p>
*
* @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;
}
/**
* <p>
*
* </p>
*
* @param dbConfig
* @param tableInfo
* @param fieldList
* @return true continue;
*/
private static boolean initTableFieldWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
List<TableFieldInfo> fieldList, Field field) {
/* 获取注解属性,自定义字段 */
TableField tableField = field.getAnnotation(TableField.class);
if (null == tableField) {
return false;
}
fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField));
return true;
}
/**
* <p>
* related
* </p>
*
* @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);
}
}
/**
* <p>
*
* </p>
*
* @param clazz
* @return
*/
public static List<Field> getAllFields(Class<?> clazz) {
List<Field> 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);
}
}

View File

@ -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<I extends IService<T>, J extends JpaRepository<T, ID>, 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) {

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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<M extends BaseMapper<T>, T> implements IService<T> {
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<T> currentModelClass() {
return (Class<T>) 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<T> 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<T> 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<T> 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<T> entityList, int batchSize) {
String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(sqlStatement, param);
});
}
@Override
public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
if (throwEx) {
return baseMapper.selectOne(queryWrapper);
}
return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
}
@Override
public Map<String, Object> getMap(Wrapper<T> queryWrapper) {
return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper));
}
@Override
public <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> 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<SqlSession> 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 <E>
* @return
* @since 3.3.1
*/
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> 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 <E>
* @return
* @since 3.3.1
*/
protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
}
}

View File

@ -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<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> 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);
}
}
}
/**
* <p>
*
* </p>
*
* @param mapperClass mapper
* @return
* @since 3.1.2 add mapperClass
*/
public abstract List<AbstractMethod> 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];
}
}

View File

@ -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<AbstractMethod> 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());
}
}

View File

@ -0,0 +1,269 @@
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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<? extends Annotation> 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<? extends TypeHandler> typeHandler = UnknownTypeHandler.class;
private String numericScale = "";
/**
* ,
* :
* <li> {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} true ,
* (mptrue,mybatisfalse), .replace("_","").toUpperCase() == .toUpperCase() </li>
* <li> {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} false ,
* .toUpperCase() == .toUpperCase()</li>
*/
@Override
public String value() {
return value;
}
/**
*
* true false
*/
@Override
public boolean exist() {
return exist;
}
/**
* where
* `=`
*/
@Override
public String condition() {
return condition;
}
/**
* update set , el 使
* <p>
* 1@TableField(.. , update="%s+1") %s
* SQL update set =+1 where ...
* <p>
* 2@TableField(.. , update="now()") 使
* SQL update set =now() where ...
*/
@Override
public String update() {
return update;
}
/**
* insert: insertinsert
* IGNORED: insert into table_a(column) values (#{columnProperty});
* NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
* NOT_EMPTY: insert into table_a(<if test="columnProperty != null and columnProperty!=''">column</if>) values (<if test="columnProperty != null and columnProperty!=''">#{columnProperty}</if>)
*
* @since 3.1.2
*/
@Override
public FieldStrategy insertStrategy() {
return insertStrategy;
}
/**
* update: set
* IGNORED: update table_a set column=#{columnProperty}, null/stringset
* NOT_NULL: update table_a set <if test="columnProperty != null">column=#{columnProperty}</if>
* NOT_EMPTY: update table_a set <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
*
* @since 3.1.2
*/
@Override
public FieldStrategy updateStrategy() {
return updateStrategy;
}
/**
* where: where
* IGNORED: column=#{columnProperty}
* NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if>
* NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
*
* @since 3.1.2
*/
@Override
public FieldStrategy whereStrategy() {
return whereStrategy;
}
/**
*
*/
@Override
public FieldFill fill() {
return fill;
}
/**
* select
* <p> false select </p>
*/
@Override
public boolean select() {
return select;
}
/**
* 使 Format
* <p> Format {@link #value()} </p>
* <li> false , Format </li>
*
* @since 3.1.1
*/
@Override
public boolean keepGlobalFormat() {
return keepGlobalFormat;
}
/**
* JDBC (),
* mp method,
* {@link TableName#autoResultMap()} 使
* <p>
* {@link ResultMapping#jdbcType} and {@link ParameterMapping#jdbcType}
*
* @since 3.1.2
*/
@Override
public JdbcType jdbcType() {
return jdbcType;
}
/**
* (),
* mp method,
* {@link TableName#autoResultMap()} 使
* <p>
* {@link ResultMapping#typeHandler} and {@link ParameterMapping#typeHandler}
*
* @since 3.1.2
*/
@Override
public Class<? extends TypeHandler> typeHandler() {
return typeHandler;
}
/**
* ,
* mp method,
* {@link TableName#autoResultMap()} 使
* <p>
* {@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<? extends Annotation> annotationType() {
return annotationType;
}
public void setValue(String value) {
this.value = value;
}
public void setAnnotationType(Class<? extends Annotation> 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<? extends TypeHandler> typeHandler) {
this.typeHandler = typeHandler;
}
public void setNumericScale(String numericScale) {
this.numericScale = numericScale;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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<? extends Annotation> 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<? extends Annotation> annotationType() {
return annotationType;
}
public void setValue(String value) {
this.value = value;
}
public void setType(IdType type) {
this.type = type;
}
public void setAnnotationType(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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<? extends Annotation> annotationType;
/**
*
*/
@Override
public String value() {
return value;
}
/**
* schema
*
* @since 3.1.1
*/
@Override
public String schema() {
return schema;
}
/**
* 使 tablePrefix
* <p> tablePrefix {@link #value()} </p>
* <li> false , tablePrefix </li>
*
* @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<? extends Annotation> 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<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
}

View File

@ -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);
}

View File

@ -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}.
* <p>
* 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.
* </p>
* <p> copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}</p>
*
* @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<ConfigurationCustomizer> configurationCustomizers;
private final List<MybatisPlusPropertiesCustomizer> mybatisPlusPropertiesCustomizers;
private final ApplicationContext applicationContext;
public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider,
ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<MybatisPlusPropertiesCustomizer>> 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<? extends LanguageDriver> 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();
}
/**
* springbean,
*
* @param clazz class
* @param consumer
* @param <T>
*/
private <T> void getBeanThen(Class<T> clazz, Consumer<T> 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<String> 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.");
}
}
}

View File

@ -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+)
* <p>
* , mp
*/
private Class<? extends LanguageDriver> 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];
}
}
}

View File

@ -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.
*
* <p> </p>
*
* @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);
}

View File

@ -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<String, Object> 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));
}
}
}
}

View File

@ -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<String> 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());
}
}

View File

@ -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
* <p>FIXME </p>
*/
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<MetaObjectHandler> 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<String> getMapperRegistryCache(Configuration configuration) {
return getGlobalConfig(configuration).getMapperRegistryCache();
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package 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
* <p>Caratacus 2016/9/25 replace mapperRegistry</p>
*
*
* @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
* <p>1XMLSQL</p>
* <p>2sqlProviderSQL</p>
* <p>3xmlSql sqlProviderSQL</p>
* <p>SQLxmlSql > sqlProvider > curdSql</p>
*/
@Override
public void addMappedStatement(MappedStatement ms) {
logger.debug("addMappedStatement: " + ms.getId());
if (mappedStatements.containsKey(ms.getId())) {
/*
* xml mapperSqlProvider
*/
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 <T> void addMapper(Class<T> 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> T getMapper(Class<T> 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<? extends LanguageDriver> 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;
}
}

View File

@ -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.*;
/**
*
* <p>
* {@link MapperAnnotationBuilder#parse} #getReturnType
* XMLCRUD
* </p>
*
* @author Caratacus
* @author liaojinlong
* @since 2020/6/29 18:13
*/
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
private static final Set<Class<? extends Annotation>> 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<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
synchronized (incompleteMethods) {
Iterator<MethodResolver> 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<ResultMapping> 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<ResultMapping> 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<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
(discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler());
Case[] cases = discriminator.cases();
Map<String, String> 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<? extends LanguageDriver> 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<byte[]>
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<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> 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<? extends Annotation> 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<? extends Annotation> getSqlAnnotationType(Method method) {
return chooseAnnotationType(method, SQL_ANNOTATION_TYPES);
}
private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
return chooseAnnotationType(method, SQL_PROVIDER_ANNOTATION_TYPES);
}
private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
for (Class<? extends Annotation> type : types) {
Annotation annotation = method.getAnnotation(type);
if (annotation != null) {
return type;
}
}
return null;
}
private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
for (Result result : results) {
List<ResultFlag> flags = new ArrayList<>();
if (result.id()) {
flags.add(ResultFlag.ID);
}
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
((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<ResultMapping> resultMappings) {
for (Arg arg : args) {
List<ResultFlag> flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if (arg.id()) {
flags.add(ResultFlag.ID);
}
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>)
(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;
}
}

View File

@ -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<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new HashMap<>();
private final MybatisConfiguration config;
public MybatisMapperRegistry(MybatisConfiguration config) {
super(config);
this.config = config;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) 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 <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
@Override
public <T> void addMapper(Class<T> 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<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
}

View File

@ -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()
* <p> MybatisXmlConfigBuilder </p>
* <p> sqlSessionFactoryBuilder ,使 `new MybatisSqlSessionFactoryBuilder()` </p>
* <p> environment ,使 `MybatisSqlSessionFactoryBean.class.getSimpleName()` </p>
*
* @author hubin
* @author liaojinlong
* @since 2020/6/29 18:13
*/
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
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<? extends LanguageDriver> defaultScriptingLanguageDriver;
// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> 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<? extends VFS> getVfs() {
return this.vfs;
}
/**
* Sets the VFS.
*
* @param vfs a VFS
*/
public void setVfs(Class<? extends VFS> 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.
*
* <p>
* 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.
*
* <p>
* 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.
* <p>
* This is an alternative to specifying "&lt;sqlmapper&gt;" 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 &lt;properties&gt;} 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.
* <p>
* 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}.
* <p>
* 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}
* <p>
* 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.
*
* <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> 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<? extends LanguageDriver> 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.
* <p>
* 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).
* </p>
*
* @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<Class<?>> 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<Class<?>> 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<? extends SqlSessionFactory> 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<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
Set<Class<?>> 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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<ColumnInfoService, ColumnInfoRepository, ColumnInfo, Long> {
public ColumnInfoDao(ColumnInfoService baseService, ColumnInfoRepository jpaRepository ) {
public ColumnInfoDao(ColumnInfoService baseService, ColumnInfoRepository jpaRepository) {
super(baseService, jpaRepository);
}

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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 {
}

View File

@ -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<? extends org.apache.ibatis.scripting.LanguageDriver>",
"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<? extends org.apache.ibatis.scripting.LanguageDriver>",
"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<? extends org.apache.ibatis.type.TypeHandler>"
}
]
}

View File

@ -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

View File

@ -209,8 +209,8 @@
<!-- MyBatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.3.3-SNAPSHOT</version>
<artifactId>mybatis-plus</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>