|
之前一直在用 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,类型为 MapperFactoryBeanSqlSessionFactory 解析 mapper.xml 和 mapper 接口 中的 sql 语句保存到 Configuration 中,同时加入 mybatisPlus 提供的动态 sql。 最后注册对应 mapper 的 MybatisMapperProxyFactory。SqlSessionTemplate 使用 SqlSessionInterceptor 代理实现一个线程安全的 spring 管理的 SqlSession,并最终通过 MybatisMapperProxyFactory 获取 mapper 的代理对象 MybatisMapperProxy.
|