之前一直在用 mybatis-plus(mybatis 的增强版, 简化开发),只觉得 mybatis-plus 用起来很方便,但一直没了解其实现原理。于是最近开始学习一下。 平时使用 mybatisplus 时都是定义一个 Mapper 接口继承下 BaseMapper 就直接使用,并没有实现类。
那么现在就开始探究一下 mybatisPlus 是怎么实现的, 首先来了解 mybatisPlus 的加载流程。
springboot 中 mybatis-plus 加载流程
首先是了解 mybatis-plus 的加载流程。
从 mybatis-plus 源码中的 spring.factories 文件中我们可以了解到,其加载入口为 MybatisPlusAutoConfiguration 。 点进去这个类可以发现里面有几个核心的 bean:
- SqlSessionFactory
- SqlSessionTemplate
- MapperScannerConfigurer
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
applyConfiguration(factory);
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);
}
}
@Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
MapperScannerConfigurer
首先看 MapperScannerConfigurer 这个 bean,这是一个扫描 mapper 的配置类,内部使用 ClassPathMapperScanner 根据配置扫描 mapper 接口并将接口注册为 MapperFactoryBean。
创建 MapperScannerConfigurer bean 的方式一般有两种:(当然也可以自己手动声明创建)
- 一种是默认的使用
AutoConfiguredMapperScannerRegistrar (如上 AutoConfig 中手动注册的 beanDefinition)扫描 spring 默认包下的带 @Mapper 注解的接口。 - 一种是
MapperScannerRegistrar 通过解析 @MapperScan 注解的属性在 spring 中注册 MapperScannerConfigurer 的 beanDefinition。
- 关于
@MapperScan 注解:默认扫描注解的类所在的包下所有接口。主要提供一下功能
- 配置扫描规则:指定扫描的包,指定扫描携带的注解,指定继承的接口等。
- 指定 sqlSessionFactory 等
- 指定 自定义的 MapperFactoryBean
扫描并注册 Mapper BeanDefinition
MapperScannerConfigurer#postProcessBeanDefinitionRegistry
- 创建
ClassPathMapperScanner 对象(继承 ClassPathBeanDefinitionScanner ),设置相关属性(参考MapperScan 属性),根据设置的属性注册扫描过滤器,开始执行扫描
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner#doScan
- 根据配置的过滤器扫描相关接口,并注册 beanDifinition
- 修改扫描到的 beanDefinition 类型为
MapperFactoryBean (或@MapperScan 指定的自定义类型)
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
...
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);
...
definition.setLazyInit(lazyInitialization);
}
}
MapperFactoryBean
前面可知,扫描 Mapper 后会将其 beanDefinition.beanClass 修改为 MapperFactoryBean . 所以在后续 spring 将 mapper bean 初始化时,会通过调用 MapperFactoryBean.getObject 获取其对象。
查看源码可以发现,最终是调用 sqlSession 的 getMapper 方法获取 mapper 对象。具体实现后面再看。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
SqlSessionFactory
这里的 sqlSessionFactory 是 mybatis 的用来获取 sqlSession (用来执行 sql 管理事务的对象)的工厂类. 里面主要提供 openSession 的一些列重载方法。
创建 sqlSessionFactory 的逻辑在 MybatisSqlSessionFactoryBean#buildSqlSessionFactory 方法中,里面主要是
- 设置前面配置的属性:globalConfig,typeHandlers,interceptor…
- 解析 mapper.xml 并保存到 configuration 中
- 创建 sqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
if (this.configuration != null) {
targetConfiguration = this.configuration;
...
}
...
...
for (Resource mapperLocation : this.mapperLocations) {
...
Builder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
...
}
...
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);
、
return sqlSessionFactory;
}
解析 mapper.xml
XMLMapperBuilder 主要用于解析 mapper.xml 文件,入口为 XMLMapperBuilder#parse , XMLMapperBuilder 会解析 mapper.xml 文件中配置的 statements、resultMap、parameter 等信息, 并将其存放于 Configuration 的 对应 map 中。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析 mapper.xml 后 Configuration 对象示例:
当前只包含 mapper.xml 中的 sql 语句。
<–, 里面解析 注解 sql 和 mybatisPlus 的默认 sql–>
MybatisMapperRegistry 中注册 mapper 代理工厂
前面 XMLMapperBuilder 解析完 xml 文件后,就会在 MybatisMapperRegistry 中添加对应的 Mapper 代理工厂 MybatisMapperProxyFactory 。同时使用 MybatisMapperAnnotationBuilder 解析 mapper 接口中使用注解写的 sql 语句。
这里的 MybatisMapperProxyFactory 就是用来获取 Mapper 代理(MybatisMapperProxy )的工厂类。 在调用 Mapper 中方法的时候其实就是调用的 MybatisMapperProxy#invoke 方法。
备注:MybatisMapperRegistry 同时还有另一个方法 getMapper(Class<T> type, SqlSession sqlSession) , 用来获取 Mapper 代理,后面讲。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
return;
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
<–##### MybatisMapperAnnotationBuilder 解析注解 sql & 注入 动态 sql–>
注入 curd 动态 sql
另 MybatisMapperAnnotationBuilder 在解析完注解 sql 后,会注入 mybatis-plus 的的 curd 动态 sql。平时调用 mybatis-plus 的 BaseMapper 的方法就是使用的这里注入的动态 sql。
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
...
for (Method method : type.getMethods()) {
...
parseStatement(method);
...
}
...
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
parserInjector();
}
...
}
parsePendingMethods();
}
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
AbstractSqlInjector 注入的 sql 默认为 DefaultSqlInjector 中的方法(也就是 BaseMapper 中的方法)。 注入的方式为:
- 根据不同方法的模板构建对应的 sql 脚本(同 xml),并填充对应的数据表和实体类的字段
备注:这里的数据表字段是通过 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>
SqlSessionTemplate
线程安全、Spring管理的、g,以确保实际使用的SqlSession是与当前Spring事务关联的。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。
sqlSession 代理
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
...
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
return result;
} catch (Throwable t) {
throw unwrapped;
} finally {
}
}
}
}
-
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");
}
获取 Mapper 对象
源码中可以看到, 前面提到的 sqlSessionTempalte 的 getMapper 方法中也是调用 configuration.getMapper 方法来获取 mapper 对象的. 而configuration 里面又是通过 MybatisMapperRegistry 获取 mapper。
前面解析 mapper 时提到了 MybatisMapperRegistry 提供了一个 getMapper 方法用来获取 mapper 代理对象。
在这个 getMapper 方法中之前注册的代理工厂 MybatisMapperProxyFactory 使用 通过 MybatisMapperProxy 生成了 mapper 的代理对象。
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
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 .
|