#0 系列目錄#spring
#1 三種事務模型#sql
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();
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; }
@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; } });
詳細過程以下:編程
因爲TransactionTemplate繼承了DefaultTransactionDefinition,因此使用PlatformTransactionManager事務管理器來根據TransactionTemplate來獲取事務,這部分就是上一篇文章的內容了,見Spring事務管理器PlatformTransactionManager源碼分析。session
在TransactionCallback中的doInTransaction中執行相應的業務代碼。分佈式
若是使用的是DataSourceTransactionManager,你就可使用JdbcTemplate來執行業務邏輯;或者直接使用Connection,可是必須使用DataSourceUtils來獲取Connectionide
若是使用的是HibernateTransactionManager,就可使用HibernateTemplate來執行業務邏輯,或者則可使用SessionFactory的getCurrentSession方法來獲取當前線程綁定的Session,不可以使用SessionFactory的openSession方法。也是不可亂用的,下面詳細解釋。源碼分析
##2.2 事務代碼和業務代碼能夠實現分離的原理## 咱們能夠看到,使用TransactionTemplate,其實就作到了事務代碼和業務代碼的分離
,分離以後的代價就是,必須保證他們使用的是同一類型事務。以後的聲明式事務實現分離也是一樣的原理,這裏就提早說明一下。this
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。
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); } }
代碼分析以下:
proxyFactory.setTarget(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個參數:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property>
<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>
@Transactional(propagation=Propagation.REQUIRED);
在執行代理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>
上面有三大配置:
有了以上2個元素,咱們就能夠建立出一個事務攔截器TransactionInterceptor
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>
###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註解中取出對應的事務配置和事務管理器配置,進而能夠執行事務的建立等操做。