分佈式事務系列(1.2)Spring的事務體系

#1 系列目錄spring

#2 三種事務模型sql

三種事務模型以下:數據庫

  • 本地事務模型
  • 編程式事務模型
  • 聲明式事務模型

先來看幾個例子:express

案例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();
}

案例3:session

InitialContext ctx = new InitialContext();
UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction");
try {
   txn.begin();
	//業務代碼				
   txn.commit();
} catch (Exception up) {
   txn.rollback();
   throw up;
}

案例4:框架

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

個人見解:分佈式

  • 案例1屬於本地事務模型
  • 案例二、3屬於編程式事務模型
  • 案例4屬於聲明式事務模型

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

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

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

上述的特色也不是所有合理,以下文提到的Spring的TransactionTemplate,雖然屬於編程式事務,可是它的確是把事務的提交和回滾交給了Spring來管理。總之不用過度糾結於劃分事務模型

#3 編程式事務

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

##3.1 Spring的TransactionTemplate

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

案例以下:

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;
	}
	
});
  • TransactionTemplate繼承了DefaultTransactionDefinition,有了默認的事務定義,也能夠自定義設置隔離級別、傳播屬性等

  • TransactionTemplate須要一個PlatformTransactionManager事務管理器,來執行事務的操做

  • TransactionTemplate在TransactionCallback中執行業務代碼,try catch的事務模板代碼,則被封裝起來,包裹在業務代碼的周圍,詳細見TransactionTemplate的execute方法,以下:

    Spring的事務模板代碼

詳細過程以下:

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

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

  • 第二步:執行業務代碼

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

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

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

  • 第三步:若是業務代碼出現異常,則回滾事務,沒有異常則提交事務

    回滾與提交都是經過PlatformTransactionManager事務管理器來進行的

##3.2 事務代碼和業務代碼能夠實現分離的原理

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

  • 1 若是使用DataSourceTransactionManager:

    • 1.1 事務代碼是經過和當前線程綁定的ConnectionHolder中的Connection的commit和rollback來執行相應的事務,詳見上一篇文章說明事務管理器的事務分析,因此咱們必需要保證業務代碼也是使用相同的Connection,這樣才能正常回滾與提交。
    • 1.2 業務代碼使用jdbcTemplate.update(sql)來執行業務,這個方法是使用的Connection從哪來的?是和上面的事務Connection是同一個嗎?源碼以下:

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

    jdbcTemplate獲取Connection

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

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

  • 2 若是使用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的實現。它的實現以下:

    Hibernate獲取當前Session的策略

    在spring環境下,默認採用的是SpringSessionContext,它獲取當前Session的方式以下:

    SpringSessionContext獲取當前Session

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

    若是不想經過使用HibernateTemplate,想直接經過Session來操做,同理則須要使用SessionFactory的getCurrentSession方法來獲取Session,而不能使用SessionFactory的openSession方法。

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

#4 Spring的聲明式事務

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

##4.1 利用AOP實現聲明式事務的原理

###4.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);
	}
}

代碼分析以下:

  • 首先須要一個原始的UserDao,咱們須要對它進行AOP代理,產生代理對象proxyUserDao,以後保存的功能就是使用proxyUserDao來執行

  • 對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時,就會首先進入攔截器,執行相關攔截器代碼,所以咱們能夠在這裏實現事務的處理

###4.1.2 事務攔截器的原理分析

事務攔截器須要2個參數:

  • 事務配置的提供者

    用於指定哪些方法具備什麼樣的事務配置

    能夠經過屬性配置方式,或者經過其餘一些配置方式,以下三種方式都是爲了獲取事務配置提供者:

    • 方式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事務攔截器代碼

  • 第一步:首先獲取所執行方法的對應的事務配置
  • 第二步:而後獲取指定的事務管理器PlatformTransactionManager
  • 第三步:根據事務配置,使用事務管理器建立出事務
  • 第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法
  • 第五步:一旦執行過程發生異常,使用事務攔截器進行事務的回滾
  • 第六步:若是沒有異常,則使用事務攔截器提交事務

一個事務攔截器的內容大體就是這樣。

##4.2 Spring的三種事務配置形式

###4.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建立出代理對象了,同時加入上述事務攔截器,就能夠實現事務攔截功能了

###4.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="pointcut" 
        expression="XXXX" />  
    <aop:advisor advice-ref="txAdvice" 
        pointcut-ref="pointcut" />  
</aop:config>

這裏有2種配置:

  • tx:advice:

    有事務管理器transactionManager和事務配置提供者attributes,就能夠產生一個事務攔截器TransactionInterceptor

  • aop:config:

    這裏會對符合pointcut的bean建立出代理對象,同時加入上述建立的事務攔截器

###4.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註解中取出對應的事務配置和事務管理器配置,進而能夠執行事務的建立等操做。

#5 結束語

至此Spring的事務體系大概就講完了,接下來就要說明如何實現分佈式事務了,在涉及jotm、atomikos這些第三方框架以前,先要了解下,分佈式事務的一些概念

  • 1 X/Open DTP模型、XA規範、2PC
  • 2 JTA、JTS概念
  • 3 JTA接口定義的理解

歡迎關注微信公衆號:乒乓狂魔

微信公衆號

相關文章
相關標籤/搜索