- 吃透Mybatis源码-Mybatis初始化(一)
- 吃透Mybatis源码-Mybatis执行流程(二)`
- 吃透Mybatis源码-缓存的理解(三)
- 吃透Mybatis源码-通过分析Pagehelper源码来理解Mybatis的拦截器(四)
- 吃透Mybatis源码-面试官问我mapper映射器是如何工作的(五)
- 吃透Mybatis源码-面试官问我Spring是怎么整合Mybatis的(六)
前言
我们在项目中都是使用Spring整合Mybatis进行数据操作,而不会直接使用SqlSession去操作数据库,因为这样操作会显得特别的麻烦。Spring整合Mybatis之后,Spring对Mybatis的核心进行了封装和适配,让我们用起来更加简单。本篇文章将带你了解Spring整合Mybatis的核心原理,从此再也不用担心面试官问我“Spring是如何整合Mybatis的?”
Spring整合Mybatis案例
第一步,导入需要的依赖包括:驱动包,mybatis包;Spring包;测试包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
第二步:编写实体类,mapper映射器,mapper.xml 文件 ,这里省略
第三步:编写数据库配置文件
#mysql
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/test?useSSL=false
mysql.username=root
mysql.password=admin
第四步:编写Spring核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!--1 引入属性文件,在配置中占位使用 -->
<context:property-placeholder location="classpath*:db.properties" />
<!--2 配置数据源 -->
<bean id="datasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
<!--驱动类名 -->
<property name="driver" value="${mysql.driver}" />
<!-- url -->
<property name="url" value="${mysql.url}" />
<!-- 用户名 -->
<property name="username" value="${mysql.username}" />
<!-- 密码 -->
<property name="password" value="${mysql.password}" />
</bean>
<!--3 会话工厂bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="datasource"></property>
<!-- 别名 -->
<property name="typeAliasesPackage" value="cn.whale.domian"></property>
<!-- sql映射文件路径 -->
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"></property>
<!-- <property name="configLocation" value="classpath*:mybatis-config.xml"></property>-->
<!-- <property name="plugins"></property>-->
</bean>
<!--4 自动扫描对象关系映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定要自动扫描接口的基础包,实现接口 -->
<property name="basePackage" value="cn.whale.mapper"></property>
</bean>
<!--5 声明式事务管理 -->
<!--定义事物管理器,由spring管理事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--支持注解驱动的事务管理,指定事务管理器 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
第五步:编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
//注入Mapper接口
@Autowired
private StudentMapper studentMapper ;
@Test
public void test(){
Student student = studentMapper.selectById(1L);
System.out.println(student);
}
}
Spring整合Mybatis和单纯使用Mybatis做了哪些改变?
- 除了增加了Spring的包以外,增加了一个比较关键的依赖mybatis-spring , 它是Spring整合Mybatis核心的一个包
- SqlSessionFactory不在手动创建,而是在Spirng的配置文件中通过注册一个SqlSessionFactoryBean来创建
- 以前mybatis-config.xml中配置的DataSource 数据源 和 事务管理 现在在Spring的配置文件中配置了
Spring只是对Mybatis做了整合或者包装,简单点理解就是整合Spring之后只是改变了对Mybatis的配置方式和使用方式,其本质还是使用的是Mybatis的核心,那么我们思考一下,之前在使用Mybatis的时候有几个核心的东西
- SqlSessionFactory的创建
- SqlSession的创建
- MapperPorxy的创建
- 事务的管理
那么理解Spring是如何整合Mybatis的就是理解上面的几个核心在Spring中是如何创建和工作的。
SqlSessionFactory的创建
在之前的Mybatis案例中我们是通过 SqlSessionFactoryBuilder.buider(inputStream)
来加载Mybatis配置文件和创建SqlSessionFactory的,在Spring中不一样了,我们在Spring的配置文件中 配置了一个Bean SqlSessionFactoryBean
,如下
<!--3 会话工厂bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="datasource"></property>
<!-- 别名 -->
<property name="typeAliasesPackage" value="cn.whale.domian"></property>
<!-- sql映射文件路径 -->
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"></property>
<!-- mybatis配置文件-->
<!-- <property name="configLocation" value="classpath*:mybatis-config.xml"></property>-->
<!-- 插件配置-->
<!-- <property name="plugins"></property>-->
</bean>
那么我们源码分析的入口就是它了,下面是SqlSessionFactoryBean的继承体系图
首先SqlSessionFactoryBean是一个FactoryBean的子类,它提供了一个 getObject
来返回SqlSessionFactory
的实例,这个是Spring创建Bean的一种方式,相信使用过Spring的童靴都清楚。
SqlSessionFactoryBean实现类一个InitializingBean
接口,该接口提供了一个 afterPropertiesSet
方法,当Bean在初始化的时候会触发afterPropertiesSet方法调用。也就是说 SqlSessionFactoryBean在Bean初始化的过程中会调用其afterPropertiesSet
方法。下面是SqlSessionFactoryBean的源码
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
//mybatis-config.xml 配置文件
private Resource configLocation;
//Mybatis的configuration对象
private Configuration configuration;
//mapper.xml 映射文件
private Resource[] mapperLocations;
//数据源
private DataSource dataSource;
//mybatis的事务工厂
private TransactionFactory transactionFactory;
//配置sqlSessionFactory Properties
private Properties configurationProperties;
//mybatis的SqlSessionFactory的构造器
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//mybatis的SqlSession工厂
private SqlSessionFactory sqlSessionFactory;
//EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();
...省略...
//初始化SqlSessionFactoryBean
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");
//构建 SqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
...省略...
//获取对象,通过FactroyBean来实例化
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//如果sqlSessionFactory 是空的就触发afterPropertiesSet方法
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
SqlSessionFactoryBean 是Mybatis-Spring这个jar包提供的。里面使用到了Mybatis的一些核心类,比如:Configuration , SqlSessionFactory。 在 afterPropertiesSet 方法中 调用 buildSqlSessionFactory() 去buid sqlSessionFactory 。
你可能会问,afterPropertiesSet 和 getObject 这两个方法又什么关系呢?谁先执行呢? afterPropertiesSet 是对 SqlSessionFactoryBean的初始化 ,而getObject 方法是用来创建 SqlSessionFactory 的。这里其实有有俩个Bean,一个是 SqlSessionFactoryBean ,一个是 SqlSessionFactory ,所以是先创建SqlSessionFactoryBean,然后执行afterPropertiesSet 初始化,最后调用getObject 来创建SqlSessionFactory 。所以是afterPropertiesSet 先执行。
下面是org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 的源码
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//Mybatis的配置类
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
//1.判断是否有配置configLocation,如果有就创建一个XMLConfigBuilder来解析mybatis-config.xml配置文件
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
//如果this.objectFactory不为空,就把this.objectFactory设置给 targetConfiguration
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
//2.处理别名
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
//别名还是注册到Configuration#typeAliasRegistry 中
targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
});
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
//3.处理插件 ,还是通过 Configuration.addInterceptor 来注册插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
//4.处理插件 ,处理typeHandlersPackage,还是注册到Configuration#TypeHandlerRegistry
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeHandlersPackageArray).forEach(packageToScan -> {
targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
});
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (this.databaseIdProvider != null) {
//fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
//5.处理缓存
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
//6.如果有配置configLocaltion ,开始解析xmlConfigBuilder,解析mybtis-config.xml
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
//7.创建 Environment 把 SpringManagedTransactionFactory 和 dataSource保存进去
//如果有指定transactionFactory 就使用指定的,否则就使用SpringManagedTransactionFactory
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
//8.如果mapperLocations不为空,就解析mapper.xml
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//创建XMLMapperBuilder解析器
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
//开始解析mapper.xml
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
}
//9.执行sqlSessionFactoryBuilder.build来创建SqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
这个方法有点长,但是看起来还是比较轻松,在其中我们看到了很多之前看过的代码,总结一下
- 如果在xml中定义SqlSessionFactoryBean的时候有指定 configLocation 属性如:
<property name="configLocation" value="classpath*:mybatis-config.xml">
,就会创建一个 XMLConfigBuilder 来解析mybatis-config.xml配置文件。 - 然后判断了
objectFactory
和objectWrapperFactory,如果有不为空会加入到targetConfiguration中 - 如果配置了
typeAliasesPackage
,就会扫描对应的包,把相关的类加入Configurationd TypeAliasRegistry中 - 然后判断是否配置了pluns,如果有就解析并注册到Configuration的
InterceptorChain
中 - 如果就配置TypeHandlerRegistry ,就注册到Configuration的
TypeHandlerRegistry
中 - 如果有没有配置 transactionFactory 就会使用Spring的
SpringManagedTransactionFactory
事务工厂,和dataSource
一起封装到environment对象中,把Environment设置给Configuration
中 - 如果配置了 mapperLocations 属性,就创建一个 XMLMapperBuilder 来解析mapper.xml
- 最后通过
sqlSessionFactoryBuilder.build
来构建sqlSessionFactory,默认实现是DefaultSqlSessionFactory
好吧看到上面的这些步骤你应该很熟悉的,在之间分析Mybatis的时候都有分析到上面这些类。具体的每个类的注册步骤就不去看了,因为之间是看过的,这里重点给搭建看一下 SpringManagedTransactionFactory 事务工厂。源码如下
public class SpringManagedTransactionFactory implements TransactionFactory {
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
SpringManagedTransactionFactory 实现了TransactionFactory 接口,复写了newTransaction方法,该方法是用来创建Transaction 事务对象的,它使用的是SpringManagedTransaction作为事务对象,下面是SpringManagedTransaction的源码
public class SpringManagedTransaction implements Transaction {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
//数据源
private final DataSource dataSource;
//JDBC的链接对象
private Connection connection;
private boolean isConnectionTransactional;
//自动提交事务
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
/**
* {@inheritDoc}
*/
//获取连接
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
//打开connection
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() ->
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
/**
* {@inheritDoc}
*/
@Override
//提交事务,使用的是connection.commit
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
this.connection.commit();
}
}
/**
* {@inheritDoc}
*/
@Override
//回滚事务
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
this.connection.rollback();
}
}
/**
* {@inheritDoc}
*/
@Override
//关闭链接
public void close() {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Integer getTimeout() {
ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (holder != null && holder.hasTimeout()) {
return holder.getTimeToLiveInSeconds();
}
return null;
}
}
SpringManagedTransaction对事务的操作还是使用的是JDBC的connection 事务方法 来完成的,Spring只是做了一个封装而已。
MapperScannerConfigurer Mapper扫描
在Spring配置文件中我们除了要配置SqlSesionFactoryBean而外还配置了一个 MapperScannerConfigurer
<!--4 自动扫描mapper映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定要自动扫描接口的基础包,实现接口 -->
<property name="basePackage" value="cn.whale.mapper"></property>
</bean>
MapperScannerConfigurer的作用是实现Mapper映射器的自动扫描,先看一下这个类的继承体系
首先 MapperScannerConfigurer 实现了InitializingBean复写了afterPropertiesSet方法,在该方法中只是对basePackage属性做了断言,如果basePackage为空则会抛出异常
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
其次MapperScannerConfigurer 实现BeanDefinitionRegistryPostProcessor
,而BeanDefinitionRegistryPostProcessor 又继承了 BeanFactoryPostProcessor
接口,该接口是一个BeanFactory的后置处理器,它提供了一个 postProcessBeanFactory方法。该方法会在BeanFactory初始化之后被调用
,这时所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建,我们可以通过该方法来定制和修改BeanFactory的内容,如覆盖或添加属性。
BeanDefinitionRegistryPostProcessor
是对BeanFactoryPostProcessor的扩展,它提供了一个 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry
registry) 方法,该方法是在Spring注册完BeanDefinition,实例化Bean之前被调用,它允许我们使用BeanDefinitionRegistry对我们自定义Bean进行注册。
这里我要稍微解释一下什么是BeanDefinition ,在Spring启动的时候,我们在Spring的配置文件中配置的<bean />
都会被Spring封装成BeanDefinition,注册到IOC容器中。然后Spring会对单利的Bean进行实例化,也就是找到单利的非惰性加载的Bean,根据它的BeanDefinition来创建真正的实例
。
下面是MapperScannerConfigurer #postProcessBeanDefinitionRegistry方法的源码
@Override
//通过 BeanDefinitionRegistry 可以往Spring容器中注册Bean
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//Mapper扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
//扫描Mapper
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
该方法中创建了一个 ClassPathMapperScanner ,扫描Mapper接口是通过ClassPathMapperScanner #Scan方法完成的
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//扫描指定的包路径下的mapper
this.doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//[重要] 1.调用父类扫描指定包下的Mapper,然后封装成BeanDefinition通过 BeanDefinitionRegistry注册到Spring容器中
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 {
//【重要】2.处理Mapper的BeanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
ClassPathMapperScanner #scan方法首先是调用父类去扫描 basePackages 下的Mapper接口,然后会封装成 BeanDefinition ,通过BeanDefinitionRegistry注册到Spring容器中 。
紧接着就是调用org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions对Mapper的BeanDefinition做处理
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + beanClassName + "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//【重要】bean 的实际类是 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
...省略...
注意注意,这行代码:definition.setBeanClass(this.mapperFactoryBean.getClass());
,它把我们的Mapper接口的class修改为MapperFactoryBean 。也就是说Mapper接口实例创建是通过 FactoryBean 的方式来完成的。为什么这么做,我们待会儿来解释。
这里稍作总结:首先 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor ,在Spring注册完Bean之后,就会触发MapperScannerConfigurer #postProcessBeanDefinitionRegistry
方法的执行,在该方法中通过 ClassPathMapperScanner #scan
来扫描 指定的 basePackages
包下的Mapper接口,然后封装成 BeanDefinition
注册到IOC的容器中。
接着会把Mapper的BeanDefinition的class指定为MapperFactoryBean 。也就是说Mapper是通过MapperFactoryBean来创建的。
上面一系列流程只是扫描和注册了Mapper接口,封装成MapperFactoryBean,紧接着Spring就会通过MapperFactoryBean 来创建Mapper的实例。下面是MapperFactoryBean 的源码
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
//还是通过sqlSessionl.getMapper("mapper接口的class")
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
//注意,这里不是使用的是DefaultSqlSession,而是使用sqlSessionTemplate
return this.sqlSessionTemplate;
}
...省略...
这个类里面透露出2个重要的信息
- MapperFactoryBean实现了FactoryBean,提供了getObject方法来返回Mapper的实例,使用的还是
getSqlSession().getMapper(this.mapperInterface)
来创建Mapper的代理。但是它用的是sqlSessionTemplate的getMapper方法。 - MapperFactoryBean继承了SqlSessionDaoSupport
SqlSessionDaoSupport 是什么?下面是 org.mybatis.spring.support.SqlSessionDaoSupport的源码
public abstract class SqlSessionDaoSupport extends DaoSupport {
//Spring提供的SqlSesion
private SqlSessionTemplate sqlSessionTemplate;
/**
* Set MyBatis SqlSessionFactory to be used by this DAO.
* Will automatically create SqlSessionTemplate for the given SqlSessionFactory.
*
* @param sqlSessionFactory a factory of SqlSession
*/
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
//创建SqlSessionTemplate
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
//创建SqlSessionTemplate
return new SqlSessionTemplate(sqlSessionFactory);
}
SqlSessionDaoSupport是mybatis-spring提供的一个Dao支持,其中包含了一个很关键的东西 SqlSessionTemplate,注意:在整合了Spring之后不在使用SqlSesion了,而是使用SqlSessionTemplate
,这是为什么,我们后面会说到。我们重点看一下 setSqlSessionFactory 方法,在该方法中创建了SqlSessionTemplate,下面是SqlSessionTemplate
的构造器 org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate , 最终会通过该构造器来创建SqlSessionTemplate
/**
线程安全的交给Spring管理的, SqlSession与 Spring 事务管理一起工作,以确保实际使用的 SqlSession 是与当前 Spring 事务相关联的。
此外,它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。
* Thread safe, Spring managed, {@code SqlSession} that works with Spring
* transaction management to ensure that that the actual SqlSession used is the
* one associated with the current Spring transaction. In addition, it manages
* the session life-cycle, including closing, committing or rolling back the
* session as necessary based on the Spring transaction configuration.
* <p>
* The template needs a SqlSessionFactory to create SqlSessions, passed as a
* constructor argument. It also can be constructed indicating the executor type
* to be used, if not, the default executor type, defined in the session factory
* will be used.
* <p>
* This template converts MyBatis PersistenceExceptions into unchecked
* DataAccessExceptions, using, by default, a {@code MyBatisExceptionTranslator}.
* <p>
因为 SqlSessionTemplate 是线程安全的,所以单个实例可以被所有 DAO 共享;
这样做还应该节省少量内存。 这种模式可以在 Spring 配置文件中使用,如下所示:
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory" />
</bean>
* Because SqlSessionTemplate is thread safe, a single instance can be shared
* by all DAOs; there should also be a small memory savings by doing this. This
* pattern can be used in Spring configuration files as follows:
*
* <pre class="code">
* {@code
* <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
* <constructor-arg ref="sqlSessionFactory" />
* </bean>
* }
* </pre>
*
* @author Putthiphong Boonphong
* @author Hunter Presnall
* @author Eduardo Macarron
*
* @see SqlSessionFactory
* @see MyBatisExceptionTranslator
*/
public class SqlSessionTemplate implements SqlSession, DisposableBean {
//SqlSession工厂
private final SqlSessionFactory sqlSessionFactory;
//执行器类型
private final ExecutorType executorType;
//SqlSession代理对象
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//使用Proxy.newProxyInstance 为SqlSession创建代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] {
SqlSession.class },
new SqlSessionInterceptor());
}
在SqlSessionTemplate的构造器中使用JDK动态代理为SqlSession创建了一个代理类
,SqlSessionInterceptor作为SqlSession的拦截器它
实现了InvocationHandler
接口,复写了invoke方法,当SqlSessionTemplate的方法被调用就会触发SqlSessionInterceptor#invoke的执行(JDK动态代理)。
这里我特意把 SqlSessionTemplate 的注释截出来,上面有这样一句话:“SqlSessionTemplate” 是Spring管理的线程安全
的SqlSession,它与 Spring 事务管理一起工作
,以确保实际使用的 SqlSession 是与当前 Spring 事务相关联的。 此外,它还管理会话生命周期
,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。
也就是说在Spirng整合了Mybatis之后,不是直接使用SqlSession,而是使用SqlSessionTemplate
来管理SqlSession的生命周期,它是线程安全的,并且和事务一起工作的。那是不是说我们之前在Mybatis中使用的SqlSession的默认实现DefaultSqlSession 是线程不安全的
呢?请看:
/**
* DefaultSqlSession 实现于 SqlSession 这个类是线程不安全的
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
果然在DefaultSqlSession 中有解释,DefaultSqlSession 它并不是一个线程安全的类。那么我们就要思考了,如果只是在Mybatis的开发中,我们每次请求都需要创建一个DefaultSqlSession ,这没什么问题,但是在Spring中只有一个SqlSessionTemplate,也就是说它是单利的,那么它怎么保证线程安全
?别急慢慢往下看。
总结一下:在整合Mybatis的时候,我们需要定义一个,MapperScannerConfigurer,指定一个basePackage扫描包实现Mapper的自动扫描, MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor一个BeanFactory后置处理器
,在Spring启动的过程中会触发postProcessBeanDefinitionRegistry方法的执行。该方法会扫描basePackages包下的Mpaper,然后把Mapper封装成一个一个的MapperFactoryBean
。也就是说Mapper是通过MapperFactoryBen来创建的。
MapperFactoryBean继承了SqlSessionDaoSupport 其中提供了一个SqlSessionTemplate
来管理SqlSesion,而在SqlSessionTemplate中是通过动态代理来创建的SqlSesion
。也就是说在SqlSessionTemplate中管理了SqlSesion的代理类。
因为我们SqlSesson的默认实现DefaultSqlSession是线程不安全的,所以Spring使用了SqlSessionTemplate一个线程安全的类来管理DefaultSqlSession
,那么它是怎么做的呢。
通过SqlSessionTemplate管理SqlSession
下面是SqlSessionInterceptor的源代码,因为用到了JDK动态代理,当SqlSessionTemplate的方法被调用(比如:studentMapper.selectById就会触发SqlSessionTemplate)就会触发SqlSessionInterceptor#invoke的执行它是SqlSessionTemplate中的一个内部类,见:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke 源码如下
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.创建一个 SqlSession,默认实现是DefaultSqlSession,底层一样是通过 SqlSessionFactory#openSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//2.执行方法,比如:DefaultSqlSession#selectOne
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
//3.提交
sqlSession.commit(true);
}
//返回结果
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
//4.关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
这里做了四个事情
- 调用getSqlSession方法得到SqlSession,底层一样会通过SqlSessionFactory#openSession,默认还是得到的DefaultSqlSession
- method.invoke 执行DefaultSqlSession的方法
- sqlSession.commit 提交事务
- closeSqlSession关闭资源
这里重点看getSqlSession方法是如何创建SqlSession的,源码如下 见:org.mybatis.spring.SqlSessionUtils#getSqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
//空值判断
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//从 TransactionSynchronizationManager 事务同步管理器 中拿到SqlSession的助手类
//其实这里是从缓存中去那SqlSessionHolder ,该holder中有SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//拿到SqlSession
SqlSession session = sessionHolder(executorType, holder);
//如果缓存中有SqlSession,就直接返回了
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//如果缓存中没有 sqlSession 为空,就通过sessionFactory创建一个
session = sessionFactory.openSession(executorType);
//这里会把SqlSession交给SqlSessionHolder ,然后缓存到 TransactionSynchronizationManager中的一个 ThreadLocal<Map<Object, Object>> resources中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
上面是从TransactionSynchronizationManager中去拿SqlSession
,我们可以认为是从缓存中获取,如果没有的话就会使用 sessionFactory.openSession(executorType)
创建一个新的,然后把SqlSession缓存到 TransactionSynchronizationManager
。我们看一下 TransactionSynchronizationManager.getResource的源码
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
//重点在这,通过一个ThreadLocal来缓存
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
public static Object getResource(Object key) {
//拿到key
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
//拿到selSessionHolder
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
private static Object doGetResource(Object actualKey) {
//从Resources(一个ThreadLocal)中获取Map
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
//拿到SelSessionHolder ,selSessionHolder里面是真正的SqlSession
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
//缓存SqlSession
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
...省略了部分代码...
//把SqlSession封装到SqlSessionHolder
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//重点在这,缓存SqlSessionHalder
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
...省略了部分代码...
}
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
//拿到ThreadLocal中的map
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
map = new HashMap();
resources.set(map);
}
//最终把SqlSession 是缓存到了ThreadLocal中
Object oldValue = ((Map)map).put(actualKey, value);
真相大白了,在 TransactionSynchronizationManager
中有个 ThreadLocal<Map<Object, Object>> resources
我们的SqlSession就是被封装成SelSessionHolder 然后缓存到该ThreadLocal中,所以回答上面那个问题,SqlSessionTemplate是如何保证线程安全?答案是:通过一个ThreadLocal来缓存SqlSesion来保证线程安全,如果ThreadLocal没有SqlSession就会通过SqlSessionFactory#openSession创建一个,再缓存到ThreadLocal中。 这一切的动作都是交给SqlSessionTemplate来管理的。所以它是线程安全的
。
加餐MapperScan
我们在注册mapper的时候除了在xml中可以使用 MapperScannerConfigurer 方式,也可以使用注解 @MapperScan(basePackages = "cn.whale.mapper")去指定。那这两种方式有什么区别呢?我们来看一下MapperScan的源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise
* annotation declarations e.g.:
* {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
* @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
*/
String[] value() default {
};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces
* with at least one method will be registered; concrete classes will be
* ignored.
*/
String[] basePackages() default {
};
在MapperScan上有个 @Import(MapperScannerRegistrar.class),看名字大概意思是 MappersScanner的注册器,源码如下
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
//资源加载器
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//拿到MapperScan注解的属性
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//mapper扫描器,专门用来扫描Mapper接口
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
...省略部分代码...
List<String> basePackages = new ArrayList<String>();
//拿到MapperScan注解的value属性,即:basePackage ,如:cn.whale.mapper
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
//调用ClassPathMapperScanner扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//扫描basePackages包下的mapper,封装成BeanDefinition
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编程MapperFactoryBean , 关联sqlSessionFactory,关联sqlSessionFactory,sqlSessionTemplate等属性。
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
好吧看到这我们就不用往下看了,因为和MapperScannerConfigurer做的事情是一样的呢,所以MapperScan注解方式使用的是MapperScannerRegistrar 来注册Mapper,它也实现了ImportBeanDefinitionRegistrar,它和MapperScannerConfigurer一样都是用到了ClassPathMapperScanner 扫描Mapper。
总结
SqlSessionFactory的创建
在Sping中通过SqlSessionFactoryBean
来创建SqlSessionFactory,它实现了InitializingBean
,在Bean初始化的时候会触发afterPropertiesSet 方法去构建sqlSessionFactory
在SqlSessionFactoryBean中会涉及到configLocation(Mybatis核心配置文件,Spring中往往不用配置);mapperLocations(mapper.xml);typeAliasesPackage(别名),Transaction,DataSource等的解析和注册。底层也是走mybatis的解析流程,最终也会形成一个全局的configuration。 解析完之后就会创建一个DefaultSqlSessionFactory。
Mapper代理的创建
在整合Mybatis的时候,我们需要定义一个,MapperScannerConfigurer并指定一个basePackage属性来实现Mapper的自动扫描, MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor一个BeanFactory后置处理器
,在Spring启动的过程中会触发postProcessBeanDefinitionRegistry方法的执行。该方法会扫描basePackages包下的Mpaper,然后把Mapper封装成一个一个的MapperFactoryBean
。也就是说Mapper是通过MapperFactoryBen来创建的。
在MapperFactoryBean是一个FactoryBean,在它的getObject方法中,使用了sqlSessionTemplate.getMapper
方法来创建Mapper的代理。底层和之前分析mybatis的时候是一样,是通过MapperProxyFactory#newInstance方法通过Proxy.newProxyInstance来为mapper生成的代理类。
SqlSession的管理
MapperFactoryBean继承了SqlSessionDaoSupport 其中提供了一个SqlSessionTemplate
,SqlSessionTemplate是线程安全的,在SqlSessionTemplate中通过动态代理来创建的SqlSesion
。不直接使用DeafultSqlSession
是因为它并不是线程安全
的。
mapper的执行
在我们执行mapper对数据库操作的时候,请求会调用MapperProxy
去执行(Mapper被动态代理),MapperProxy中会调用SqlSesion去执行。在Spring中是通过SqlSessionTemplate通过动态代理来创建的SqlSession
,所以请求会来到SqlSessionTemplate的一个内部类SqlSessionInterceptor
(SqlSession被动态代理)在其invoke方法中,会先去TransactionSynchronizationManager中的threadLocal获取
真正的SqlSession,如果没有就会使用SqlSessionFactory创建一个DefaultSqlSession,然后缓存到TransactionSynchronizationManager中的threadLocal中
。以此来保证线程安全
。同时SqlSessionTemplate还关联了事务
的操作。