本博客内容非自创,转载自如下三位,侵删:java
https://juejin.im/post/5ab7bd11f265da23906bfbc5mysql
https://my.oschina.net/fifadxj/blog/785621spring
https://www.jianshu.com/p/b864aecc0de1sql
Java程序都是经过JDBC链接数据库的,经过SQL对数据库编程,JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式。ps:桥接模式是一种结构型设计模式,它的主要特色是把抽象与行为实现分离开来,分别定义接口,能够保持各部分的独立性以及应对他们的功能扩展。数据库
所谓规范,就是本身定义了标准接口,作了以下抽象:用Connection表明和数据库的链接,用Statement执行SQL,用ResultSet表示SQL返回的结果,提供了对数据的便利。从Connection能够建立Statement,Statement执行查询获得ResultSet。apache
上面说的Connection、Statement、ResultSet都应该是接口,具体实现由各个数据库提供商提供。有了规范,能够经过统一的接口,访问多种类型的数据库,可随便切换数据库。编程
上面提到,接口的实现由各个厂商提供,那么实现类的类名就会不统一,去建立Connection对象时,代码就会写死某个实现类,切换数据库时,就须要修改代码,这样不太好。为了解决这个问题,抽象了Driver驱动的概念。设计模式
Connection con=MySqlConnectionImpl("127.0.0.1",3306,"mi_user",userName,pwd);
每一个数据库都须要实现Driver接口,经过Driver可得到数据库链接Connection,经过反射机制动态建立。api
Class.forName("com.mysql.jdbc.Drier");
同一个程序可能访问不一样的数据库,经过DriverManager来管理驱动,Driver在初始化的时候,须要注册到DriverManager中。缓存
DriverManager提供了一个getConnection方法,用于创建数据库Connection:
Connection con=DriverManager.getConnection("127.0.0.1",3306,"mi_user",userName,pwd);
若是有多个数据库驱动,DriverManager如何区分呢,须要在数据库链接url中指定,好比mysql须要添加jdbc:mysql前缀:
String url= "jdbc:mysql://127.0.0.1:3306/mi_user";
Connection con=DriverManager.getConnection(url,userName,pwd)
数据源DataSource包含链接池和链接池管理2个部分,习惯上称为链接池。在系统初始化的时候,将数据库链接做为对象存储在内存中,当须要访问数据库时,从链接池中取出一个已创建的空闲链接对象。
使用数据源,获取其DataSource对象,经过该对象动态的获取数据库链接。另外,DataSource对象能够注册到名字服务(JNDI)中,能够经过名字服务得到DataSource对象,无需硬性编码驱动。
DriverManager是JDBC1提供的,DataSource是JDBC2新增的功能,提供了更好的链接数据源的方法。
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource); TransactionStatus status = txManager.getTransaction(def); try { //get jdbc connection... //execute sql... } catch (Exception e) { txManager.rollback(status); throw e; } txManager.commit(status);
能够看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring处理事务的核心api,分别对应事务的开始,提交和回滚。
spring事务处理的一个关键是保证在整个事务的生命周期里全部执行sql的jdbc connection和处理事务的jdbc connection始终是同一个。而后执行sql的业务代码通常都分散在程序的不一样地方,如何让它们共享一个jdbc connection呢?这里spring作了一个前提假设:即一个事务的操做必定是在一个thread中执行,且一个thread中若是有多个不一样jdbc connection生成的事务的话,他们必须顺序执行,不能同时存在。(这个假设在绝大多数状况下都是成立的)。基于这个假设,spring在transaction建立时,会用ThreadLocal把建立这个事务的jdbc connection绑定到当前thread,接下来在事务的整个生命周期中都会从ThreadLocal中获取同一个jdbc connection。
咱们看一下详细调用过程
对spring jdbc的事务处理有了了解后,咱们来看mybatis是如何经过spring处理事务的。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="transactionFactory"> <bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" /> </property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
而后看其调用过程
能够看到mybatis-spring处理事务的主要流程和spring jdbc处理事务并无什么区别,都是经过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,并且jdbc connection的建立也是经过DataSourceTransactionManager.getTransaction()完成,mybatis并无参与其中,mybatis只是在执行sql时经过DataSourceUtils.getConnection()得到当前thread的jdbc connection,而后在其上执行sql。
下面结合代码来看
<SqlSessionUtils>: public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); } holder = new SqlSessionHolder(session, executorType, exceptionTranslator); TransactionSynchronizationManager.bindResource(sessionFactory, holder); TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } }
执行sql时调用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一个代理类,它经过SqlSessionUtils.getSqlSession()试图从ThreadLocal获取当前事务所使用的SqlSession。若是是第一次获取时会调用SqlSessionFactory.openSession()建立一个SqlSession并绑定到ThreadLocal,同时还会经过TransactionSynchronizationManager注册一个SqlSessionSynchronization。
<SqlSessionSynchronization>: public void beforeCommit(boolean readOnly) { // Connection commit or rollback will be handled by ConnectionSynchronization or // DataSourceTransactionManager. // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so // they are actually executed. // SpringManagedTransaction will no-op the commit over the jdbc connection // TODO This updates 2nd level caches but the tx may be rolledback later on! if (TransactionSynchronizationManager.isActualTransactionActive()) { try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); } this.holder.getSqlSession().commit(); } catch (PersistenceException p) { if (this.holder.getPersistenceExceptionTranslator() != null) { DataAccessException translated = this.holder .getPersistenceExceptionTranslator() .translateExceptionIfPossible(p); if (translated != null) { throw translated; } } throw p; } }
SqlSessionSynchronization是一个事务生命周期的callback接口,mybatis-spring经过SqlSessionSynchronization在事务提交和回滚前分别调用DefaultSqlSession.commit()和DefaultSqlSession.rollback()
<BaseExecutor>: public void commit(boolean required) throws SQLException { if (closed) throw new ExecutorException("Cannot commit, transaction is already closed"); clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } public void rollback(boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true); } finally { if (required) { transaction.rollback(); } } } } public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
<SpringManagedTransaction>: this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.connection + "]"); } this.connection.commit(); } } public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]"); } this.connection.rollback(); } }
<DataSourceUtils>: /** * Determine whether the given JDBC Connection is transactional, that is, * bound to the current thread by Spring's transaction facilities. * @param con the Connection to check * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @return whether the Connection is transactional */ public static boolean isConnectionTransactional(Connection con, DataSource dataSource) { if (dataSource == null) { return false; } ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); return (conHolder != null && connectionEquals(conHolder, con)); }
这里的DefaultSqlSession只会进行一些自身缓存的清理工做,并不会真正提交事务给数据库,缘由是这里的DefaultSqlSession使用的Transaction实现为SpringManagedTransaction,SpringManagedTransaction在提交事务前会检查当前事务是否应该由spring控制,若是是,则不会本身提交事务,而将提交事务的任务交给spring,因此DefaultSqlSession并不会本身处理事务。
<SpringManagedTransaction>: public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } /** * Gets a connection from Spring transaction manager and discovers if this * {@code Transaction} should manage connection or let it to Spring. * <p> * It also reads autocommit setting because when using Spring Transaction MyBatis * thinks that autocommit is always false and will always call commit/rollback * so we need to no-op that calls. */ 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"); } }
DefaultSqlSession执行sql时,会经过SpringManagedTransaction调用DataSourceUtils.getConnection()从ThreadLocal中获取jdbc connection并在其上执行sql。
总结:mybatis-spring处理事务的主要流程和spring jdbc处理事务并无什么区别,都是经过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,并且jdbc connection的建立也是经过DataSourceTransactionManager.getTransaction()完成,mybatis并无参与其中,mybatis只是在执行sql时经过DataSourceUtils.getConnection()得到当前thread的jdbc connection,而后在其上执行sql。
mybatis-spring作的最主要的事情是: