spring學習筆記(22)聲明式事務配置,readOnly無效寫無異常
在上一節內容中。我們使用了編程式方法來配置事務,這種優點是我們對每一個方法的控制性非常強。比方我須要用到什麽事務,在什麽位置假設出現異常須要回滾等。能夠進行非常細粒度的配置。但在實際開發中。我們可能並不須要這樣細粒度的配置。
還有一方面,假設我們的項目非常大,service層方法非常多。單獨為每一個方法配置事務也是一件非常繁瑣的事情。並且也可能會造成大量反復代碼的冗雜堆積。面對這些缺點,我們首要想到的就是我們spring中的AOP了。
spring聲明式事務的實現恰建立在AOP之上。
在這一篇文章中。我們介紹spring的聲明式事務配置。
實例分析
聲明式事務配置原理相當於使用了圍繞增強,攔截目標方法,在其調用前織入我們的事務。然後在調用結束依據執行情況提交或回滾事務。通過橫切的邏輯,能夠讓我們的service層更專註於自身業務邏輯的處理而免去繁瑣的事務配置。
配置聲明式事務的核心在於配置我們的TransactionProxyFactoryBean和BeanNameAutoProxyCreator。先看以下一個實例配置
事務核心類配置
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" /><!-- 指定一個事務管理器-->
<property name="transactionAttributes"><!-- 配置事務屬性 `-->
<props>
<prop key="add*" >PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,+Exception</prop>
<prop key="delete*"> PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"><!-- 配置須要代理的Bean-->
<list>
<value>myBaseServiceImpl</value>
</list>
</property>
<property name="interceptorNames"><!-- 聲明攔截器-->
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 測試用到的相關依賴-->
<bean id="myBaseDao" class="com.yc.dao.MyBaseDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="myBaseServiceImpl" class="com.yc.service.MyBaseServiceImpl">
<property name="myBaseDao" ref="myBaseDao" />
</bean>
屬性具體分析
在實例中我們通過配置攔截器和代理生成器。
在配置TransactionInterceptor事務屬性時,key相應於方法名,我們以add*來匹配目標類中所有以add開頭的方法,在針對目標對象類的方法進行攔截配置事務時。我們依據屬性的定義順序攔截,假設它被key="add*"
所在事務屬性攔截,即使後面有key="*"能夠匹配隨意方法,也不會再次被攔截。
關於標簽內的事務屬性格式例如以下:
傳播行為 [。隔離級別] [,僅僅讀屬性] [。超時屬性] [,-Exception] [,+Exception]
其中除了傳播行為外,其它都是可選的。
每一個屬性說明可見下表
屬性 | 說明 |
---|---|
傳播行為 | 取值必須以“PROPAGATION_”開頭,具體包含:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七種取值。 |
隔離級別 | 取值必須以“ISOLATION_”開頭,具體包含:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE。共五種取值。 |
僅僅讀屬性 | 假設事務是僅僅讀的,那麽我們能夠指定僅僅讀屬性,使用“readOnly”指定。 否則我們不須要設置該屬性。 |
超時屬性 | 取值必須以“TIMEOUT_”開頭,後面跟一個int類型的值。表示超時時間,單位是秒。 |
+Exception | 即使事務中拋出了這些類型的異常,事務仍然正常提交。必須在每一個異常的名字前面加上“+”。異常的名字能夠是類名的一部分。比方“+RuntimeException”、“+tion”等等。可同一時候指定多個。如+Exception1,+Exception2 |
-Exception | 當事務中拋出這些類型的異常時。事務將回滾。必須在每一個異常的名字前面加上“-”。異常的名字能夠是類名的所有或者部分。比方“-RuntimeException”、“-tion”等等。可同一時候指定多個,如-Exception1,-Exception2 |
從配置文件裏能夠看出,我們能夠配置多個攔截器和多個Bean來適配不同的事務。這種聲明式事務使用起來還是非常方便的。
service層配置
使用聲明式事務後,相對於上篇文章樣例。我們的service層需改寫成:
public class MyBaseServiceImpl implements MyBaseService{
private MyBaseDao myBaseDao;
@Override
public void queryUpdateUser(final Integer id,final String newName) {
User user = myBaseDao.queryUnique(User.class, id);
System.out.println(user);
user.setName(newName);
myBaseDao.update(user);
System.out.println(user);
}
public void setMyBaseDao(MyBaseDao myBaseDao) {
this.myBaseDao = myBaseDao;
}
}
可見,我們去除了事務模板的侵入式註入,同一時候還去除了事務(在每一個方法中的)侵入式配置。當然,編程式事務的優點是能將事務配置細粒度到每一個方法其中。。當我們大部分方法的事務還是一致的。我們能夠使用聲明式事務。針對那些須要獨立配置的,我們能夠將其排除出聲明式事務,然後使用編程式事務或後面我們會提到的註解式事務單獨配置。
測試結果和分析
以下,執行我們同樣的測試方法:
public class Test1 {
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-datasource.xml");
MyBaseServiceImpl myBaseService= (MyBaseServiceImpl) ac.getBean("myBaseServiceImpl");
myBaseService.queryUpdateUser(1, "newName2");
}
}
執行測試方法,會發現報錯:
java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to com.yc.service.MyBaseServiceImpl
意思是我們的代理類無法轉換成我們自己定義的Service實現類。究其原因,是由於我們的BeanNameAutoProxyCreator沒有默認使用CGLib代理,這樣我們的代理類是利用JDK動態代理基於接口創建的。而非基於類創建,我們有以下兩種解決方法:
1. 將代理類轉換成MyBaseServiceImpl所實現的接口MyBaseService而非MyBaseServiceImpl:
MyBaseService myBaseService= (MyBaseService) ac.getBean("myBaseServiceImpl");
2. 在BeanNameAutoProxyCreator配置下加入:
<property name="proxyTargetClass" value="true"/>
,即
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
<property name="beanNames">
<list>
<value>myBaseServiceImpl</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
然後,再執行測試程序,我們會得到正確的結果,部分信息打印例如以下所看到的:
DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtaining JDBC connection
DEBUG: org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtained JDBC connection
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin
DEBUG: org.hibernate.loader.Loader - Done entity load
User [id=1, name=newName]
User [id=1, name=newName2]
DEBUG: org.springframework.orm.hibernate4.HibernateTransactionManager - Initiating transaction commit
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - committing
這和我們使用編程式事務的結果基本是一致的。
拓展測試
如今。在我們的攔截器中略微改動一行:
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
我們將其設置為僅僅讀模式,這時候。調用我們的測試方法。queryUpdateUser(1,”newName3”)(由於前面測試已將name改動成newName2,為了顯示不同的結果,這裏射程newName3做參數)。顯然。前面的add*,update*,delete*
都不能匹配。這時候必然啟動key="*"
所屬事務。執行方法,我們會發現結果:
User [id=1, name=newName2]
User [id=1, name=newName3]
這似乎和我們沒設置readOnly應有的結果一致,但我們再次執行,程序沒有拋出異常,並且會發現結果仍是:
User [id=1, name=newName2]
User [id=1, name=newName3]
說明我們的改動實際上並沒有生效!這時在看DEBUG信息,發如今:
DEBUG: org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin信息上面多了一行:
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Setting JDBC Connection [jdbc:mysql://localhost:3306/yc, [email protected], MySQL Connector Java] read-only
說明當前事務確實為僅僅讀模式
歸納
這裏單獨拿出readOnly來分析。主要是針對實際開發中可能遇到的麻煩。設想我們哪天僅僅讀屬性配置錯了。但我們沒發現,而當我們試圖進行相應的寫數據操作時,發現程序並沒有出現異常,但數據不管怎麽都寫不進去。
這個時候就要好好看看我們的僅僅讀屬性有沒有跑到它不該到的地方去了!
spring學習筆記(22)聲明式事務配置,readOnly無效寫無異常