本文将结合源码介绍mybatis-plus的原理,包括:
系列文档:
MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis-plus会自动为BaseMapper API创建MappedStatement,后续我们会分析这个过程:
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
int updateById(@Param(Constants.ENTITY) T entity);
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(
E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
这个类也实现了FactoryBean接口,最终也会创建一个DefaultSqlSessionFactory对象,基本上和SqlSessionFactoryBean的作用一致,只是在中间加入了mybatis-plus的Statement注入等逻辑。
另外还有一些mybatis-plus的自定义配置参数,我们平时能够使用的只有GlobalConfig和DbConfig这两个类。
这个类由mybatis-plus提供,继承了mybatis的Configuration类,用于封装mybatis和mybatis-plus的核心配置参数。
与本文相关的内容暂时只有这个:
protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
在分析mybatis getMapper(Class)原理时,我们了解到mybatis是使用MapperRegistry.getMapper(Class)方法实现的Mapper接口扫描和创建代理。而在mybatis-plus中,使用的是MybatisMapperRegistry类,这个类由mybatis-plus提供,继承了MapperRegistry类,重写了getMapper(Class)和addMapper(Class)等核心方法,在这些方法中实现了mybatis-plus BaseMapper API Statement的解析和注入。后续有详细分析。
封装全局配置信息:
public class GlobalConfig implements Serializable {
// 是否开启LOGO
private boolean banner = true;
// 是否初始化SqlRunner
private boolean enableSqlRunner = false;
// 数据库相关配置
private DbConfig dbConfig;
// SQL注入器
private ISqlInjector sqlInjector = new DefaultSqlInjector();
// Mapper父类
private Class<?> superMapperClass = Mapper.class;
// 仅用于缓存SqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
// 缓存已注入CRUD的Mapper信息
private Set<String> mapperRegistryCache = new ConcurrentSkipListSet<>();
// 元对象字段填充控制器
private MetaObjectHandler metaObjectHandler;
// 主键生成器
private IdentifierGenerator identifierGenerator;
}
封装DB通用配置:
public static class DbConfig {
// 主键类型
private IdType idType = IdType.ASSIGN_ID;
// 表名前缀
private String tablePrefix;
// schema
private String schema;
//
private String columnFormat;
//
private String propertyFormat;
//
private boolean replacePlaceholder;
// 转义符
private String escapeSymbol;
// 表名是否使用驼峰转下划线命名,只对表名生效
private boolean tableUnderline = true;
// 大写命名,对表名和字段名均生效
private boolean capitalMode = false;
// 表主键生成器
private IKeyGenerator keyGenerator;
// 逻辑删除全局属性名
private String logicDeleteField;
// 逻辑删除全局值,默认1表示已删除
private String logicDeleteValue = "1";
// 逻辑未删除全局值,默认0表示未删除
private String logicNotDeleteValue = "0";
// 字段insert验证策略
private FieldStrategy insertStrategy = FieldStrategy.NOT_NULL;
// 字段update验证策略
private FieldStrategy updateStrategy = FieldStrategy.NOT_NULL;
// 字段select验证策略
private FieldStrategy selectStrategy = FieldStrategy.NOT_NULL;
}
这个类里面的insertStrategy、updateStrategy、selectStrategy后续深入分析时再做分析。
入口在**com.baomidou.mybatisplus.core.MybatisConfiguration.getMapper(Class, SqlSession)**方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
这里的mybatisMapperRegistry就是上文介绍过的MybatisMapperRegistry类对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 这里换成MybatisMapperProxyFactory而不是MapperProxyFactory
final MybatisMapperProxyFactory<T> mapperProxyFactory = 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);
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
return;
}
boolean loadCompleted = false;
try {
// 这里也换成MybatisMapperProxyFactory而不是MapperProxyFactory
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
// 这里也换成MybatisMapperAnnotationBuilder而不是MapperAnnotationBuilder
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 这里有mybatis-plus的Statement解析、注入逻辑
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
String mapperName = type.getName();
assistant.setCurrentNamespace(mapperName);
parseCache();
parseCacheRef();
// 从以下开始加入了mybatis-plus的核心逻辑
InterceptorIgnoreHelper.InterceptorIgnoreCache cache =
InterceptorIgnoreHelper.initSqlParserInfoCache(type);
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 加入注解过滤缓存
InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
SqlParserHelper.initSqlParserInfoCache(mapperName, method);
parseStatement(method);
} catch (IncompleteElementException e) {
// 使用MybatisMethodResolver而不是MethodResolver
configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
}
}
// 注入CURD动态SQL,放在最后,可能会有人会用注解重写sql
try {
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
parserInjector();
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new InjectorResolver(this));
}
}
parsePendingMethods();
}
由于mybatis-plus注入CRUD SQL的核心逻辑在这个方法,我们重点看一下这个方法:
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
这里获取到的是DefaultSqlInjector对象。前文分析过。
inspectInject方法:
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
// 解析model类型,比如Blog、User类型
Class<?> modelClass = extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache =
GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
// 未解析过才做model解析
if (!mapperRegistryCache.contains(className)) {
// 待解析的方法
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
// 通过model解析数据库表信息
TableInfo tableInfo = TableInfoHelper.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);
}
}
}
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());
}
TableInfo类,封装从model解析出来的数据库表信息,这个类还是比较简单的:
public class TableInfo implements Constants {
// 实体类型
private Class<?> entityType;
// 表主键ID生成类型,不展开分析
private IdType idType = IdType.NONE;
// 表名称
private String tableName;
// 表映射结果集
private String resultMap;
// 是否是需要自动生成的resultMap
private boolean autoInitResultMap;
// 主键是否有存在字段名与属性名关联,true表示要进行as
private boolean keyRelated;
// 表主键ID字段名
private String keyColumn;
// 表主键ID属性名
private String keyProperty;
// 表主键ID属性类型
private Class<?> keyType;
// 表主键ID Sequence
private KeySequence keySequence;
// 表字段信息列表
private List<TableFieldInfo> fieldList;
// 命名空间,对应的mapper接口的全类名
private String currentNamespace;
// MybatisConfiguration引用
private Configuration configuration;
// 是否开启下划线转驼峰
private boolean underCamel;
// 缓存包含主键及字段的sql select
private String allSqlSelect;
// 缓存主键字段的sql select
private String sqlSelect;
// 表字段是否启用了插入填充
private boolean withInsertFill;
// 表字段是否启用了更新填充
private boolean withUpdateFill;
// 表字段是否启用了逻辑删除
private boolean withLogicDelete;
// 逻辑删除字段
private TableFieldInfo logicDeleteFieldInfo;
// 表字段是否启用了乐观锁
private boolean withVersion;
// 乐观锁字段
private TableFieldInfo versionFieldInfo;
TableFieldInfo类,封装表字段信息:
public class TableFieldInfo implements Constants {
// 属性的引用
private final Field field;
// 字段名
private final String column;
// 属性名
private final String property;
// 属性表达式#{property}, 可以指定jdbcType, typeHandler等
private final String el;
// 属性类型
private final Class<?> propertyType;
// 是否是基本数据类型
private final boolean isPrimitive;
// 属性是否是CharSequence类型
private final boolean isCharSequence;
// 字段insert验证策略
private final FieldStrategy insertStrategy;
// 字段update验证策略
private final FieldStrategy updateStrategy;
// 字段where验证策略
private final FieldStrategy whereStrategy;
// 是否是乐观锁字段
private final boolean version;
// 是否进行select查询
private boolean select = true;
// 是否是逻辑删除字段
private boolean logicDelete = false;
// 逻辑删除值
private String logicDeleteValue;
// 逻辑未删除值
private String logicNotDeleteValue;
// 字段update set部分注入
private String update;
// where字段比较条件
private String condition = SqlCondition.EQUAL;
// 字段填充策略
private FieldFill fieldFill = FieldFill.DEFAULT;
// 表字段是否启用了插入填充
private boolean withInsertFill;
// 表字段是否启用了更新填充
private boolean withUpdateFill;
// 缓存sql select
private String sqlSelect;
// JDBC类型
private JdbcType jdbcType;
// 类型处理器
private Class<? extends TypeHandler<?>> typeHandler;
public void inject(
MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
/* 注入自定义方法 */
injectMappedStatement(mapperClass, modelClass, tableInfo);
}
// 这是一个抽象方法,由子类实现,以SelectList类为例
public abstract MappedStatement injectMappedStatement(
Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);
SelectList类:
public class SelectList extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(
Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
//
SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
// 这里根据sqlMethod、tableInfo等信息生成一个动态SQL
// 与我们在mapper xml文件里面配置效果一样
String sql = String.format(
sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo), sqlComment());
// 生成SqlSource,此处是一个动态DynamicSqlSource对象
// 此处languageDriver是MybatisXMLLanguageDriver对象
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
}
}
// MybatisXMLLanguageDriver.createSqlSource(configuration, sql, modelClass)
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
GlobalConfig.DbConfig config = GlobalConfigUtils.getDbConfig(configuration);
if (config.isReplacePlaceholder()) {
List<String> find = SqlUtils.findPlaceholder(script);
if (CollectionUtils.isNotEmpty(find)) {
try {
script = SqlUtils.replaceSqlPlaceholder(script, find, config.getEscapeSymbol());
} catch (MybatisPlusException e) {
throw new IncompleteElementException();
}
}
}
return super.createSqlSource(configuration, script, parameterType);
}
// super.createSqlSource(configuration, script, parameterType)
// 这里回到了之前分析mapper xml解析的代码上,已经分析过,此处不再展开分析
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
if (script.startsWith("
格式化后的动态SQL:
<script>
<choose>
<when test="ew != null and ew.sqlFirst != null">
${ew.sqlFirst}
</when>
<otherwise></otherwise>
</choose> SELECT
<!-- 拼接查询字段 -->
<choose>
<when test="ew != null and ew.sqlSelect != null">
${ew.sqlSelect}
</when>
<otherwise>id,title,content,create_time,update_time</otherwise>
</choose> FROM blog <!-- 拼接表名 -->
<!-- 拼接查询条件 -->
<if test="ew != null">
<where>
<!-- 拼接实体对象查询条件 -->
<if test="ew.entity != null">
<if test="ew.entity.id != null">id=#{ew.entity.id}</if>
<if test="ew.entity['title'] != null"> AND title=#{ew.entity.title}</if>
<if test="ew.entity['content'] != null"> AND content=#{ew.entity.content}</if>
<if test="ew.entity['createTime'] != null"> AND create_time=#{ew.entity.createTime}</if>
<if test="ew.entity['updateTime'] != null"> AND update_time=#{ew.entity.updateTime}</if>
</if>
<!-- 拼接Wrapper对象查询条件 -->
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
<if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
</if>
</where>
<!-- 拼接Wrapper对象查询条件,这里通常没有办法进来,存在疑问 -->
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
${ew.sqlSegment}
</if>
</if>
<choose>
<when test="ew != null and ew.sqlComment != null">
${ew.sqlComment}
</when>
<otherwise></otherwise>
</choose>
script>
生成的SqlSource:
之前分析过了,此处不再展开。