1.寫在前面
事務的模型為3中:
本地事務模式。
編程事務模式。
聲明事務模式。
例子1:本地事務模式
Connection conn=jdbcDao.getConnection(); PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute();
案例2:編程事務模式
Connection conn=jdbcDao.getConnection(); conn.setAutoCommit(false); try { PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute(); conn.commit(); } catch (Exception e) { e.printStackTrace(); conn.rollback(); }finally{ conn.close(); }
InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction"); try { txn.begin(); //業務代碼 txn.commit(); } catch (Exception up) { txn.rollback(); throw up; }
案例3:聲明事務模式
@Transactional public void save(User user){ jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge()); }
我認為他們各自的特點在於:誰在管理着事務的提交和回滾等操作?
這里有三個角色:數據庫、開發人員、spring(等第三方)
- 對於案例1:開發人員不用知道事務的存在,事務全部交給數據庫來管理,數據庫自己決定什么時候提交或回滾,所以數據庫是事務的管理者
- 對於案例2、3:事務的提交和回滾操作完全交給開發人員,開發人員來決定事務什么時候提交或回滾,所以開發人員是事務的管理者
- 對於案例4:開發人員完全不用關心事務,事務的提交和回滾操作全部交給Spring來管理,所以Spring是事務的管理者
2.編程式事務
編程式事務:即通過手動編程方式來實現事務操作,大部分情況,都是類似於上述案例2情況,開發人員來管理事務的提交和回滾,但也可能是Spring自己來管理事務,如Spring的TransactionTemplate。
Spring的TransactionTemplate 封裝了對於數據庫的操作(使用jdbc操作事務,編程非常麻煩,老是需要寫一套模板式的try catch代碼)
TransactionTemplate template=new TransactionTemplate(); template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); template.setTransactionManager(transactionManager); template.execute(new TransactionCallback<User>() { @Override public User doInTransaction(TransactionStatus status) { //可以使用DataSourceUtils獲取Connection來執行sql //jdbcTemplate.update(sql2); //可以使用SessionFactory的getCurrentSession獲取Session來執行 //hibernateTemplate.save(user1)
//可以使用myBatis的sqlSessionTemplate
//simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table); return null; } });
如果使用的是DataSourceTransactionManager,你就可以使用jdbc對應的JdbcTemplate或者myBatis對應的simpleTempalte來執行業務邏輯;或者直接使用Connection,但是必須使用DataSourceUtils來獲取Connection
如果使用的是HibernateTransactionManager,就可以使用HibernateTemplate來執行業務邏輯,或者則可以使用SessionFactory的getCurrentSession方法來獲取當前線程綁定的Session
- TransactionTemplate繼承了DefaultTransactionDefinition,有了默認的事務定義,也可以自定義設置隔離級別、傳播屬性等
- TransactionTemplate需要一個PlatformTransactionManager事務管理器,來執行事務的操作
- TransactionTemplate在TransactionCallback中執行業務代碼,try catch的事務模板代碼,則被封裝起來,包裹在業務代碼的周圍,詳細見TransactionTemplate的execute方法,如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException { if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else {
//由於TransactionTemplate繼承了DefaultTransactionDefinition,所以使用PlatformTransactionManager事務管理器來根據TransactionTemplate來獲取事務 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try {
//在TransactionCallback中的doInTransaction中執行相應的業務代碼。回調 result = action.doInTransaction(status); } catch (RuntimeException ex) { // Transactional code threw application exception -> rollback
//如果業務代碼出現異常,則回滾事務,沒有異常則提交事務,回滾與提交都是通過PlatformTransactionManager事務管理器來進行的 rollbackOnException(status, ex); throw ex; } catch (Error err) { // Transactional code threw error -> rollback rollbackOnException(status, err); throw err; } catch (Exception ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); }
//由transactionManager關於事務的提交 this.transactionManager.commit(status); return result; } }
事務代碼和業務代碼可以實現分離的原理
我們可以看到,使用TransactionTemplate,其實就做到了事務代碼和業務代碼的分離,分離之后的代價就是,必須保證他們使用的是同一類型事務。之后的聲明式事務實現分離也是同樣的原理,這里就提前說明一下。
1 如果使用DataSourceTransactionManager
- 1.1 事務代碼是通過和當前線程綁定的ConnectionHolder中的Connection的commit和rollback來執行相應的事務,所以我們必須要保證業務代碼也是使用相同的Connection,這樣才能正常回滾與提交。
- 1.2 業務代碼使用jdbcTemplate.update(sql)來執行業務,這個方法是使用的Connection從哪來的?是和上面的事務Connection是同一個嗎?源碼如下(jdbcTemplate在執行sql時,會使用DataSourceUtils從dataSource中獲取一個Connection):
JdbcTemplate.java(jdbcTemplate在執行sql時,會使用DataSourceUtils從dataSource中獲取一個Connection)
@Override public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); //使用DataSourceUtils從dataSource中獲取一個Connection Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } T result = action.doInStatement(stmtToUse); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //從當前線程中(TransactionSynchronizationManager管理器)中獲取connection ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } return conHolder.getConnection(); } }
也是先獲取和當前線程綁定的ConnectionHolder(由於事務在執行業務邏輯前已經開啟,已經有了和當前線程綁定的ConnectionHolder),所以會獲取到和事務中使用的ConnectionHolder,這樣就保證了他們使用的是同一個Connection了,自然就可以正常提交和回滾了。
如果想使用Connection,則需要使用DataSourceUtils從dataSorce中獲取Connection,不能直接從dataSource中獲取Connection。
- 1.3 業務代碼使用myBatis管理的simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table);來執行業務,所以我們必須要保證業務代碼也是使用相同的sqlSession?源碼如下:(詳細見myBaits源代碼系列文章)
由於myBatis的實際執行tempalte是simpleTempalte的代理對象,可以看到在SqlSessionInterceptor的invoke方法中是從SqlSessionUtils中獲取sqlSession和
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取sqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); 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); } } }
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //也是從當前線程中(TransactionSynchronizationManager管理器)中獲取SqlSessionHolder SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } //如果沒有獲取到則, 創建已經綁定到TransactionSynchronizationManager session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
2 如果使用HibernateTransactionManager
- 2.1 事務代碼是通過和當前線程綁定的SessionHolder中的Session中的Transaction的commit和rollback來執行相應的事務,詳見上一篇文章說明事務管理器的事務分析,所以我們必須要保證業務代碼也是使用相同的session
- 2.2業務代碼就不能使用jdbcTemplate來執行相應的業務邏輯了,需要使用Session來執行相應的操作,換成對應的HibernateTemplate來執行。
HibernateTemplate在執行save(user)的過程中,會獲取一個Session,方式如下:
session = getSessionFactory().getCurrentSession();
Hibernate定義了這樣的一個接口:CurrentSessionContext,內容如下:
public interface CurrentSessionContext extends Serializable { public Session currentSession() throws HibernateException; }
上述SessionFactory獲取當前Session就是依靠CurrentSessionContext的實現
在spring環境下,默認采用的是SpringSessionContext,它獲取當前Session的方式如下:
也是先獲取和當前線程綁定的SessionHolder(由於事務在執行業務邏輯前已經開啟,已經有了和當前線程綁定的SessionHolder),所以會獲取到和事務中使用的SessionHolder,這樣就保證了他們使用的是同一個Session了,自然就可以正常提交和回滾了。
如果不想通過使用HibernateTemplate,想直接通過Session來操作,同理則需要使用SessionFactory的getCurrentSession方法來獲取Session,而不能使用SessionFactory的openSession方法。
3.Spring的聲明式事務
Spring可以有三種形式來配置事務攔截,不同配置形式僅僅是外在形式不同,里面的攔截原理都是一樣的,所以先通過一個小例子了解利用AOP實現事務攔截的原理
利用AOP實現聲明式事務的原理(簡單的AOP事務例子)
@Repository public class AopUserDao implements InitializingBean{ @Autowired private UserDao userDao; private UserDao proxyUserDao; @Resource(name="transactionManager") private PlatformTransactionManager transactionManager; @Override public void afterPropertiesSet() throws Exception {
//使用代理工廠 ProxyFactory proxyFactory = new ProxyFactory();
//設置代理的目標對象 proxyFactory.setTarget(userDao);
//引入spring的事務攔截器(詳細見spring事務攔截器) TransactionInterceptor transactionInterceptor=new TransactionInterceptor();
//設置事務管理器(詳細見spring事務攔截器) transactionInterceptor.setTransactionManager(transactionManager); Properties properties=new Properties(); properties.setProperty("*","PROPAGATION_REQUIRED");
//設置事務的屬性(詳細見TransactionDefinition ) transactionInterceptor.setTransactionAttributes(properties);
//對代理對象加入攔截器 proxyFactory.addAdvice(transactionInterceptor); proxyUserDao=(UserDao) proxyFactory.getProxy(); } public void save(User user){ proxyUserDao.save(user); } }
代碼分析如下:
- 首先需要一個原始的UserDao,我們需要對它進行AOP代理,產生代理對象proxyUserDao,之后保存的功能就是使用proxyUserDao來執行
- 對UserDao具體的代理過程如下:
- 使用代理工廠,設置要代理的對象 proxyFactory.setTarget(userDao);
- 對代理對象加入攔截器
- 分成2種情況,一種默認攔截原UserDao的所有方法,一種是指定Pointcut,即攔截原UserDao的某些方法。
- 這里使用proxyFactory.addAdvice(transactionInterceptor);就表示默認攔截原UserDao的所有方法。
- 如果使用proxyFactory.addAdvisor(advisor),這里的Advisor可以簡單看成是Pointcut和Advice的組合,Pointcut則是用於指定是否攔截某些方法。
設置好代理工廠要代理的對象和攔截器后,便可以創建代理對象。詳細見spring的事務攔截器(TransactionInterceptor)
- proxyUserDao=(UserDao) proxyFactory.getProxy()
-
- 之后,我們在使用創建出的proxyUserDao時,就會首先進入攔截器,執行相關攔截器代碼,因此我們可以在這里實現事務的處理
事務攔截器的原理分析
事務攔截器需要2個參數:事務配置的提供者、事務管理器PlatformTransactionManager
事務配置的提供者
用於指定哪些方法具有什么樣的事務配置
可以通過屬性配置方式,或者通過其他一些配置方式,如下三種方式都是為了獲取事務配置提供者:
-
- 方式1:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property
-
- 方式2:
<tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> </tx:attributes>
-
- 方式3:
@Transactional(propagation=Propagation.REQUIRED)
事務管理器PlatformTransactionManager
有了事務的配置,我們就可以通過事務管理器來獲取事務了。
在執行代理proxyUserDao的save(user)方法時,會先進入事務攔截器中,具體的攔截代碼如下:(很早之前有過分析這段代碼,spring事務攔截器)
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // 第一步:首先獲取所執行方法的對應的事務配置 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); //第二步:然后獲取指定的事務管理器PlatformTransactionManager final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 第三步:根據事務配置,使用事務管理器創建出事務 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // 第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 第五步:一旦執行過程發生異常,使用事務攔截器進行事務的回滾 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } //第六步:如果沒有異常,則使用事務攔截器提交事務 commitTransactionAfterReturning(txInfo); return retVal; } }
總結:
- 第一步:首先獲取所執行方法的對應的事務配置
- 第二步:然后獲取指定的事務管理器PlatformTransactionManager
- 第三步:根據事務配置,使用事務管理器創建出事務
- 第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法
- 第五步:一旦執行過程發生異常,使用事務攔截器進行事務的回滾
- 第六步:如果沒有異常,則使用事務攔截器提交事務