当前位置 : 主页 > 编程语言 > java >

Mybatis源码分析——Mybatis事务如何被Spring管理

来源:互联网 收集:自由互联 发布时间:2022-10-14
前言 不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下 !--会话工厂

前言

不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下

<!--会话工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> <!--spring事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!--使用注释事务 --> <tx:annotation-driven transaction-manager="transactionManager" />

看到没,sqlSessionFactory中配置了dataSource,transactionManager也配置了dataSource,我们来回忆一下SqlSessionFactoryBean这个类

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // 配置类 Configuration configuration; // 解析mybatis-Config.xml文件, // 将相关配置信息保存到configuration XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } //资源文件不为空 } else if (this.configLocation != null) { //根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); //将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性 configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { //解析mybatis-Config.xml文件,并将相关配置信息保存到configuration xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { 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(); } } if (this.transactionFactory == null) { //事务默认采用SpringManagedTransaction,这一块非常重要 this.transactionFactory = new SpringManagedTransactionFactory(); } // 为sqlSessionFactory绑定事务管理器和数据源 // 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 解析mapper.xml if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // 解析mapper.xml文件,并注册到configuration对象的mapperRegistry XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } // 将Configuration对象实例作为参数, // 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例 return this.sqlSessionFactoryBuilder.build(configuration); } }

我们看this.transactionFactory = new SpringManagedTransactionFactory(),Mybatis集成Spring后,默认使用的transactionFactory是SpringManagedTransactionFactory,那我们就来看看其获取Transaction的方法

public class DefaultSqlSessionFactory implements SqlSessionFactory { private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) { try { boolean autoCommit; try { autoCommit = connection.getAutoCommit(); } catch (SQLException e) { // Failover to true, as most poor drivers // or databases won't support transactions autoCommit = true; } //从configuration中取出environment对象 final Environment environment = configuration.getEnvironment(); //从environment中取出TransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //创建Transaction final Transaction tx = transactionFactory.newTransaction(connection); //创建包含事务操作的执行器 final Executor executor = configuration.newExecutor(tx, execType); //构建包含执行器的SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } //这里返回SpringManagedTransactionFactory return environment.getTransactionFactory(); } } public interface TransactionFactory { Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); } public class SpringManagedTransactionFactory implements TransactionFactory { @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { //创建SpringManagedTransaction return new SpringManagedTransaction(dataSource); } }

SpringManagedTransaction

也就是说mybatis的执行事务的事务管理器就切换成了SpringManagedTransaction,下面我们再去看看SpringManagedTransactionFactory类的源码:

public class SpringManagedTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class); private final DataSource dataSource; private Connection connection; private boolean isConnectionTransactional; private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } @Override public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } private void openConnection() throws SQLException { //通过DataSourceUtils获取connection,这里和JdbcTransaction不一样 this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } @Override public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.connection + "]"); } //通过connection提交,这里和JdbcTransaction一样 this.connection.commit(); } } @Override public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]"); } //通过connection回滚,这里和JdbcTransaction一样 this.connection.rollback(); } } @Override public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); } @Override public Integer getTimeout() throws SQLException { ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (holder != null && holder.hasTimeout()) { return holder.getTimeToLiveInSeconds(); } return null; } }

org.springframework.jdbc.datasource.DataSourceUtils#getConnection

public abstract class DataSourceUtils { public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); } catch (SQLException var2) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", var2); } catch (IllegalStateException var3) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + var3.getMessage()); } } public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //TransactionSynchronizationManager重点!!!有没有很熟悉的感觉?? //还记得我们前面Spring事务源码的分析吗?@Transaction会创建Connection,并放入ThreadLocal中 //这里从ThreadLocal中获取ConnectionHolder ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource); if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) { logger.debug("Fetching JDBC Connection from DataSource"); //如果没有使用@Transaction,那调用Mapper接口方法时,也是通过Spring的方法获取Connection Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { try { ConnectionHolder holderToUse = conHolder; if (conHolder == null) { holderToUse = new ConnectionHolder(con); } else { conHolder.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { //将获取到的ConnectionHolder放入ThreadLocal中,那么当前线程调用下一个接口,下一个接口使用了Spring事务,那Spring事务也可以直接取到Mybatis创建的Connection //通过ThreadLocal保证了同一线程中Spring事务使用的Connection和Mapper代理类使用的Connection是同一个 TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } catch (RuntimeException var4) { releaseConnection(con, dataSource); throw var4; } } return con; } else { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } //所以如果我们业务代码使用了@Transaction注解,在Spring中就已经通过dataSource创建了一个Connection并放入ThreadLocal中 //那么当Mapper代理对象调用方法时,通过SqlSession的SpringManagedTransaction获取连接时,就直接获取到了当前线程中Spring事务创建的Connection并返回 return conHolder.getConnection(); } } }

想看怎么获取connHolder

org.springframework.transaction.support.TransactionSynchronizationManager#getResource

public abstract class TransactionSynchronizationManager { //保存数据库连接的ThreadLocal private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources"); @Nullable public static Object getResource(Object key) { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); //获取ConnectionHolder Object value = doGetResource(actualKey); if (value != null && logger.isTraceEnabled()) { logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } return value; } @Nullable private static Object doGetResource(Object actualKey) { /** * 从threadlocal <Map<Object, Object>>中取出来当前线程绑定的map * map里面存的是<dataSource,ConnectionHolder> */ Map<Object, Object> map = (Map)resources.get(); if (map == null) { return null; } else { //map中取出来对应dataSource的ConnectionHolder Object value = map.get(actualKey); if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) { map.remove(actualKey); if (map.isEmpty()) { resources.remove(); } value = null; } return value; } } }

我们看到直接从ThreadLocal中取出来的conn,而spring自己的事务也是操作的这个ThreadLocal中的conn来进行事务的开启和回滚,由此我们知道了在同一线程中Spring事务中的Connection和Mybaits中Mapper代理对象中操作数据库的Connection是同一个,当取出来的conn为空时候,调用org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取,然后把从数据源取出来的连接返回

public abstract class DataSourceUtils { private static Connection fetchConnection(DataSource dataSource) throws SQLException { //从数据源取出来conn Connection con = dataSource.getConnection(); if (con == null) { throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource); } else { return con; } } }

我们再来回顾一下上篇文章中的SqlSessionInterceptor

public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取一个sqlSession来执行proxy的method对应的SQL, // 每次调用都获取创建一个sqlSession线程局部变量,故不同线程相互不影响,在这里实现了SqlSessionTemplate的线程安全性 SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //直接通过新创建的SqlSession反射调用method //这也就解释了为什么不需要目标类属性了,这里每次都会创建一个 Object result = method.invoke(sqlSession, args); // 如果当前操作没有在一个Spring事务中,则手动commit一下 // 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit // 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个Mapper的方法,sqlSession都提交了 // sqlSession提交,会清空一级缓存 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); } } } } }

看if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory))和sqlSession.commit(true)行,如果我们没有使用@Transation,Mapper方法执行完后,sqlSession将会提交,也就是说通过org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取到的Connection将会commit,相当于Connection是自动提交的,也就是说如果不使用@Transation,Mybatis将没有事务可言。

 

Mybatis和Spring整合后SpringManagedTransaction和Spring的Transaction的关系:

  • 如果开启Spring事务,则先有Spring的Transaction,然后mybatis创建sqlSession时,会创建SpringManagedTransaction并加入sqlSession中,SpringManagedTransaction中的connection会从Spring的Transaction创建的Connection并放入ThreadLocal中获取。

  • 如果没有开启Spring事务或者第一个方法没有事务后面的方法有事务,则SpringManagedTransaction创建Connection并放入ThreadLocal中。

spring结合mybatis后mybaits一级缓存失效分为两种情况:

  • 如果没有开启事务,每一次sql都是用的新的SqlSession,这时mybatis的一级缓存是失效的。
  • 如果有事务,同一个事务中相同的查询使用的相同的SqlSessioon,此时一级缓存是生效的。

如果使用了@Transation呢?那在调用Mapper代理类的方法之前就已经通过Spring的事务生成了Connection并放入ThreadLocal,并且设置事务不自动提交,当前线程多个Mapper代理对象调用数据库操作方法时,将从ThreadLocal获取Spring创建的connection,在所有的Mapper方法调用完后,Spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了。

 

还有文章开头的问题,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?

 

因为Spring事务在没调用Mapper方法之前就需要开一个Connection,并设置事务不自动提交,那么transactionManager中自然要配置dataSource。那如果我们的Service没有用到Spring事务呢,难道就不需要获取数据库连接了吗?当然不是,此时通过SpringManagedTransaction调用org.springframework.jdbc.datasource.DataSourceUtils#getConnection#fetchConnection方法获取,并将dataSource作为参数传进去,实际上获取的Connection都是通过dataSource来获取的。

 

参考: https://www.cnblogs.com/java-chen-hao/p/11839993.html

上一篇:Debug系列清单
下一篇:没有了
网友评论