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

1.寫在前面

  事務的模型爲3中:html

    本地事務模式。java

    編程事務模式。spring

    聲明事務模式。sql

  例子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:聲明事務模式session

@Transactional
public void save(User user){
    jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge());
}

 

 

我認爲他們各自的特色在於:誰在管理着事務的提交和回滾等操做?app

  這裏有三個角色:數據庫、開發人員、spring(等第三方)ide

  • 對於案例1:開發人員不用知道事務的存在,事務所有交給數據庫來管理,數據庫本身決定何時提交或回滾,因此數據庫是事務的管理者
  • 對於案例二、3:事務的提交和回滾操做徹底交給開發人員,開發人員來決定事務何時提交或回滾,因此開發人員是事務的管理者
  • 對於案例4:開發人員徹底不用關心事務,事務的提交和回滾操做所有交給Spring來管理,因此Spring是事務的管理者

 

2.編程式事務

  編程式事務:即經過手動編程方式來實現事務操做,大部分狀況,都是相似於上述案例2狀況,開發人員來管理事務的提交和回滾,但也多是Spring本身來管理事務,如Spring的TransactionTemplateui

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