Mybatis——Spring事务实现
前面学习Mybatis中Executor执行sql时,只研究了非事务执行时,直接DataSource.getConnection()创建连接。这里学习一下事务执行。
前提:SpringAOP——事务实现中学习到:Spring开启事务时会创建一个“事务连接”(需要手动提交的连接)然后通过ThreadLocal的方式,存储到当前线程中。
起因是面试官问了一个问题:spring中的事务连接和mybatis中的事务连接是否是同一个?
回答说:是同一个,但是说不出具体实现来(ThreadLocal)。
也就是说“事务连接”是Spring中创建的,Mybatis执行sql时,会从线程中取出“事务连接”,然后创建statement执行。结合前面学习的mybatis源码,用源码求证一下
一、复习mybatis与spring的交界处——代理实现
MapperFactoryBean.getObject()创建了一个接口的代理实例,InvocationHandler是MapperProxy类型,方法调用代码如下:
/* org.apache.ibatis.binding.MapperProxy#invoke */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { //Object的方法直接执行mapperProxy的方法 return method.invoke(this, args); } else if (isDefaultMethod(method)) { //静态方法直接执行 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //new MapperMethod(mapperInterface, method, configuration) //实际new sqlCommond(mapperInterface, mehod, configuration) //sqlCommond.name = mapperStatement.getId //sqlCommond.type = ms.getSqlCommondType()--insert|delete|update|select final MapperMethod mapperMethod = cachedMapperMethod(method); //sqlSession根据sqlCommondType执行对应sql,例如当sqlCommondType == select时 //sqlsession.select(sqlCommond.name) return mapperMethod.execute(sqlSession, args); } /* org.apache.ibatis.binding.MapperMethod#execute */ public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
二、mybatis中事务实现
以上面的insert为例,result = rowCountResult(sqlSession.insert(command.getName(), param));
sqlSession.insert()-->BaseExecutor.update()-->SimpleExecutor.doUpdate()
/* org.apache.ibatis.executor.SimpleExecutor#doUpdate */ public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); //这里获取连接并创建statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取连接 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } /* org.apache.ibatis.executor.BaseExecutor#getConnection */ protected Connection getConnection(Log statementLog) throws SQLException { //获取连接transaction.getconnection() Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
重点是BaseExecutor初始化时,transaction属性的类型。
/* org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //transaction属性 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { //需要确定environment.getTransactionFactory的类型 if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); }
确定environment.transactionFactory属性:SpringManagedTransactionFactory类型的实例
/* org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory */ protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; //... if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } //transactionFactory是SpringManagedTransactionFactory类型实例 configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); //... return this.sqlSessionFactoryBuilder.build(configuration); }
所以transaction是SpringManagedTransaction类型实例
/* org.mybatis.spring.transaction.SpringManagedTransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean) */ public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new SpringManagedTransaction(dataSource); }
Mybatis事务最终获取连接:SpringManagedTransaction.getConnection()
/* org.mybatis.spring.transaction.SpringManagedTransaction#getConnection */ 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); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } /* org.springframework.jdbc.datasource.DataSourceUtils#getConnection */ public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); } catch (IllegalStateException ex) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage()); } } public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //从当前线程中获取连接 //connectionHolder = Thread.currentThread().get("Transactinal resources").get(dataSource) ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { //不是事务连接时,从DataSource获取一个连接 logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } //事务执行时,从Thread中获取spring创建的事务连接 //非事务执行时,直接从Datasource获取一个连接 return conHolder.getConnection(); } // Else we either got no holder or an empty thread-bound holder here. //获得一个没有事务连接的空封装器ConnectionHolder时,从DataSource获取一个连接 logger.debug("Fetching JDBC Connection from DataSource"); Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { try { // Use same Connection for further JDBC actions within the transaction. // Thread-bound object will get removed by synchronization at transaction completion. ConnectionHolder holderToUse = conHolder; if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } else { holderToUse.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } catch (RuntimeException ex) { // Unexpected exception from external delegation call -> close Connection and rethrow. releaseConnection(con, dataSource); throw ex; } } return con; }
验证完毕:
“事务连接”是Spring调用DataSource创建的,并放到线程中,Mybatis执行sql时,会从线程中取出“事务连接”,然后创建statement执行。(ThreadLocal实现)
“非事务连接”由mybatis直接调用DataSource创建。
补充:mybatis的两种config文件,需要注意传统配置文件,配置可能导致spring事务失效。
① 依赖mybatis-spring.jar后的配置文件,默认初始化SQLSessionFactoryBean时,初始化SpringManagedTransactionFactory放入到Environment中
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="mapper/UserMapper.xml"/> </bean>
② 传统的配置文件,<environment />有个属性<transactionManager />与上面transactionFactory对应,需要设置成SpringManagedTransactionFactory.class,否则Spring事务无效
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!-- 配置文件的根元素 --> <configuration> <!-- ... --> <!-- 环境:配置mybatis的环境 --> <environments default="dev"> <!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 --> <environment id="dev"> <!-- 事务管理器 --> <!-- "JDBC" == JdbcTransactionFactory.class 直接从datasource获取连接,会导致spring事务失效--> <transactionManager type="JDBC"></transactionManager> <!-- 数据源 --> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> </dataSource> </environment> </environments> <!-- ... --> </configuration>