Note: The source code analysis of this series is based on mybatis-spring 2.0.6, the corresponding version of mybatis is 3.5.6, and the address of the gitee warehouse of the source code is: funcy/mybatis-spring .
Regarding mybatis
, most of the time we spring
use it in combination with the container. This article will analyze the integration process of this mybatis
and the container.spring
1. Obtain the source code and preparedemo
1.1 mybatis
and spring
integration project and source code
mybatis
The spring
integrated project is called mybatis-spring
, and the source code is hosted on github. Of course, I also pulled it to my own gitee to create it. The address is: funcy/mybatis . About the source code checkout
and pull
operation, it is not the focus of this article, so I won't say more.
mybatis
The version analyzed earlier is 3.5.6
, the version we analyze here is, and the mybatis-spring
version 2.0.6
of mybatis it refers to is exactly 3.5.6
.
1.2 demo preparation
After getting the source code, you can then create an example demo. We src/test
will create it below. The entire directory is as follows:
Code directory:
mapper.xml file:
- prepare
model
/mapper
/service
/**
* User.java
*/
public class User implements Serializable {
/**
* 用户id
*/
private Long id;
/**
* 登录名
*/
private String loginName;
/**
* 用户昵称
*/
private String nick;
... 省略set/get方法
}
/**
* UserMapper.java
*/
public interface UserMapper {
/**
* 查询
*
* @return
*/
List<User> selectList(@Param("id") Long id, @Param("limit") Integer limit);
/**
* 更新
*
* @param id
* @param nick
* @return
*/
int updateUser(@Param("id") Long id, @Param("nick") String nick);
}
/**
* UserService.java
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 获取列表
*
* @param id
* @param limit
* @return
*/
public List<User> getUser(Long id, Integer limit) {
return userMapper.selectList(id, limit);
}
}
复制代码
UserService.java
Managed by spring and injected by spring userMapper
.
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.spring.demo.mapper.UserMapper">
<select id="selectList" resultType="org.mybatis.spring.demo.model.User">
select id, login_name as loginName, nick from user
<trim prefix="where" prefixOverrides="AND |OR">
<if test="id != null">
and id = #{id}
</if>
</trim>
<if test="limit != null">
limit #{limit}
</if>
</select>
<update id="updateUser">
update user set nick = #{nick} where id = #{id}
</update>
</mapper>
复制代码
- Prepare the configuration class
@Configuration
public class Demo01Config {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置数据源
factoryBean.setDataSource(dataSource());
// mapper.xml 文件路径
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/**/*.xml");
factoryBean.setMapperLocations(resources);
return factoryBean.getObject();
}
@Bean
public DataSource dataSource() throws Exception {
Driver driver = new com.mysql.cj.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "123";
return new SimpleDriverDataSource(driver, url, username, password);
}
}
复制代码
- prepare main class
Finally the main class:
@ComponentScan
@MapperScan("org.mybatis.spring.demo.mapper")
public class Demo01Main {
public static void main(String[] args) {
ApplicationContext application
= new AnnotationConfigApplicationContext(Demo01Main.class);
// 获取 userService 对象
UserService userService = (UserService) application.getBean("userService");
List<User> list = userService.getUser(3L, 10);
System.out.println(list);
}
}
复制代码
Run, the results are as follows:
[User{id=3, loginName='test', nick='test'}]
复制代码
It can be seen that the integration of mybatis and spring is successful, and then we will analyze the mybatis-spring
project according to this demo.
2. SqlSessionFactoryBean
Class
我们先来看看Demo01Config
引入的Bean
:
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置数据源
factoryBean.setDataSource(dataSource());
// mapper.xml 文件路径
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/**/*.xml");
factoryBean.setMapperLocations(resources);
return factoryBean.getObject();
}
复制代码
这个方法中,先是创建了SqlSessionFactoryBean
,然后配置了数据源以及mapper.xml
文件路径,然后调用SqlSessionFactory#getObject()
方法获取SqlSessionFactory
,看来SqlSessionFactoryBean
还是挺关键的,我们看看它的定义:
/**
* 实现了 FactoryBean
*/
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>,
InitializingBean, ApplicationListener<ApplicationEvent> {
...
}
复制代码
可以看到,SqlSessionFactoryBean
实现了FactoryBean
,其中类为SqlSessionFactory
,这样调用SqlSessionFactory#getObject()
方法获取SqlSessionFactory
了。
FactoryBean
是spring中一类重要的bean
,它可以将普通的类包装成FactoryBean
注册到spring容器中,要获取该类的bean时,只需调用SqlSessionFactory#getObject()
方法即可。
SqlSessionFactory
是mybatis
中的重要类,用来生成SqlSession
,而SqlSession
正是用来获取XxxMapper
实例、执行数据库操作的类。
SqlSessionFactoryBean
可以定义很多很多的配置,它的setXxx
方法如下:
实际上,在mybatis-config.xml
配置文件中配置的内容都可以在这里找到对应的setXxx
方法,示例demo中,只是设置了数据源与mapper.xml
的文件路径。
了解了SqlSessionFactoryBean
的相关功能后,我们来看看SqlSessionFactoryBean#getObject
方法:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 主动调用 afterPropertiesSet()
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* 重写了 InitializingBean 的方法
*/
public void afterPropertiesSet() throws Exception {
...
// 这里构建了 sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
复制代码
我们继续进入buildSqlSessionFactory()
方法:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
// 这里省略了好多好多的代码,都是处理 targetConfiguration 的配置
...
// 处理数据源
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory()
: this.transactionFactory, this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "...");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
mapperLocation.getInputStream(), targetConfiguration,
mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 在这里解析 mapper.xml 文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException(...);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
复制代码
首先,这个方法非常长,以上代码是精简后的代码,省略的是大段大段的设置targetConfiguration
属性的代码,剩下的是两个功能:
- 设置数据源
- 解析xml
这两个属性也是我们在sqlSessionFactory()
中设置的。
处理完sqlSessionFactory()
方法后,sqlSessionFactory
就注册到spring容器中了。
3. @MapperScan
扫描流程
sqlSessionFactory
注册到spring容器并不能实现MapperInterface
的自动注入,像这样:
@Autowired
private UserMapper userMapper;
复制代码
这个功能的实现,就是@MapperScan
的功劳了。
在Demo01Main.java
中,我们是这样配置@MapperScan
注解的:
@ComponentScan
@MapperScan("org.mybatis.spring.demo.mapper")
public class Demo01Main {
...
}
复制代码
这里的org.mybatis.spring.demo.mapper
就是MapperInterface
的包路径了,也是mybatis
要扫描的包路径.
我们进入@MapperScan
:
...
// @Import 里的类是关键
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}
复制代码
在MapperScan
中,有一个@Import
标签,它引入了一个类:MapperScannerRegistrar
,我们来看看它做了啥。
3.1 MapperScannerRegistrar
MapperScannerRegistrar
的代码如下:
/**
* 实现了 ImportBeanDefinitionRegistrar类
*/
public class MapperScannerRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
...
/**
* 来自于 ImportBeanDefinitionRegistrar 的方法,可以往spring容器中注册beanDefinition
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 获取 MapperScan 的属性值
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 注册mapper接口类
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
...
}
复制代码
MapperScannerRegistrar
实现了ImportBeanDefinitionRegistrar
,在其registerBeanDefinitions
中向spring容器注入了某些beanDefinition
,我们继续进入registerBeanDefinitions(...)
方法,看看注入了啥:
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// 创建 BeanDefinitionBuilder,构建 MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 以下为解析 @MapperScan 的各种属性,并它他们设置到 builder 中
...
// 解析扫描的包路径
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText).collect(Collectors.toList()));
...
// 添加属性值
builder.addPropertyValue("basePackage",
StringUtils.collectionToCommaDelimitedString(basePackages));
// 注册 MapperScannerConfigurer 为 beanDefinition
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
复制代码
registerBeanDefinitions(...)
方法先是为MapperScannerConfigurer
创建了一个BeanDefinitionBuilder
,然后就是解析@MapperScan
的各种属性,并添加到BeanDefinitionBuilder
中,然后调用registry.registerBeanDefinition
容器中。上述方法是精简后的代码,由于@MapperScan
支持的配置比较多,原本的代码非常长,精简后的代码中仅留下了我们设置的value
值,它就是这样设置到BeanDefinitionBuilder
中的。
因此,整个MapperScannerRegistrar
就只是向spring容器中注册了一个类:MapperScannerConfigurer
。
3.2 MapperScannerConfigurer
接下来我们就把目光指向了MapperScannerConfigurer
:
/**
* 它实现了 BeanDefinitionRegistryPostProcessor
*/
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor,
InitializingBean, ApplicationContextAware, BeanNameAware {
/**
* 来自于 BeanDefinitionRegistryPostProcessor 的方法
* 确切地说,是来自于 BeanFactoryPostProcessor 的方法,因为 BeanFactoryPostProcessor 是
* BeanDefinitionRegistryPostProcessor 的父类
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 方法实现为空,没有任何内容,所以spring为何不在接口方法中加个default关键字呢
}
/**
* 来自于 BeanDefinitionRegistryPostProcessor 的方法
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 定义扫描类
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 设置各种属性,一些是由 @MapperScan 中解析得到的
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
// 注册过滤配置
scanner.registerFilters();
// 在这里处理扫描操作
scanner.scan(
// 这里就是前面解析的 basePackage
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
复制代码
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
,这是spring提供的BeanFactoryPostProcessor
之一,可以在spring执行的早期向容器中注册beanDefinition
,它提供了两个方法:postProcessBeanFactory(...)
(定制化beanFactory
的某些行为)与postProcessBeanDefinitionRegistry(...)
(处理beanDefinition
注册),由于postProcessBeanFactory(...)
方法为空实现,因此我们重点关注postProcessBeanDefinitionRegistry(...)
方法。
在postProcessBeanDefinitionRegistry(...)
方法中,先是创建了一个ClassPathMapperScanner
的实例,该实例主要功能就是用来处理MapperInterface
的扫描功能的,之后再对ClassPathMapperScanner
实例进行一些属性设置,这些属性大部分来自于@@MapperScan
的设置,最后调用ClassPathMapperScanner#scan(...)
方法也进行扫描操作,在这个方法里,满足条件的MapperInterface
会被转化为 BeanDefinition
从而注册到spring容器中的。
接下来我们就重点关注ClassPathMapperScanner
。
3.3 ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
...
}
复制代码
可以看到,ClassPathMapperScanner
继承了ClassPathBeanDefinitionScanner
,关于ClassPathBeanDefinitionScanner
,熟悉spring的小伙伴并不陌生,它就是spring中用来处理@ComponentScan
注解扫描的类,也就是说,处理@MapperScan
的扫描底层使用的也是与@ComponentScan
一样的方式。
1. ClassPathMapperScanner#registerFilters(...)
我们知道,spring的@ComponentScan
只扫描包下标记了@Component
注解的类,而MapperInterface
只是个接口,它为何也能扫描呢?这就是ClassPathMapperScanner#registerFilters(...)
的功能了,在MapperScannerConfigurer#postProcessBeanDefinitionRegistry
中我们也看到了它的调用:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 省略一些配置
...
// 注册过滤配置
scanner.registerFilters();
// 在这里处理扫描操作
scanner.scan(
// 这里就是前面解析的 basePackage
StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
复制代码
我们来看看ClassPathMapperScanner
的注册过滤配置的方法registerFilters
:
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 省略一些配置
...
// 表示包下的接口也扫描
if (acceptAllInterfaces) {
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// 过滤 package-info.java 文件
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
复制代码
这个方法里注册了一些过滤配置,这里我们只关注acceptAllInterfaces
的处理,相关代码为:
if (acceptAllInterfaces) {
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
复制代码
这表明包下的一切类、接口等都会被扫描为BeanDefinition
。
2.ClassPathMapperScanner#doScan
准备工作做完后,我们来看看具体的扫描流程,也就是scanner.scan(...)
方法,ClassPathMapperScanner
并没有重写该方法,调用的 是spring 提供的 ClassPathBeanDefinitionScanner#scan
方法中:
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 这是真正干活的方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
复制代码
doScan(...)
方法ClassPathMapperScanner
是有重写的,我们进入ClassPathMapperScanner#doScan
方法:
@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 {
// 处理扫描得到的 beanDefinitions
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
复制代码
doScan(...)
方法继续调用父类的doScan(...)
方法进行扫描,然后再调用processBeanDefinitions(...)
处理扫描结果,调用super.doScan(basePackages)
得到的结果如下:
org.mybatis.spring.demo.mapper
包下的UserMapper
已经被扫描为beanDefinition
了,它的beanClass
是UserMapper
,这是个接口,它该如何进行实例化呢?这就是processBeanDefinitions(...)
的功能,我们继续看下去。
3. ClassPathMapperScanner#processBeanDefinitions
ClassPathMapperScanner#processBeanDefinitions
方法的代码如下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// 遍历得到的 beanDefinitions
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
...
// 设置构造方法参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 获取 beanClassName
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 修改 beanClass 为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// 设置 MapperFactoryBean 的 factoryBeanObjectType 为 beanClassName
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
// 设置 sqlSessionFactory,这里的 sqlSessionFactoryBeanName 与 sqlSessionFactory 都是
// 由 @MapperScan 中配置的,如果未配置,这两个属性的值就为 null
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
// 设置 sqlSessionFactory 属性,设置时会调用 setSqlSessionFactory(...) 方法
definition.getPropertyValues().add("sqlSessionFactory",
// RuntimeBeanReference 会把传入的字符串解析为 bean
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
// 如果传入的是 sqlSessionFactory,则直接设置值
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
// 设置 sqlSessionTemplate,操作同设置sqlSessionFactory类似
// 同样地,这里的 sqlSessionTemplateBeanName 与 sqlSessionTemplate 都是
// 由 @MapperScan 中配置的,如果未配置,这两个属性的值就为 null
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
// 设置 sqlSessionTemplate,这是个beanName,RuntimeBeanReference会beanName解析成对应的bean
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
// 设置 sqlSessionTemplate,直接设置 sqlSessionTemplate 的值
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
// 如果 sqlSessionFactory与sqlSessionTemplate都未设置,就使用byType的自动注入方式
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
// 省略了其他代码
...
}
}
复制代码
这个方法还是有点长的,经过精简后,代码还是比较清晰的,这里先来看看它是如何替换beanDefinition
的beanClass的:
- 调用
definition.getBeanClassName()
获取原本的beanClassName
,这里得到的beanClassName
是org.mybatis.spring.demo.mapper.UserMapper
- 设置构造方法参数为
beanClassName
,也就是org.mybatis.spring.demo.mapper.UserMapper
- 将
definition
的beanClass
设置为org.mybatis.spring.mapper.MapperFactoryBean
经过这三步之后,definition
的beanClass
就是MapperFactoryBean
了,就可以放心大胆地创建MapperFactoryBean
实例了:
我们要的是org.mybatis.spring.demo.mapper.UserMapper
,但spring给我们创建的却是org.mybatis.spring.mapper.MapperFactoryBean
,这两者之间有何关系呢?我们进入MapperFactoryBean
一探究竟:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
...
}
复制代码
从代码来看,它继承了FactoryBean
,类型为mapperInterface
,也就是我们处理构造方法传入的类型:
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
复制代码
这样一来,相当于MapperFactoryBean
的mapperInterface
值为org.mybatis.spring.demo.mapper.UserMapper
.
除了构造方法外,ClassPathMapperScanner#processBeanDefinitions
方法还为MapperFactoryBean
设置了其他的一些值,这里我们主要关注sqlSessionFactory
与sqlSessionTemplate
的设置。
先看sqlSessionFactory
的设置,如果@MapperScan
中配置了sqlSessionFactoryBeanName
,就会将其设置到MapperFactoryBean
到sqlSessionFactory
属性中。如果sqlSessionFactoryBeanName
未配置,就看@MapperScan
有没有配置sqlSessionFactory
,如果配置了,就直接设置。
处理sqlSessionFactoryBeanName
设置操作时,有两点需要注意:
- 传入的
sqlSessionFactoryBeanName
是字符串,如果转换为spring bean
呢?这就是RuntimeBeanReference
的功能了,它会字符串解析为具体的spring bean
; MapperFactoryBean
并没有sqlSessionFactory
的属性,不仅如此,就边它的父类也没有,它该如何设置呢?实际上,在spring中设置属性值,并不需要属性存在,只需要属性对应的setXxx(...)
方法存在就可以了,而MapperFactoryBean
的父类SqlSessionDaoSupport
正好有setSqlSessionFactory(...)
方法,因此设置sqlSessionFactory
属性时,调用的是setSqlSessionFactory(...)
方法。
关于sqlSessionTemplate
的设置同sqlSessionFactory
非常类似,这里就不再分析了。
在我们的demo中,@MapperScan
并没有配置sqlSessionFactoryBeanName
/sqlSessionFactory
/sqlSessionTemplateName
/sqlSessionTemplate
,MapperFactoryBean
的sqlSessionFactory
是从何而来的呢?
注意看processBeanDefinitions
方法,如果sqlSessionFactory
与sqlSessionTemplate
,explicitFactoryUsed
的值就为false
,接下来会有一个关键操作:
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
复制代码
这个设置表明,该beanDefinition
的注入模式为byType
,也就是按类型注入,spring在处理注入时,会搜索MapperFactoryBean
的setXxx()
,为其设置值。
在我们的示例demo中,MapperFactoryBean
的setSqlSessionFactory(...)
方法如下:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory
!= this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
复制代码
spring 会先获取到setSqlSessionFactory(...)
方法,然后获知其参数类型为SqlSessionFactory
,接着就从spring容器中找到SqlSessionFactory
类型的bean,这个bean我们在Demo01Config
已经创建了:
@Configuration
public class Demo01Config {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
...
}
...
}
复制代码
找到SqlSessionFactory
类型的bean后,就以此bean为参数,调用setSqlSessionFactory(...)
方法。至于sqlSessionFactory
如何设置到MapperFactoryBean
的具体操作,那就是setSqlSessionFactory(...)
的操作了。
一句话,spring 提供sqlSessionFactory
参数,setSqlSessionFactory(...)
负责具体的设置操作。
4. 获取注入的XxxMapper
:MapperFactoryBean#getObject
MapperInterface
注入到spring容器的bean是MapperFactoryBean
,在自动注入时,该如何拿到对应的MapperInterface
bean 呢?实际上,spring 在处理FactoryBean
的自动注入时,会调用FactoryBean#getObject
获取FactoryBean
实际表示的bean,我们进入MapperFactoryBean
的getObject()
方法:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
复制代码
这个方法分为两部分:getSession()
与getMapper()
,我们先来看getSession()
方法,它位于SqlSessionDaoSupport
内:
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
复制代码
这个方法只是简单地返回了sqlSessionTemplate
。在前面我们并没有设置sqlSessionTemplate
,这个sqlSessionTemplate
是从何而来呢?通过调试,我们发现它是从setSqlSessionFactory(...)
中来的:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
/**
* 设置 SqlSessionFactory
*/
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null
|| sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
/**
* 创建 SqlSessionTemplate
*/
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
...
}
复制代码
在调用setSqlSessionFactory(...)
方法时,会创建sqlSessionTemplate
。我们再进入SqlSessionTemplate
方法,看看它的处理逻辑:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
/** 传入的的 sqlSessionFactory */
private final SqlSessionFactory sqlSessionFactory;
/** 这就是 sqlSessionProxy */
private final SqlSession sqlSessionProxy;
/**
* SqlSessionTemplate 的构造方法
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 传入的 sqlSessionFactory
this.sqlSessionFactory = sqlSessionFactory;
// 又是一个动态代理对象,代理的是 SqlSession 对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
...
}
...
}
复制代码
SqlSessionTemplate
中在两个重要的成员变量:sqlSessionFactory
与sqlSessionProxy
,这个sqlSessionFactory
就是在SqlSessionDaoSupport#setSqlSessionFactory(...)
中传入的sqlSessionFactory
,而sqlSessionProxy
是SqlSession
的代理对象,它的InvocationHandler
实例为SqlSessionInterceptor
,关于它的妙用我们后面再分析。
回到MapperFactoryBean#getObject()
方法,看完了getSession()
后,我们再来看看getMapper
的逻辑:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
...
}
复制代码
这个方法比较简单,就是先获取Configuration
,然后从Configuration
中获取UserMapper
的实例,这里的type
为UserMapper.class
,this
为SqlSessionTemplate
的实例。继续跟进去,到达MapperRegistry#getMapper
方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 创建对就的 type 实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
复制代码
可以看到,XxxMapper
实例是在调用MapperRegistry#getMapper
方法时创建的,关于创建流程及mapperProxyFactory
,这些是mybatis
相关的内容,本文主要关注与spring的整合,这些就不分析了。
经过了以上步骤,MapperFactoryBean#getObject
就能成功获取UserMapper
实例了,UserService
中的UserMapper
也能注入成功了。
4. 执行数据库操作
接下来我们来看看数据库的执行操作,也就是userMapper.selectList(...)
方法。
关于mybatis
中XxxMapper
的执行流程,在mybatis源码分析02:执行sql语句一文已经详细分析过了,本文不再赘述,本节我们主要关注SqlSessionTemplate
的相关操作。
在前面的分析文章中,我们提到mybatis
提供的sqlSession
默认实现DefaultSqlSession
不是线程安全的,在前面得到的UserMapper
中,使用的是SqlSessionTemplate
,它是线程安全的吗?我们通过调试来进行分析。
在userMapper.selectList(...)
的调用过程中,经过一系列的调用栈后,最终会来到MapperMethod#executeForMany
方法:
在MapperMethod#executeForMany
方法中,又会调用sqlSession.selectList(...)
方法来获取数据,而这个sqlSession
就是SqlSessionTemplate
:
再进入SqlSessionTemplate#selectList(...)
方法:
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
复制代码
它是调用sqlSessionProxy
对象的selectList
方法进行查询的。对于sqlSessionProxy
,上一节在分析SqlSessionTemplate
的构造方法时我们提到,它是在SqlSessionTemplate
中创建的:
// 又是一个动态代理对象,代理的是 SqlSession 对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
复制代码
没错,它是个动态代理对象,它的InvocationHandler
实例为SqlSessionInterceptor
,执行sqlSessionProxy.selectList(...)
时,SqlSessionInterceptor#invoke(...)
会先被执行,我们进入SqlSessionInterceptor#invoke(...)
方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取 sqlSession,获取到的类型是 DefaultSqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
...
} finally {
...
}
}
复制代码
这个方法里,主要是处理sqlSession
的获取操作,得到sqlSession
之后会再调用原本方法。这个获取到的sqlSession
是不是线程安全的呢?从运行结果来看,它的实际类型依旧是DefaultSqlSession
,并不是线程安全的。那这个操作依然不是线程安全的吗?不一定,衡量DefaultSqlSession
是否线程安全,并不一定它本身是否线程安全,如果每个线程都有自己的DefaultSqlSession
,DefaultSqlSession
不会被跨线程共享,那么即使DefaultSqlSession
非线程安全,但整个操作还是线程安全的。
每个线程都有自己的DefaultSqlSession
的办法或许你已经想到了,就是ThreadLocal
!mbyatis
提供的SqlSessionManager
就是这样实现的,而SqlSessionTemplate
由于是与spring结合,这块功能它托管给了spring来做,我们且看sqlSession
的获取:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
...
// 获取 SqlSessionHolder,托管给了 spring
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 新建一个 sqlSession
session = sessionFactory.openSession(executorType);
// 注册SessionHolder
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
复制代码
从代码来看,SqlSession
是由TransactionSynchronizationManager.getResource(...)
获取的,获取不到时才会新建。获取到的SqlSession
是所有线程共享的,还是一个线程独占的呢?关于TransactionSynchronizationManager.getResource(...)
,本文并不打算分析,我们直接看它的方法说明:
从这个方法的说明来看,它就是用来返回与当前线程的绑定的资源的,这也就是说,通过TransactionSynchronizationManager.getResource(...)
得到的SqlSession
就是线程独占的,因此不存在线程安全问题。
另外,据本人验证,只有在开启事务时,SqlSession
才会被保存,如果未开启事务,TransactionSynchronizationManager.getResource(...)
得到的SqlSession
一直为null
,每次调用getSqlSession(...)
时都会创建一个新的SqlSession
.
到了这里,我们就明白了,在执行MapperInterface
的方法时,获取到的SqlSession
由spring容器托管,并且线程独占。
5. 总结
This article mainly analyzes the integration process of mybatis and spring:
SqlSessionFactory
The property configuration of isSqlSessionFactoryBean
handled by this class, which provides a lot ofsetXxx(...)
methods to customizemybatis
a series of configurations;@MapperScan
The scan path that can be specifiedMapperInterface
, these package paths willMapperScannerConfigurer
be scanned in the class and registered in the spring container;- The initial obtained by scanning the packet path is
beanDefinition
thebeanClass
interface, whichmybatis
will be convertedClassPathMapperScanner#processBeanDefinitions
to , this is , the actual type is ;beanDefinition
beanClass
MapperFactoryBean
FactoryBean
MapperInterface
- When the bean to be injected is obtained, the corresponding bean is obtained
MapperInterface
through the method , and the obtained bean is a dynamic proxy object, among which is ;MapperFactoryBean#getObject(...)
MapperInterface
sqlSession
SqlSessionTemplate
SqlSessionTemplate
There is a member variable:sqlSessionProxy
, whose type isSqlSession
, when the method is executed , the database operation method calledMapperInterface
will eventually be used ;sqlSessionProxy
SqlSession
- Since it
sqlSessionProxy
is a dynamic proxy object, when a method is executed, it will pass through theInterceptor#invoke(...)
method, and the method will obtain the binding to the current threadDefaultSqlSession
, which is also used in actual executionDefaultSqlSession
, thus avoiding thread safety issues.
The link to the original text of this article: my.oschina.net/funcy/blog/… , limited to the author's personal level, there are inevitably mistakes in the text, welcome to correct me! Originality is not easy. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.