mybatis与Spring整合分析

mybatis与Spring整合分析

mybatis与spring整合

主要依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4-snapshot</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>

Domain数据表实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.dxysun.mybatis.spring.domain;

import java.io.Serializable;

public class User implements Serializable
{
private Integer id;

private String userName;

private String realName;

private String password;

private Integer age;

private Integer dId;

public Integer getId()
{
return id;
}

public void setId(Integer id)
{
this.id = id;
}

public String getUserName()
{
return userName;
}

public void setUserName(String userName)
{
this.userName = userName;
}

public String getRealName()
{
return realName;
}

public void setRealName(String realName)
{
this.realName = realName;
}

public String getPassword()
{
System.out.println("getPassword:" + password);
return password;
}

public void setPassword(String password)
{
System.out.println("setPassword:" + password);
this.password = password;
}

public Integer getAge()
{
return age;
}

public void setAge(Integer age)
{
this.age = age;
}

public Integer getDId()
{
return dId;
}

public void setDId(Integer dId)
{
this.dId = dId;
}

@Override
public String toString()
{
return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", realName='" + realName + '\''
+ ", password='" + password + '\'' + ", age=" + age + ", dId=" + dId + '}';
}
}

mapper接口

1
2
3
4
5
6
public interface UserMapper
{

User selectUserByName(String userName);

}

mapper配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="com.dxysun.mybatis.spring.mapper.UserMapper">

<resultMap id="BaseResultMap" type="com.dxysun.mybatis.spring.domain.User" >
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="realName" column="real_name" jdbcType="VARCHAR" />
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="dId" column="d_id" jdbcType="INTEGER"/>
</resultMap>

<select id="selectUserByName" resultMap="BaseResultMap" >
select * from t_user where user_name = #{userName}
</select>

</mapper>

SqlSessionFactoryBean

在Spring的配置文件 applicationContext.xml 里面配置SqlSessionFactoryBean。这个Bean会初始化一个SqlSessionFactory,用来帮我们创建SqlSession。
它的属性还要指定全局配置文件mybatis-config.xml和Mapper映射器文件的路径,数据源也是由Spring来管理。mybatis-spring.xml可以是空的,什么都不用配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 整合mybatis -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean"
id="sqlSessionFactoryBean" >
<!-- 关联数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 关联mybatis的配置文件 -->
<property name="configLocation" value="classpath:mybatis-spring.xml"/>
<!-- 指定映射文件的位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<!-- 添加别名 -->
<property name="typeAliasesPackage" value="com.dxysun.mybatis.spring.domain" />

</bean>

MapperScannerConfigurer

然后在applicationContext.xml配置需要扫描Mapper接口的路径。

有几种方式,第一种是配置一个MapperScannerConfigurer。

1
2
3
4
<!-- 配置扫描的路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name="basePackage" value="com.dxysun.mybatis.spring.mapper"/>
</bean>

第二种是配置一个<scan>标签

1
<mybatis-spring:scan base-package="com.dxysun.mybatis.spring.mapper"/>

还有一种就是直接用@MapperScan注解,比如我们在Spring Boot的启动类上加上一个注解。

1
2
3
4
5
6
@SpringBootApplication
@MapperScan("com.dxysun.mybatis.spring.mapper")
public class MybaitsApp {
public static void main(String[] args){
SpringApplication.run(MybaitsApp.class, args);
}

这三种方式实现的效果是一样的。

经过这两步(SqlSessionFactoryBean+ MapperScannerConfigurer)配置以后,Mapper就可以注入到Service层使用了,MyBatis其他的代码和配置不需要做任何的改动。

所有配置信息汇总如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" 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">
<!-- 关联数据属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 开启扫描 -->
<context:component-scan base-package="com.dxysun.mybatis.spring"/>

<!-- 配置数据源 -->
<bean class="com.alibaba.druid.pool.DruidDataSource"
id="dataSource" >
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 整合mybatis -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean"
id="sqlSessionFactoryBean" >
<!-- 关联数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 关联mybatis的配置文件 -->
<property name="configLocation" value="classpath:mybatis-spring.xml"/>
<!-- 指定映射文件的位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<!-- 添加别名 -->
<property name="typeAliasesPackage" value="com.dxysun.mybatis.spring.domain" />

</bean>
<!-- 配置扫描的路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name="basePackage" value="com.dxysun.mybatis.spring.mapper"/>
</bean>
</beans>

运行示例

1
2
3
4
5
6
7
8
9
10
public class Main
{
public static void main(String[] args)
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

UserMapper userMapper = applicationContext.getBean(UserMapper.class);
System.out.println(userMapper.selectUserByName("zhangsan"));
}
}

与springboot整合参考

整合分析

把MyBats集成到Spring里面,是为了进一步简化MyBatis的使用,所以只是对MyBatis做了一些封装,并没有替换MyBatis的核心对象。

也就是说:MyBatisjar包中的SqlSessionFactory、SqlSession、MapperProxy这些类都会用到,mybatis-spring.jar里面的类只是做了一些包装或者桥梁的工作。

只要我们弄明白了这三个对象是怎么创建的,也就理解了Spring集成MyBatis的原理,可以把它分成三步:
1)SqlSessionFactory在哪创建的。
2)SqlSession在哪创建的
3)代理类在哪创建的。

创建会话工厂SqlSessionFactory

第一步,我们看一下在Spring里面,Sq|SessionFactory是怎么创建的。

我们在Spring的配置文件中配置了一个SqlSessionFactoryBean,看下这个类的内容:

1
2
3
4
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
...
}

它实现了三个接口:InitializingBean、FactoryBean、 Appicationlistener。

InitializingBean

实现了InitializingBean接口,所以要实现afterPropertiesSet()方法,这个方法会在bean的属性值设置完的时候被调用。

1
2
3
4
5
6
7
8
9
@Override
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();
}

afterPropertiesSet()方法里面,经过一些检查之后,调用了buildSqlSessionFactory()方法。

这里创建了一个Configuration对象,叫做targetConfiguration,还创建了一个用来解析全局配置文件的XMLConfigBuilder。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

XMLConfigBuilder xmlConfigBuilder = null;
// 判断Configuration对象是否已经存在,也就是是否已经解析过。如果已经有对象,就覆盖一下属性。
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);
}
} else if (this.configLocation != null) {
// 如果Configuration不存在,但是配置了configLocation属性,就根据mybatis-config.xm的文件路径,构建一个xmlConfigBuilder对象。
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
//否则,Configuration对象不存在,configLocation路径也没有,只能使用默认属性去构建去给configurationProperties赋值。
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}

/*
后面就是基于当前SqlSessionFactoryBean对象里面已有的属性,对targetConfiguration对象里面属性的赋值。
Optional.ofNullable()是Java8里面的一个判空的方法。如果不为空的话,就会调用括号里面的对象的方法,把解析出来的属性,赋值给SqISessionFactoryBean的属性。
*/
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

/*
后面对于typeAliases、 plugins、 typeHandlers的解析,都是调用Configuration类的方法。
*/
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}

if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}

if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}

if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}

if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}

if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);

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);
}
}

Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

/*
如果xmlConfigBuilder不为空,也就是上面的第二种情况,调用了xmlConfigBuilder.parse() 去解析配置文件,最终会返回解析好的Configuration对象。
*/
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();
}
}

/*
先判断事务工厂是否存在。这个事务工厂,是解析environments标签的时候,根据<transactionManager>子标签创建的。
mybatis提供了JDBC或者MANAGED这两个值。如果没有配置,比如数据源交给Spring管理的时候,这里就是空的。
此时会new一个SpringManagedTransactionFactory,放在Environment里面。这个事务工厂获取的事务是SpringManagedTransaction 对象,定义了getConnection()、close()、commit()、rollback()等方法。
*/
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(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}

try {
/*
创建了一个用来解析Mapper.xml的XMLMapperBuilder,调用了它的parse()方法。主要做了两件事情,一个是把增删改查标签注册成MappedStatement对象。第二个是把接口和对应的MapperProxyFactory工厂类注册到MapperRegistry中。
*/
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
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.");
}
// 最后调用sq|SessionFactoryBuilder.build()返回了DefaultSqlSessionFactory。
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

总结一下,通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面有一个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用,Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。

FactoryBean

另外SqlSessionFactoryBean实现了FactoryBean接口。

FactoryBean的作用是让用户可以自定义实例化Bean的逻辑。如果从BeanFactory 中根据Bean的ID获取一个Bean,它获取的其实是FactoryBean的getObject()返回的对象。也就是说,我们获取SqlSessionFactoryBean的时候,就会调用它的getObject()方法。

1
2
3
4
5
6
7
8
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}

return this.sqlSessionFactory;
}

getObject()方法也是调用了afterPropertiesSet()方法,去做MyBatis解析配置文件的工作,返回一个DefaultSqlSessionFactory。

ApplicationListener

实现ApplicationListener接口让SqISessionFactoryBean有能力监控应用发出的一些事件通知。

比如这里监听了ContextRefreshedEvent (上下文刷新事件),会在Spring容器加载完之后执行。

这里做的事情是检查mybatis是否加载完毕。

1
2
3
4
5
6
7
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}

Sq|SessionFactoryBean用到的Spring扩展点总结:

接口 方法 作用
FactoryBean getObject() 返回由FactoryBean创建的Bean实例
InitializingBean afterPropertiesSet() bean属性初始化完成后添加操作
ApplicationListener onApplicationEvent() 对应用的事件进行监听

创建会话SqlSession

为什么不能直接使用DefaultSqlSession?

我们现在已经有一个DefaultSqlSessionFactory,按照编程式的开发过程,接下来就会用openSession()创建一个SqlSession的实现类。

但是在Spring里面,我们不是直接使用DefaultSq|Session的。为什么不用DefaultSqISession?

它是线程不安全的。

所以,在Spring里面,我们要保证Sq|Session实例的线程安全,必须为每一次请求单独创建一个SqlSession。但是每一次请求用openSession()自己去创建,又会比较麻烦。

在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是单例的)。

SqlSessionTemplate虽然跟 DefaultSqlSession一样定义了操作数据的selectOne()、selectList()、insert()、update()、delete()等所有方法,但是没有自己的实现,全部调用了一个代理对象的方法。

例如

1
2
3
4
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}

这个代理对象是在构造方法里面通过JDK动态代理创建:

1
2
3
4
5
6
7
8
9
10
11
12
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

它是对SqlSession实现类DefaultSqlSession的代理。既然是JDK动态代理,那对代理类任意方法的调用都会走到(第三个参数)实现了InvocationHandler接口的触发管理类SqlSessionInterceptor的invoke()方法。

SqlSessionInterceptor是一个内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
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
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);
}
}
}
}

这里会先用getSqISession()方法创建一个SqlSession对象,把 SqlSessionFactory、执行器类型、异常解析器传进去。 获得Sq|Session实例(实际上是DefaultSqlSession)之后,再调用它的增删改查的方法。

总结一下:因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建一个DefaultSqlSession实例,再调用被代理对象的相应方法。

MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成到Spring要保证线程安全,就用SqlSessionManager。

跟JdbcTemplate,RedisTemplate一样,SqlSessionTemplate可以简化MyBatis在Spring中的使用,也是Spring跟MyBatis整合的最关键的一个类。

SqlSessionTemplate有三个重载的构造函数,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

怎么拿到一个SqlSessionTemplate

因为SqlSessionTemplate是线程安全的,可以替换DefaultSqlSession,那在DAO层怎么拿到一个SqlSessionTemplate呢?

MyBatis-Spring提供了一个抽象的支持类SqlSessionDaoSupport。

SqlSessionDaoSupport类中持有一个SqlSessionTemplate对象,并且提供了一个getSqlSession()方法,让我们获得一个SqlSessionTemplate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSessionTemplate sqlSessionTemplate;

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}

@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

public final SqlSessionFactory getSqlSessionFactory() {
return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}

public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
...
}

也就是说我们让DAO层(实现类)继承抽象类Sq|SessionDaoSupport,就自动拥有了getSqlSession()方法。调用getSqlSession()就能拿到共享的Sq|SessionTemplate。

但是,我们在实际的Spring项目里面也没有这么做。我们是直接注入了一个Mapper接口,调用它的方法就OK了。

那这个Mapper接口是怎么拿到SqlSessionTemplate的?当我们调用方法的时候,还会不会通过MapperProxy?

这个Mapper接口可以@Autowired注入到任何地方的话,它肯定是在容器BeanFactory (比如XmlWebApplicationContext)中注册过了。

问题是:

1、什么时候注册到容器中的?
2、注册的时候,注册的是什么对象?是代理对象吗?

接口的扫描注册

我们在applicationContext.xml里面配置了一个MapperScannerConfigurer,它是用来扫描Mapper接口的。

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子类,里面有一个postProcessBeanDefinitionRegistry()方法。

实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义,Spring创建Bean之前会调用这个方法。

MapperScannerConfigurer重写了postProcessBeanDefinitionRegistry(),那它要做什么呢?

在这个方法里面:

创建了一个scanner对象,然后设置属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}

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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

ClassPathBeanDefinitionScanner的scan()方法:

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

这里会调用它的子类ClassPathMapperScanner的doScan方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}

// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}

if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}

if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

return beanDefinitions;
}

子类ClassPathMapperScanner又调用了父类ClassPathBeanDefinitionScanner的doScan()扫描所有的接口,把接口全部添加到beanDefinitions中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

2、ClassPathMapperScanner的doScan方法,在注册beanDefinitions的时候,BeanClass被改为MapperFactoryBean。

1
2
3
4
5
6
7
8
9
10
11
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}

// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

也就是说,所有的Mapper接口,在容器里面都被注册成一个支持泛型的MapperFactoryBean了。
为什么要注册成它呢?那注入使用的时候,也是这个对象,这个对象有什么作用?

看一下这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

private Class<T> mapperInterface;

private boolean addToConfig = true;

/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
...
}

继承了抽象类SqlSessionDaoSupport,这不就解决了我们的第一个问题了,现在每一个注入Mapper的地方,都可以拿到SqlSessionTemplate。

现在只剩下最后一个问题了,有没有用到MapperProxy?如果注册的是MapperFactoryBean,难道注入使用的也是MapperFactoryBean吗?这个类并不是代理类。

接口注入使用

所以注入的到底是一个什么对象?注意看MapperFactoryBean也实现了FactoryBean,。它可以在getObject() 中修改获取Bean实例的行为。

1
2
3
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

它并没有直接返回一个MapperFactoryBean。而是调用了SqlSessionTemplate的getMapper()方法。SqlSessionTemplate的本质是一个代理,所以它最终会调用DefaultSqISession的getMapper()方法。也就是说,最后返回的还是一个JDK的动态代理对象。

所以最后调用Mapper接口的任何方法,也是执行MapperProxy的invoke()方法,后面的流程就跟编程式的流程里面一模一样了。

总结

总结一下,Spring是怎么把MyBatis集成进去的?

1、提供了SqlSession的替代品SqlSessionTemplate,里面有一个实现了实现了InvocationHandler的内部SqlSessionlnterceptor,本质是对SalSession的代理。

2、提供了获取SqISessionTemplate的抽象类SqlSessionDaoSupport。

3、扫描Mapper接口,注册到容器中的是MapperFactoryBean,它继承了SqlSessionDaoSupport,可以获得SqlSessionTemplate。

4、把Mapper注入使用的时候,调用的是getObject()方法,它实际上是调用了SqlSessionTemplate的getMapper()方法,注入了一个JDK动态代理对象。

5、执行Mapper接口的任意方法,会走到触发管理类MapperProxy,进入SQL处理流程。

对象 生命周期
SqlSessionTemplate Spring中SqlSession的替代品,是线程安全的
SqlSessionDaoSupport 用于获取 SqlSessionTemplate
SqlSessionInterceptor(内部类) 代理对象,用来代理DefaultSqlSession,在SqlSessionTemplate中使用
MapperFactoryBean 代理对象,继承了SqlSessionDaoSupport用来获取SqlSessionTemplate
SqlSessionHolder 控制SqlSession和事务
打赏

请我喝杯咖啡吧~

支付宝
微信