MyBatis-Spring 帮助将 MyBatis 代码无缝地整合到 Spring 中。

Mybatis与Spring集成时都做了什么?

MyBatis集成Spring后,Spring侵入了Mybatis的初始化和mapper绑定,具体就是:

  1. Cofiguration的实例化是读取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml
  2. mapper对象是方法级别的,Spring通过FactoryBean巧妙地解决了这个问题
  3. 事务交由Spring管理

初始化相关

在基础的MyBatis中,通过SqlSessionFactoryBuilder创建SqlSessionFactory。
集成Spring后由SqlSessionFactoryBean来创建。  

Cofiguration的实例化是读取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml

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
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//配置加载完毕后,创建SqlSessionFactory
afterPropertiesSet();
}
// 返回sqlSessionFactory
return this.sqlSessionFactory;
}

@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");

this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
// ...
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
}

事务管理

MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中。
借助 Spring 的 DataSourceTransactionManager 实现事务管理。

SqlSeeion与事务的关系:

  1. 同一事务中不管调用多少次 mapper里的方法,最终都是用得同一个sqlSession,即一个事务中使用的是同一个sqlSession。
  2. 如果没有开启事务,调用一次mapper里的方法将会新建一个 sqlSession 来执行方法。

SqlSeeion

在MyBatis 中,使用 SqlSessionFactory 来创建 SqlSession。通过它执行映射的sql语句,提交或回滚连接,当不再需要它的时候,可以关闭 session。
使用 MyBatis-Spring 之后,我们不再需要直接使用 SqlSessionFactory 了,因为我们的bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。

spring整合mybatis之后,通过动态代理的方式,使用SqlSessionTemplate持有的sqlSessionProxy属性来代理执行sql操作。方法执行完操作后,会执行finally里面的代码对sqlSeesion进行关闭。
因此,spring整合mybatis之后,由spring管理的sqlSeesion在sql方法(增删改查等操作)执行完毕后就自行关闭了sqlSession,不需要我们对其进行手动关闭。

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
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;

public SqlSessionDaoSupport() {
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}

}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
protected void checkDaoConfig() {
Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}

public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSession sqlSessionProxy;

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

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
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}

映射器 Mapper

Mapper的注册

1
2
3
4
/**  @MapperScan注解方式  */
@MapperScan("com.feng.blog.mybatis.*.mapper")
@SpringBootApplication
public class HaierDocumentCenterApplication {}

最终mapper接口都将被注册为MapperFactoryBean。

MapperFactoryBean分析

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
// 1. MapperFactoryBean.getObject()
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
// 2. SqlSessionTemplate.getMapper()
public class SqlSessionTemplate implements SqlSession, DisposableBean {
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
}
// 3. Configuration.getMapper()
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
// 4. MapperRegistry.getMapper()
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
// 5. MapperProxyFactory.newInstance()
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}

最终看到动态代理生成了一个新的代理实例返回了,也就是说,我们使用@Autowired注解进来一个Mapper接口,每次使用时都会由代理生成一个新的实例。

为什么在Mybatis中SqlSession是方法级的,Mapper是方法级的,在集成Spring后却可以注入到类中使用?
因为在Mybatis-Spring中所有Mapper被注册为FactoryBean,每次调用都会执行getObject(),返回新实例。