赞
踩
之前一直在用 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
.Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。