之前被同事问了一个问题:在我们的工程里,事务的开启跟关闭是由Spring负责的,但具体的SQL语句却是由Mybatis执行的。那么问题来了,Mybatis怎么保证自己执行的SQL语句是处在Spring的事务上下文中?
注:这篇文章重点不是分析Spring事务的实现原理,但却需要读者提前了解Spring事务原理的一些知识点,这样读起来才会容易些
现在公司主流的开发框架大部分是使用spring+mybatis来操作数据库,所有的事务操作都是交给spring去管理。当我们需要一个有事务上下文的数据库操作时,我们的做法就是写一个操作数据库的方法,并在该方法上面加上@Transactional注解就可以了。
仔细思考一下这个过程,@Transactional是由spring进行处理的,spring做的事情是从数据源(一般为数据库连接池,比如说druid,c3p0等)获取一个数据库连接,然后在进入方法逻辑前执行setAutoCommit(false)操作,最后在处理成功或者出现异常的时候分别执行commit或者rollback操作。
那么问题来了,开启跟结束事务是由spring获取到数据库连接以后进行操作的,但我们实际执行的update或者insert语句却是由mybatis获取数据库连接进行操作的,可以想到如果想让事务生效,那么spring跟mybatis使用的必须是同一个连接,真实情况是什么样呢?它们之间如何进行无缝衔接?让我们通过源码来分析一下。
首先如果想在spring中使用mybatis,我们除了引入mybatis依赖以外,还需要引入一个包:mybatis-spring。
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>x.x.x</version> </dependency>可以猜测这个依赖包应该就是Spring跟Mybatis进行无缝连接的关键。
一般来说,我们在工程中的配置文件往往是这样:
<!--会话工厂 --> <bean> <property ref="dataSource" /> </bean> <!--spring事务管理 --> <bean> <property ref="dataSource" /> </bean> <!--使用注释事务 --> <tx:annotation-driven transaction-manager="transactionManager" />注:
1.会话工厂sqlSessionFactory跟Spring事务管理器transactionManager所使用的数据源dataSource必须是同一个。
2.这里的sqlSessionFactory类型是org.mybatis.spring.SqlSessionFactoryBean,该类是由我们引入的包mybatis-spring提供的。
看名字就知道SqlSessionFactoryBean是一个工厂bean,也就是说它交给Spring的真正实例是由getObject()方法提供的,那么我们去看下它真正实例初始化源码:
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { //可以看出逻辑都在这里面 afterPropertiesSet(); } return this.sqlSessionFactory; } @Override public void afterPropertiesSet() throws Exception { //此处省略一些校验逻辑 //... this.sqlSessionFactory = buildSqlSessionFactory(); } //最后来看这个最核心的方法 protected SqlSessionFactory buildSqlSessionFactory() throws IOException { //... //省略一些其他初始化信息,我们重点关注事务处理逻辑 if (this.transactionFactory == null) { //可以看出,mybatis中把事务操作交给了SpringManagedTransactionFactory去做 this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); //省略后续逻辑 //... }下面我们再去看看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"); } /** * {@inheritDoc} */ @Override public void setProperties(Properties props) { // not needed in this version } }代码很少,且只有一个方法是有效的,看来离成功越来越近了,继续跟进去看看SpringManagedTransaction的源码:
@Override public Connection getConnection() throws SQLException { if (this.connection == null) { 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); }省略该类中其他部分,我们重点看获取连接的地方,这里最关键的地方就在this.connection = DataSourceUtils.getConnection(this.dataSource);,
DataSourceUtils全名是org.springframework.jdbc.datasource.DataSourceUtils,没错,它是由Spring提供的类,根据我们之前的猜测,Spring开启事务以后,Mybatis要想让自己的SQL语句处在这个事务上下文中操作,那必须拿到跟Spring开启事务同一个数据库连接才行,由于DataSourceUtils类是由Spring提供的,看来跟我们开始猜测的结果类似,我们接下来看看DataSourceUtils源码验证一下: