spring---transaction(5)---事務的體系


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
  • 第三步:根據事務配置,使用事務管理器創建出事務
  • 第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法
  • 第五步:一旦執行過程發生異常,使用事務攔截器進行事務的回滾
  • 第六步:如果沒有異常,則使用事務攔截器提交事務

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM