【分佈式事務系列三】Spring的事務體系

#0 系列目錄#spring

#1 三種事務模型#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();
  1. **編程式事務模型:**事務的提交和回滾操做徹底交給開發人員,開發人員來決定事務何時提交或回滾,因此開發人員是事務的管理者。
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;
}
  1. **聲明式事務模型:**開發人員徹底不用關心事務,事務的提交和回滾操做所有交給Spring來管理,因此Spring是事務的管理者
@Transactional
public void save(User user){
    jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge());
}

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

##2.1 Spring的TransactionTemplate## 在前一篇文章中瞭解到,使用jdbc操做事務,編程很是麻煩,總是須要寫一套模板式的try catch代碼,因此咱們能夠將try catch代碼封裝成一個模板,這就引出了Spring的TransactionTemplate:express

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)
        return null;
    }
});
  1. TransactionTemplate繼承了DefaultTransactionDefinition,有了默認的事務定義,也能夠自定義設置隔離級別、傳播屬性等。
  2. TransactionTemplate須要一個PlatformTransactionManager事務管理器,來執行事務的操做。
  3. TransactionTemplate在TransactionCallback中執行業務代碼,try catch的事務模板代碼,則被封裝起來,包裹在業務代碼的周圍,詳細見TransactionTemplate的execute方法,以下:

輸入圖片說明

詳細過程以下:編程

  • 第一步:根據事務定義獲取事務

因爲TransactionTemplate繼承了DefaultTransactionDefinition,因此使用PlatformTransactionManager事務管理器來根據TransactionTemplate來獲取事務,這部分就是上一篇文章的內容了,見Spring事務管理器PlatformTransactionManager源碼分析session

  • 第二步:執行業務代碼

在TransactionCallback中的doInTransaction中執行相應的業務代碼。分佈式

若是使用的是DataSourceTransactionManager,你就可使用JdbcTemplate來執行業務邏輯;或者直接使用Connection,可是必須使用DataSourceUtils來獲取Connectionide

若是使用的是HibernateTransactionManager,就可使用HibernateTemplate來執行業務邏輯,或者則可使用SessionFactory的getCurrentSession方法來獲取當前線程綁定的Session,不可以使用SessionFactory的openSession方法。也是不可亂用的,下面詳細解釋。源碼分析

  • 第三步:若是業務代碼出現異常,則回滾事務,沒有異常則提交事務。回滾與提交都是經過PlatformTransactionManager事務管理器來進行的。

##2.2 事務代碼和業務代碼能夠實現分離的原理## 咱們能夠看到,使用TransactionTemplate,其實就作到了事務代碼和業務代碼的分離,分離以後的代價就是,必須保證他們使用的是同一類型事務。以後的聲明式事務實現分離也是一樣的原理,這裏就提早說明一下。this

  1. 若是使用DataSourceTransactionManager:

1.1 事務代碼是經過和當前線程綁定的ConnectionHolder中的Connection的commit和rollback來執行相應的事務,詳見上一篇文章說明事務管理器的事務分析,因此咱們必需要保證業務代碼也是使用相同的Connection,這樣才能正常回滾與提交。

1.2 業務代碼使用jdbcTemplate.update(sql)來執行業務,這個方法是使用的Connection從哪來的?是和上面的事務Connection是同一個嗎?源碼以下:

jdbcTemplate在執行sql時,會使用DataSourceUtils從dataSource中獲取一個Connection。獲取過程以下:

輸入圖片說明

也是先獲取和當前線程綁定的ConnectionHolder(因爲事務在執行業務邏輯前已經開啓,已經有了和當前線程綁定的ConnectionHolder),因此會獲取到和事務中使用的ConnectionHolder,這樣就保證了他們使用的是同一個Connection了,天然就能夠正常提交和回滾了。

若是想使用Connection,則須要使用DataSourceUtils從dataSorce中獲取Connection,不能直接從dataSource中獲取Connection。

  1. 若是使用HibernateTransactionManager:

2.1 事務代碼是經過和當前線程綁定的SessionHolder中的Session中的Transaction的commit和rollback來執行相應的事務,詳見上一篇文章說明事務管理器的事務分析,因此咱們必需要保證業務代碼也是使用相同的session。

2.2業務代碼就不能使用jdbcTemplate來執行相應的業務邏輯了,須要使用Session來執行相應的操做,換成對應的HibernateTemplate來執行。

HibernateTemplate在執行save(user)的過程當中,會獲取一個Session,方式以下:

session = getSessionFactory().getCurrentSession();

即採用SessionFactory的自帶的getCurrentSession方法,獲取當前Session。具體什麼策略呢?

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方法。

以上分析都是基於Hibernate4,Hibenrate3的原理自行去看,都差很少。

#3 Spring的聲明式事務# Spring能夠有三種形式來配置事務攔截,不一樣配置形式僅僅是外在形式不一樣,裏面的攔截原理都是同樣的,因此先經過一個小例子瞭解利用AOP實現事務攔截的原理。

##3.1 利用AOP實現聲明式事務的原理## ###3.1.1 簡單的AOP事務例子### 從上面的Spring的TransactionTemplate咱們能夠實現了,事務代碼和業務代碼的分離,可是分離的還不完全,仍是須要以下的代碼:

TransactionTemplate template=new TransactionTemplate();
template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.setTransactionManager(transactionManager);

一大堆的設置,同時業務代碼必須嵌套在TransactionCallback中,如何才能作到以下方式的完全分離呢?

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

這就須要使用Spring的AOP機制,SpringAOP的原理就再也不詳細說明了,能夠參考這裏的源碼分析Spring 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);

        TransactionInterceptor transactionInterceptor=new TransactionInterceptor();
        transactionInterceptor.setTransactionManager(transactionManager);
        Properties properties=new Properties();
        properties.setProperty("*","PROPAGATION_REQUIRED");
        transactionInterceptor.setTransactionAttributes(properties);

        proxyFactory.addAdvice(transactionInterceptor);
        proxyUserDao=(UserDao) proxyFactory.getProxy();
    }

    public void save(User user){
        proxyUserDao.save(user);
    }
}

代碼分析以下:

  1. 首先須要一個原始的UserDao,咱們須要對它進行AOP代理,產生代理對象proxyUserDao,以後保存的功能就是使用proxyUserDao來執行
  2. 對UserDao具體的代理過程以下:
  • 使用代理工廠,設置要代理的對象,即target
proxyFactory.setTarget(userDao);
  • 對代理對象加入攔截器:分紅2種狀況,一種默認攔截原UserDao的全部方法,一種是指定Pointcut,即攔截原UserDao的某些方法。

這裏使用proxyFactory.addAdvice(transactionInterceptor);就表示默認攔截原UserDao的全部方法。

若是使用proxyFactory.addAdvisor(advisor),這裏的Advisor能夠簡單當作是Pointcut和Advice的組合,Pointcut則是用於指定是否攔截某些方法。

上述addAdvice就是使用了默認的Pointcut,表示對全部方法都攔截,源碼以下:

addAdvisor(pos, new DefaultPointcutAdvisor(advice));

DefaultPointcutAdvisor內容以下:

public DefaultPointcutAdvisor(Advice advice) {
      this(Pointcut.TRUE, advice); // Pointcut.TRUE便表示攔截全部方法。
}
  • 設置好代理工廠要代理的對象和攔截器後,即可以建立代理對象。
proxyUserDao=(UserDao) proxyFactory.getProxy();

以後,咱們在使用建立出的proxyUserDao時,就會首先進入攔截器,執行相關攔截器代碼,所以咱們能夠在這裏實現事務的處理。

###3.1.2 事務攔截器的原理分析### 事務攔截器須要2個參數:

  1. **事務配置的提供者:**用於指定哪些方法具備什麼樣的事務配置。能夠經過屬性配置方式,或者經過其餘一些配置方式,以下三種方式都是爲了獲取事務配置提供者:
  • 方式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);
  1. **事務管理器PlatformTransactionManager:**有了事務的配置,咱們就能夠經過事務管理器來獲取事務了。

在執行代理proxyUserDao的save(user)方法時,會先進入事務攔截器中,具體的攔截代碼以下:

輸入圖片說明

第一步:首先獲取所執行方法的對應的事務配置

第二步:而後獲取指定的事務管理器PlatformTransactionManager

第三步:根據事務配置,使用事務管理器建立出事務

第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法

第五步:一旦執行過程發生異常,使用事務攔截器進行事務的回滾

第六步:若是沒有異常,則使用事務攔截器提交事務

##3.2 Spring的三種事務配置形式## ###3.2.1 使用TransactionProxyFactoryBean### 配置案例以下:

<bean id="proxy"
   class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!-- 爲事務代理工廠Bean注入事務管理器 -->
    <property name="transactionManager" ref="transactionManager" />
    <!-- 要在哪一個Bean上面建立事務代理對象 -->
    <property name="target" ref="productDao" />
    <!-- 指定事務屬性 -->
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

上面有三大配置:

  • 事務管理器transactionManager
  • 事務配置的提供者transactionAttributes(用於指定哪些方法具備什麼樣的事務配置)

有了以上2個元素,咱們就能夠建立出一個事務攔截器TransactionInterceptor

  • 要代理的對象target

TransactionProxyFactoryBean這個工廠bean建立代理對象的原理就是:經過ProxyFactory來對target建立出代理對象了,同時加入上述事務攔截器,就能夠實現事務攔截功能了。

###3.2.2 使用aop:config和tx:advice### 使用TransactionProxyFactoryBean的方式只能針對一個target進行代理,若是想再代理一個target,就須要再配置一個TransactionProxyFactoryBean,比較麻煩,因此使用apo:config的配置形式,就能夠針對符合Pointcut的全部target均可以進行代理。

<tx:advice id="txAdvice" transaction-manager="transactionManager">  
    <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>  
</tx:advice>  

<aop:config>
    <aop:pointcut id="pc" expression="execution(public * com.qding..service.*.*(..))" />
    <aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />
</aop:config>
  • tx:advice:有事務管理器transactionManager和事務配置提供者attributes,就能夠產生一個事務攔截器TransactionInterceptor。
  • aop:config:這裏會對符合pointcut的bean建立出代理對象,同時加入上述建立的事務攔截器。

###3.2.3 使用@Transactional### 使用aop:config能夠在xml中進行代理的配置,有時候想在代碼中直接進行配置,這時候就須要使用註解@Transactional。

xml中啓動@Transactional註解掃描:

<tx:annotation-driven transaction-manager="transactionManager" />

在代碼中就能夠經過配置@Transactional來實現事務攔截了:

@Transactional(propagation=Propagation.REQUIRED)
public void save(User user){
    xxxx
}

在xml配置中啓動註解掃描,會把那些加入了@Transactional標記的容器bean建立出代理對象,同時加入事務攔截器。在執行事務攔截的時候,會從@Transactional註解中取出對應的事務配置和事務管理器配置,進而能夠執行事務的建立等操做。

相關文章
相關標籤/搜索