之前一直在用 mybatis-plus(mybatis 的增强版, 简化开发),只觉得 mybatis-plus 用起来很方便,但一直没了解其实现原理。于是最近开始学习一下。
平时使用 mybatisplus 时都是定义一个 Mapper 接口继承下 BaseMapper 就直接使用,并没有实现类。
那么现在就开始探究一下 mybatisPlus 是怎么实现的, 首先来了解 mybatisPlus 的加载流程。
版本:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
首先是了解 mybatis-plus 的加载流程。
从 mybatis-plus 源码中的 spring.factories 文件中我们可以了解到,其加载入口为 MybatisPlusAutoConfiguration
。
点进去这个类可以发现里面有几个核心的 bean:
/*
* MybatisPlusAutoConfiguration.java
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// mybatisplus 重写的 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
applyConfiguration(factory); // setConfiguration
// set :interceptors,typeHandlers,
// mapperLocations,typeEnumsPackage,globalConfig 等
// ...
return factory.getObject();
}
@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);
}
}
// 如果不存在 MapperScannerConfigurer bean (对应没有使用 @MapperScan 注解) 则自动注册一个
@Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
//....
}
// 自动注册一个 MapperScannerConfigurer beanDefinition
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
// ...
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 定义 MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// addPropertyValue: basePackage等
// ...
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
首先看 MapperScannerConfigurer 这个 bean,这是一个扫描 mapper 的配置类,内部使用 ClassPathMapperScanner
根据配置扫描 mapper 接口并将接口注册为 MapperFactoryBean。
创建 MapperScannerConfigurer bean 的方式一般有两种:(当然也可以自己手动声明创建)
AutoConfiguredMapperScannerRegistrar
(如上 AutoConfig 中手动注册的 beanDefinition)扫描 spring 默认包下的带 @Mapper
注解的接口。MapperScannerRegistrar
通过解析 @MapperScan
注解的属性在 spring 中注册 MapperScannerConfigurer
的 beanDefinition。
@MapperScan
注解:默认扫描注解的类所在的包下所有接口。主要提供一下功能
MapperScannerConfigurer#postProcessBeanDefinitionRegistry
ClassPathMapperScanner
对象(继承 ClassPathBeanDefinitionScanner
),设置相关属性(参考MapperScan
属性),根据设置的属性注册扫描过滤器,开始执行扫描// MapperScannerConfigurer.java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// set 相关属性 扫描过滤规则、指定 sqlSessionxxx。。
...
// 根据 set 的属性,注册扫描过滤器
scanner.registerFilters();
// 扫描 mapper 接口,并注册 beanDefinition, 底层是 doScan
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner#doScan
MapperFactoryBean
(或@MapperScan
指定的自定义类型)// ClassPathMapperScanner.java
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类 ClassPathBeanDefinitionScanner 方法扫描并注册 beanDifinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 对 mapper 的 beanDefinition 进行处理: 修改 beanClass 类型等。
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
...
// 构造方法中传入原 mapper bean 类型
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 修改 bean 类型为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
... // set sqlSessionFactory, sqlSessionTemplate (如果存在的话)
// 设置懒加载
definition.setLazyInit(lazyInitialization);
}
}
前面可知,扫描 Mapper 后会将其 beanDefinition.beanClass 修改为 MapperFactoryBean
. 所以在后续 spring 将 mapper bean 初始化时,会通过调用 MapperFactoryBean.getObject
获取其对象。
查看源码可以发现,最终是调用 sqlSession 的 getMapper 方法获取 mapper 对象。具体实现后面再看。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里的 sqlSessionFactory 是 mybatis 的用来获取 sqlSession (用来执行 sql 管理事务的对象)的工厂类. 里面主要提供 openSession 的一些列重载方法。
创建 sqlSessionFactory 的逻辑在 MybatisSqlSessionFactoryBean#buildSqlSessionFactory
方法中,里面主要是
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
if (this.configuration != null) { // AutoConfig 里面 set 的 configuration
targetConfiguration = this.configuration;
...
}
...
// 加载前面的配置 并 set 到 targetConfiguration 中,
// 如:set globalConfig, register typeHandlers, addInterceptor, parse xmlMapper
...
// 解析 mapper.xml, 并将解析的结果存在 targetConfiguration 中
for (Resource mapperLocation : this.mapperLocations) {
...
Builder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
...
}
...
// 构建 SqlSessionFactory, 实现类为 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);
、
return sqlSessionFactory;
}
XMLMapperBuilder 主要用于解析 mapper.xml 文件,入口为 XMLMapperBuilder#parse
, XMLMapperBuilder 会解析 mapper.xml 文件中配置的 statements、resultMap、parameter 等信息, 并将其存放于 Configuration 的 对应 map 中。
// XMLMapperBuilder.java
public void parse() {
// resource: file /../xxxMapper.xml 判断是否加载过
if (!configuration.isResourceLoaded(resource)) {
// 1 解析 mapper.xml 文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2 注册 Mapper
// 底层调用 MybatisMapperRegistry#addMapper 注册 Mapper
bindMapperForNamespace();
}
// 前面没解析完成的 继续解析, 看代码主要是针对前面解析出错的
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析 mapper.xml 后 Configuration 对象示例:
当前只包含 mapper.xml 中的 sql 语句。
<–, 里面解析 注解 sql 和 mybatisPlus 的默认 sql–>
前面 XMLMapperBuilder 解析完 xml 文件后,就会在 MybatisMapperRegistry
中添加对应的 Mapper 代理工厂 MybatisMapperProxyFactory
。同时使用 MybatisMapperAnnotationBuilder
解析 mapper 接口中使用注解写的 sql 语句。
这里的 MybatisMapperProxyFactory
就是用来获取 Mapper 代理(MybatisMapperProxy
)的工厂类。 在调用 Mapper 中方法的时候其实就是调用的 MybatisMapperProxy#invoke
方法。
备注:MybatisMapperRegistry
同时还有另一个方法 getMapper(Class<T> type, SqlSession sqlSession)
, 用来获取 Mapper 代理,后面讲。
// MybatisMapperRegistry.java
// type 为 mapper 接口
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
}
boolean loadCompleted = false;
try {
// 注册 Mapper 的代理工厂
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
// 再解析 Mapper 接口中使用注解写的 sql 语句
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 里面会继续调用 AbstractSqlInjector#inspectInject 注入 mybatisPlus 的动态 curd 方法
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
<–##### MybatisMapperAnnotationBuilder 解析注解 sql & 注入 动态 sql–>
另 MybatisMapperAnnotationBuilder
在解析完注解 sql 后,会注入 mybatis-plus 的的 curd 动态 sql。平时调用 mybatis-plus 的 BaseMapper 的方法就是使用的这里注入的动态 sql。
// MybatisMapperAnnotationBuilder.java
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
...
// 遍历Mapper 接口中的方法,解析方法上的注解 sql(如果存在的话)
for (Method method : type.getMethods()) {
...
parseStatement(method);
...
}
...
// 如果继承了 baseMapper,就注入 CURD 动态 SQL
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
parserInjector();
}
...
}
parsePendingMethods();
}
// 注入动态 sql, AbstractSqlInjector
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
AbstractSqlInjector 注入的 sql 默认为 DefaultSqlInjector 中的方法(也就是 BaseMapper 中的方法)。 注入的方式为:
备注:这里的数据表字段是通过 TableInfoHelper
根据实体类字段和配置推断出来的,如根据 @TableField
/@TableId
注解指定字段名,或根据驼峰下划线转换规则推断。
注入的详细代码参考: AbstractSqlInjector#inspectInject
这里贴一段注入的 update 的 sql script。 (其中 et
为更新的实体类,ew
为查询的条件。)
<script>
UPDATE user <set>
<if test="et != null">
<if test="et['name'] != null">name=#{et.name},</if>
<if test="et['phone'] != null">phone=#{et.phone},</if>
<if test="et['age'] != null">age=#{et.age},</if>
</if>
<if test="ew != null and ew.sqlSet != null">${ew.sqlSet}</if>
</set>
<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['name'] != null"> AND name=#{ew.entity.name}</if>
<if test="ew.entity['phone'] != null"> AND phone=#{ew.entity.phone}</if>
<if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if>
</if>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
<if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
</if>
</where>
<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>
线程安全、Spring管理的、g,以确保实际使用的SqlSession是与当前Spring事务关联的。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。
SqlSessionTemplate
实现了 SqlSession
接口,但是内部持有一个 sqlSessionProxy 代理对象,最后都是调用的代理对象的方法。 而代理对象中最终调用的 sqlSession 也是通过 sqlSessionFactory.openSession
来获取的。只不过在 openSession 前会从 spring 事务同步管理器中获取一遍,不存在才创建一个新的 sqlSession,并且再执行完成后关闭或释放(引用数量-1)。
这样就可以在需要使用 sqlSession 时直接使用 sqlSessionTemplate,而不是需要每次都通过 sqlSessinFactory 获取 sqlSession,也不需要考虑 sqlSesion 的关闭。同时保证了在同一个 spring 事务中使用同一个 sqlSession 对象。
相关代码如下:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// ...
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建一个 sqlSession 的代理, 实现为一个内部类
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
// 数据库的相关操作均由代理对象实现, 但是不支持 commit,因为代理对象中户实现
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
...
// 前面提到的 getMapper 方法,最终到 Configuration 中获取
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
/*
* sqlSession 的代理实现
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取真正的 sqlSession,逻辑参考:SqlSessionUtils#getSqlSession)
// 从 spring 的 TransactionSynchronizationManager 事务同步管理器 (中的 threadlocal) 获取 sqlSession
// 不存在则通过 sqlSessionFactory.openSession 创建新的 sqlSession 对象,并保存到 spring 中
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 使用 sqlSession 执行对应方法
Object result = method.invoke(sqlSession, args);
// ...
return result;
} catch (Throwable t) {
// unwrapThrowable
// SqlSessionUtils.closeSqlSession
throw unwrapped;
} finally {
// 如果是 spring 管理的则 release(引用数量-1), 不是则 close
// SqlSessionUtils.closeSqlSession
}
}
}
}
SqlSessionUtils
处理MyBatis SqlSession生命周期,可以在 spring (TransactionSynchronizationManager)注册和获取 sqlSession。
TransactionSynchronizationManager
spring 管理每个线程的资源和事务同步的中央委托。里面使用维护了一些 ThreadLocal 用户保存想成相关的事务信息。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
// ...
}
源码中可以看到, 前面提到的 sqlSessionTempalte 的 getMapper 方法中也是调用 configuration.getMapper
方法来获取 mapper 对象的. 而configuration 里面又是通过 MybatisMapperRegistry
获取 mapper。
前面解析 mapper 时提到了 MybatisMapperRegistry
提供了一个 getMapper 方法用来获取 mapper 代理对象。
在这个 getMapper 方法中之前注册的代理工厂 MybatisMapperProxyFactory
使用 通过 MybatisMapperProxy
生成了 mapper 的代理对象。
// sqlSessionTempalte
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
// MybatisConfiguration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
// MybatisMapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// knownMappers 为前面解析 mapper 后注册的 mapper 代理工厂对象
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
// 传入 sqlSession 创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MybatisMapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
mybatisPlus 的整个加载过程概括如下:
MapperScannerConfigurer
扫描 mapper 接口,并在 spring 中注册 deanDefinition,类型为 MapperFactoryBean
SqlSessionFactory
解析 mapper.xml 和 mapper 接口 中的 sql 语句保存到 Configuration 中,同时加入 mybatisPlus 提供的动态 sql。 最后注册对应 mapper 的 MybatisMapperProxyFactory
。SqlSessionTemplate
使用 SqlSessionInterceptor
代理实现一个线程安全的 spring 管理的 SqlSession,并最终通过 MybatisMapperProxyFactory
获取 mapper 的代理对象 MybatisMapperProxy
.联系客服