在Spring与Mybatis框架整合中,主要有两个重要改动,分别是事务与SqlSession。mybatis-spring包中为以上两个问题提供了解决方案。
- 重要组件
- SpringManagedTransaction (Spring事务管理器)
- SqlSessionTemplate (SqlSession的实现)
- SqlSessionFactoryBean(整合中取代SqlSessionFactoryBuilder作用,创建SqlSessionFactory类)
- SpringManagedTransactionFactory(Spring事务工厂)
- 整合流程
(1)在MybatisAutoConfiguration中给容器中注入了一个SqlSessionFactory 。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
而这个SqlSessionFactory 中的SpringManagedTransactionFactory,这个类实现了mybatis的顶级接口TransactionFactory,此时mybatis将不再管理事务,连接的获取和回滚也完全由Spring管理。
targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
这样的话每次在mapper执行相关方法时都会创建一个事务对象,这里自然就是创建的Spring的事务对象。
public class SpringManagedTransaction implements Transaction {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
private boolean isConnectionTransactional;
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
Assert.notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException {
if (this.connection == null) {
this.openConnection();
}
return this.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(() -> {
return "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring";
});
}
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> {
return "Committing JDBC Connection [" + this.connection + "]";
});
this.connection.commit();
}
}
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> {
return "Rolling back JDBC Connection [" + this.connection + "]";
});
this.connection.rollback();
}
}
public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
public Integer getTimeout() throws SQLException {
ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
}
}
在openConnection的时候,事务对象是从DataSourceUtils.getConnection(this.dataSource);此时Spring会往当前线程中存一个Connection连接,那么在同一个方法中,多个mapper在获取connection时,其实都是获取的一个connection,当整个service方法执行结束时,Spring的事务管理器会统一执行commit方法。
(2)MybatisAutoConfiguration中同时给容器中注入了一个SqlSessionTemplate。
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
SqlSessionTemplate 替代了之前的sqlsession,用来解决mapper之间的线程安全问题。
SqlSessionTemplate的getMapper代码如下:
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
可以看到,此时依然是调用的configuration对象的getMapper方法,但是SqlSession对象传递的是this,而这里的this为sqlSessionTemplate对象,无论是通过mapper还是sqlId,在最终执行sql时,都是用的sqlSession对象。那么我们就看SqlSessionTemplate对象是如何执行sql的。
以selectList方法为例,我们发现sqlSessionTemplate中的相关方法都是通过sqlSessionProxy对象去执行的。
public <E> List<E> selectList(String statement) {
return this.sqlSessionProxy.selectList(statement);
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
}
而通过构造方法排查,这个sqlSessionProxy竟然是个SqlSession的代理对象。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
代理对象的实现逻辑封装在了SqlSessionTemplate.SqlSessionInterceptor类中,我们只需要弄清这个类的实现原理即可。
果不其然这个类实现了InvocationHandler接口
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
在invoke方法中是通过 SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
来获取sqlSession的,我们跟进这个方法。
发现最终还是调用了openSession这个方法,但是这里要注意了,每次调用任何mapper的任何方法,都会重新开启一个openSession,每个session中的事务管理器也都是spring管理,这样的话就避免了线程安全问题,保证每一个mapper方法的调用都会有独立的sqlSession,同时事务又交由Spring管理。这样Mybatis可以与Spring进行整合,实现面向接口编程。