Spring整合Mybatis原理

1、@MapperScan注解发挥作用

在Spring整合Mybatis的时候,只需要一个@MapperScan注解就可以来进行操作,所以更加好奇的是@MapperScan底层是怎么来做到的。

下面先来研究一下@MapperScan:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}

@Import注解导入了一个MapperScannerRegistrar。而在Spring启动的时候,会在org.Springframework.context.support.AbstractApplicationContext#refresh方法中会来执行@Import导入的类,看下如何来解析@Import注解的。直接来到org.Springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass会来解析@Import注解导入进来的类,那么下面就需要来看一下MapperScannerRegistrar做了什么事情。

1.1、导入MapperScannerRegistrar类

下面来看一下MapperScannerRegistrar的结构体系

1.1.2、执行ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions方法

MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,而实现了接口中的方法,那么在注册BeanDefinition之前,就会来执行ImportBeanDefinitionRegistrar接口中的方法

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}

那么只需要看一下MapperScannerRegistrar中的registerBeanDefinitions方法是如何来进行实现的:

1.2、MapperScannerConfigurer

而在上面的代码中,主要是注册了一个MapperScannerConfigurer类型的BeanDefinition

那么来看一下这个BeanDefinition有什么特点:

MapperScannerRegistrar实现了BeanDefinitionRegistryPostProcessor接口。而这个接口中又会在ConfigurationClassPostProcessor之后发挥作用,而MapperScannerRegistrar对应的BeanDefinition是在此之前来进行解析的,所以将会在下面来解析MapperScannerRegistrar对应的BeanDefinition

那么就会来执行MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法

1.2、ClassPathMapperScanner

那么看一下Spring-Mybatis中自定义整合的ClassPathMapperScanner中的注册过滤器和扫描逻辑:

将扫描出来的类利用包含过滤器添加到当前逻辑中。

那么来看一下扫描逻辑:

然后可以看到只要接口的类

1.2.1、对筛选出来的BeanDefinition进行处理

然后对扫描出来的接口来进行筛选判断:

org.mybatis.Spring.mapper.ClassPathMapperScanner#processBeanDefinitions

1、首先获取得到接口的全限定类型。如:com.guang.dao.UserDao;

2、将当前的BeanClass设置成MapperFactoryBean,而这个类是FactoryBean类型的。在创建对象的时候,说明是要用getObject方法来创建对象的;

3、设置MapperFactoryBean构造函数中的值。在MapperFactoryBean构造函数中是Class类型,而这里设置的是String,在创建的时候Spring回来进行转换;

4、将MapperFactoryBean的注入模型设置为By-Type。也就是说,MapperFactoryBean中的setXxx中的属性会从容器中来进行查找

那么看一下MapperFactoryBean的构造方法:

  public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

这里会将mapperInterface在进行赋值的时候将java.lang.String转换成Class类型,表示对应的接口。

第二个:

说明了在MapperFactoryBean中的setXxxx方法对应的xxx属性,Spring会自动来进行赋值。而MapperFactoryBean继承了SqlSessionDaoSupport,在SqlSessionDaoSupport类中的set方法中存在setSqlSessionFactory方法和setSqlSessionTemplate方法,所以容器中如果存在着对应类型的对象的时候,那么Spring到时候来进行赋值的时候将会从容器中来进行获取得到对应的bean。

所以:SqlSessionTemplate我们可以自己配置,但是SqlSessionFactory就得由我们自己来进行配置了。

1.3、SqlSessionFactoryBean类结构体系

SqlSessionFactoryBean类就是来帮我们创建SqlSessionFactory,看下SqlSessionFactory的结构如下所示:

SqlSessionFactoryBean也是一个FactoryBean类型的,产生的对象的类型是:SqlSessionFactory。

看一下对应的getObjectType方法:

public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}

看一下对应的getObject方法:

public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
} return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 调用对应的buildSqlSessionFactory方法产生一下sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}

而根据buildSqlSessionFactory方法可以知道,我们可以通过对SqlSessionFactoryBean对象的属性来进行设置,从而实现对Configuration对象属性的设置。

其中有一行代码值得注意下:

targetConfiguration.setEnvironment(new Environment(this.environment,this.transactionFactory == null ?
new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));

对于Environment对象来说,看看构造函数:

public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}

需要的第二个参数是TransactionFactory(事务工厂),Spring-Mybatis整合包中,利用SpringManagedTransactionFactory类实现TransactionFactory,重写其中的方法,让当前事务交给Spring来进行处理。

那么后续Mybatis如果存在事务操作的时候,将会由Spring中的事务来进行处理。

1.3.1、SqlSessionFactoryBean使用

这里需要注意的是,下面的使用方式是通过配置类的方式来进行注入的。

因为@MapperScan注解导入的MapperScannerRegistrar的作用就是来注册一个MapperScannerConfigurer类型的BeanDefinition。

所以在下面可以直接利用@Bean注入一个对应类型的BeanDefinition,然后给BeanDefinition来设置一些属性而已。

其中在MapperScannerConfigurer实现InitializingBean接口中的afterPropertiesSet方法中判断了扫描包不能够为空,也就是说要求必须设置basePackage属性

第一种使用方式

第二种使用方式

结合SpringBoot项目使用

@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private String typeAliasesPackage;
private String[] mapperLocations;
private Integer batchExecuteSize = 1000; public MybatisProperties() {
} public String getTypeAliasesPackage() {
return this.typeAliasesPackage;
} public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
} public String[] getMapperLocations() {
return this.mapperLocations;
} public void setMapperLocations(String[] mapperLocations) {
this.mapperLocations = mapperLocations;
} public Integer getBatchExecuteSize() {
return this.batchExecuteSize;
} public void setBatchExecuteSize(Integer batchExecuteSize) {
this.batchExecuteSize = batchExecuteSize;
}
}
@Configuration
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceConfig.class})
@MapperScan({"com.guang.common.dao"})
public class MybatisConfig {
private static final String CONFIG_LOCATION = "classpath:mybatis/mybatis-config.xml";
private static final String COMMON_MAPPER_LOCATIONS = "classpath:com/guang/common/dao/mapper/*Mapper.xml";
private final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); public MybatisConfig() {
} @Bean(
name = {"sqlSessionFactory"}
)
@ConditionalOnClass({DataSource.class})
@ConditionalOnBean({DataSource.class})
public SqlSessionFactory sqlSessionFactory(MybatisProperties mybatisProperties, DataSource dataSource, ObjectProvider<Interceptor> interceptorObjectProvider) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfigLocation(this.getConfigLocation());
sqlSessionFactoryBean.setMapperLocations(this.getMapperLocations(mybatisProperties));
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());
List<Interceptor> interceptorList = (List)interceptorObjectProvider.stream().collect(Collectors.toList());
sqlSessionFactoryBean.setPlugins((Interceptor[])interceptorList.toArray(new Interceptor[0]));
return sqlSessionFactoryBean.getObject();
} @Primary
@Bean(
name = {"simpleSqlSessionTemplate"}
)
@ConditionalOnClass({SqlSessionFactory.class})
@ConditionalOnBean({SqlSessionFactory.class})
public SqlSessionTemplate simpleSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.SIMPLE);
} @Bean(
name = {"batchSqlSessionTemplate"}
)
@ConditionalOnClass({SqlSessionFactory.class})
@ConditionalOnBean({SqlSessionFactory.class})
public SqlSessionTemplate batchSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
} @Bean(
name = {"mapperBatchExecutor"}
)
@ConditionalOnClass({SqlSessionTemplate.class})
@ConditionalOnBean(
value = {SqlSessionTemplate.class},
name = {"batchSqlSessionTemplate"}
)
public MapperBatchExecutor mapperBatchExecutor(@Qualifier("batchSqlSessionTemplate") SqlSessionTemplate batchSqlSessionTemplate, MybatisProperties mybatisProperties) {
return new MapperBatchExecutor(batchSqlSessionTemplate, mybatisProperties.getBatchExecuteSize());
} @Bean(
name = {"pagingInterceptor"}
)
public PagingInterceptor pagingInterceptor() {
return new PagingInterceptor();
} private Resource getConfigLocation() {
return this.resourceResolver.getResource("classpath:mybatis/mybatis-config.xml");
} private Resource[] getMapperLocations(MybatisProperties mybatisProperties) throws IOException {
Resource[] mapperLocations = this.resourceResolver.getResources("classpath:com/guang/common/dao/mapper/*Mapper.xml");
String[] var3 = mybatisProperties.getMapperLocations();
int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) {
String mapperLocation = var3[var5];
Resource[] addedMapperLocations = this.resourceResolver.getResources(mapperLocation);
Resource[] originMapperLocations = mapperLocations;
mapperLocations = new Resource[mapperLocations.length + addedMapperLocations.length];
System.arraycopy(originMapperLocations, 0, mapperLocations, 0, originMapperLocations.length);
System.arraycopy(addedMapperLocations, 0, mapperLocations, originMapperLocations.length, addedMapperLocations.length);
} return mapperLocations;
}
}

1.4、SqlSessionTemplate

首先看一下体系结构图

实现了SqlSession接口,那么就有了SqlSession的功能。

说明,我们可以使用SqlsesseionTemplate对象来代替SqlSession对象来进行使用。

1.4.1、构造方法

看一下对应的构造方法,只有一个构造方法。也就是说,如果想要来设置SqlSessionTemplate成为bean,那么当前容器中必须要有一个SqlSessionFactory类型的Bean。

注意这里的参数信息:

  • 1、SqlSessionFactory是创建SqlSessionTemplate对象时候传入进来的,然后赋值给当前SqlSessionTemplate类中的属性sqlSessionFactory;
  • 2、ExecutorType执行器类型是从当前SqlSessionFactory获取得到的,然后赋值给当前SqlSessionTemplate类中的属性ExecutorType;
  • 3、sqlSessionProxy是sqlSession的代理对象,是利用JDK动态代理产生的对象;

1.4.2、增删改查方法

随便看一下都是类似如下所示,使用sqlSessionProxy来进行执行的

public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}

那么执行方法的时候,肯定会指定到InvocationHandler中的invoke方法中来。

那么来看一下SqlSessionInterceptor中的invoke方法。

1.4.3、SqlSessionInterceptor

从上面来看,可以知道是一个InvocationHandler,那么直接来看一下对应的invoke方法实现:

Spring整合Mybatis后为什么一级缓存失效

Spring-Mybatis提供的用来整合Spring和Mybatis的,所以又利用到了Spring中的事务中的内容,那么重点来研究一下这里的代码:

SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

首先要获取得到对应的SqlSession,SqlSession是mybatis中的门面。获取得到了SqlSession之后,才能够来执行对应的方法。

那么看一下如何获取得到SqlSession的。

首先看看是如何放入到当前线程上下文的。

这里有几个需要注意的点:

  • 1、mybatis中的数据源和@Transactional注解或者是TransactionTemplate使用的要是同一个数据源;
  • 2、一定要在开启事务的情况,获取的才是同一个SqlSession;没有开启事务,每次获取得到的SqlSession都是新创建的;

然后看下执行阶段,如下所示:

@Transactional
public void insert(){
userMapper.insert(new User());
orderMapper.insert(new Order());
}

看一下对应的执行

首先执行的时候判断

public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// 判断是否是同一个SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
return (holder != null) && (holder.getSqlSession() == session);
}

如果是同一个SqlSession,那么就先不提交!如果不是同一个SqlSession,那么一个方法执行完成之后,就执行对应的SqlSession.commit()方法,各自占用一个数据库连接。最终如果正常的执行的话,始终占用的是同一个数据库连接,在同一个数据库连接中来执行对应的方法。

Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。 但是在Spring整合Mybatis后,如果没有执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,没执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效,具体的底层执行流程在上图。

个人理解:实际上Spring整合Mybatis后一级缓存失效并不是问题,是正常的实现,因为,一个方法如果没有开启Spring事务,那么在执行sql时候,那就是每个sql单独一个事务来执行,也就是单独一个SqlSession对象来执行该sql,如果开启了Spring事务,那就是多个sql属于同一个事务,那自然就应该用一个SqlSession来执行这多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession了)。

sqlsession的提交和回滚在哪里

对于SqlSession来说,最终的commit和rollback方法,都会调用到connection.commit()和connection.rollback()方法上去。

而在Spring接管了事务之后,在Spring控制的事务中,也会在方法提交或者回滚之后释放数据库连接。

流程

Spring整合Mybatis之后SQL执行流程: Spring整合Mybatis之后SQL执行流程 | ProcessOn免费在线作图,在线流程图,在线思维导图 |

三、总结

由很多框架都需要和Spring进行整合,而整合的核心思想就是把其他框架所产生的对象放到Spring容器中,让其成为Bean。

比如Mybatis,Mybatis框架可以单独使用,而单独使用Mybatis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合Spring就是为了将这些对象放入Spring容器中成为Bean,只要成为了Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架所提供的功能了。

Mybatis-Spring 新版本底层源码执行流程

1、通过@MapperScan导入了MapperScannerRegistrar类;

2、MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法;

3、在registerBeanDefinitions方法中定义了一个ClassPathMapperScanner对象,用来扫描mapper,设置ClassPathMapperScanner对象可以扫描到接口,因为在Spring中是不会扫描接口的

4、同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component

通过利用Spring的扫描后,会把接口扫描出来并且得到对应的BeanDefinition

5、接下来把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType

6、扫描完成后,Spring就会基于BeanDefinition去创建Bean了,相当于每个Mapper对应一个FactoryBean

7、在MapperFactoryBean中的getObject方法中,调用了getSqlSession()去得到一个sqlSession对象,然后根据对应的Mapper接口生成一个Mapper接口代理对象,这个代理对象就成为Spring容器中的Bean

8、sqlSession对象是Mybatis中的,一个sqlSession对象需要SqlSessionFactory来产生

9、MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要存在SqlSessionFactory类型的bean和SqlSessionTemplate类型的bean。(注意要注册两个的话,记得要在SIMPLESQLSESSIONTEMPLATE上加上@Primary注解)

10、如果你定义的是一个SqlSessionFactory类型的bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性

11、而在SqlSessionTemplate类中就存在一个getMapper方法,这个方法中就产生一个Mapper接口代理对象

到时候,当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程,详细的请看下图

Spring整合Mybatis之后SQL执行流程: Spring整合Mybatis之后SQL执行流程 | ProcessOn免费在线作图,在线流程图,在线思维导图 |

有点遗漏的步骤,我在这里补充一下:

带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:

@Bean
public static MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.luban");
return mapperScannerConfigurer;
}

最新文章

  1. iOS计算字符串的宽度高度
  2. 比较好用的php函数
  3. 直接拿来用!最火的Android开源项目
  4. 【推荐】开放静态文件 CDN服务staticfile.org
  5. [开发笔记]-使用jquery获取url及url参数的方法
  6. 锋利的jQuery第2版学习笔记4、5章
  7. ubuntu启用root用户
  8. Boxes - SGU 126(找规律)
  9. Delphi 动态改变Rzsplitter的Orientation(方向)属性
  10. http请求头响应头大全
  11. 帝国cms让当前栏目显示不同样式(图文)
  12. mysqldumpslow的使用简介
  13. POJ 2007 Scrambled Polygon [凸包 极角排序]
  14. C#多线程编程(7)--锁
  15. python爬虫学习笔记
  16. SSH 连接慢
  17. 六、pyqt5对话框——QInputDialog、QColorDialog、QFontDialog、QMessageBox、QFileDialog
  18. django视图函数及快捷方式
  19. 实施CMMI3的体会
  20. Annotation之三:自定义注解示例,利用反射进行解析

热门文章

  1. 《HTTP权威指南》– 2.HTTP报文与URL资源
  2. 如何在路由绑定中使用 IParsable
  3. XSS漏洞利用案例实验
  4. Spring Boot通过Actuator显示git和build的信息
  5. Pollard_Rho算法
  6. 深入理解 OpenMP 线程同步机制
  7. Loj 507 接竹竿 题解
  8. 实操好用~~~~~antd 中 Table表格动态合并~~~
  9. 标准if-else语句-扩展if-else语句
  10. 【分析笔记】SiliconLabs EFR32BG22 Bluetooth Mesh SensorClient 源码分析