天天看點

spring學習筆記(22)聲明式事務配置,readOnly無效寫無異常執行個體分析歸納

在上一節内容中,我們使用了程式設計式方法來配置事務,這樣的優點是我們對每個方法的控制性很強,比如我需要用到什麼事務,在什麼位置如果出現異常需要復原等,可以進行非常細粒度的配置。但在實際開發中,我們可能并不需要這樣細粒度的配置。另一方面,如果我們的項目很大,service層方法很多,單獨為每個方法配置事務也是一件很繁瑣的事情。而且也可能會造成大量重複代碼的冗雜堆積。面對這些缺點,我們首要想到的就是我們spring中的aop了。spring聲明式事務的實作恰建立在aop之上。

在這一篇文章中,我們介紹spring的聲明式事務配置。

聲明式事務配置原理相當于使用了環繞增強,攔截目标方法,在其調用前織入我們的事務,然後在調用結束根據執行情況送出或復原事務。通過橫切的邏輯,能夠讓我們的service層更專注于自身業務邏輯的處理而免去繁瑣的事務配置。

配置聲明式事務的核心在于配置我們的transactionproxyfactorybean和beannameautoproxycreator。先看下面一個執行個體配置

在執行個體中我們通過配置攔截器和代理生成器。在配置transactioninterceptor事務屬性時,key對應于方法名,我們以add*來比對目标類中所有以add開頭的方法,在針對目标對象類的方法進行攔截配置事務時,我們根據屬性的定義順序攔截,如果它被<code>key="add*"</code>所在事務屬性攔截,即使後面有<code>key="*"可以比對任意方法,也不會再次被攔截。</code>關于标簽内的事務屬性格式如下:

<code>傳播行為 [,隔離級别] [,隻讀屬性] [,逾時屬性] [,-exception] [,+exception]</code>

其中除了傳播行為外,其他都是可選的。每個屬性說明可見下表

屬性

說明

傳播行為

取值必須以“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層需改寫成:

可見,我們去除了事務模闆的侵入式注入,同時還去除了事務(在每一個方法中的)侵入式配置。當然,程式設計式事務的好處是能将事務配置細粒度到每個方法當中。。當我們大部分方法的事務還是一緻的,我們可以使用聲明式事務,針對那些需要獨立配置的,我們可以将其排除出聲明式事務,然後使用程式設計式事務或後面我們會提到的注解式事務單獨配置。

下面,運作我們相同的測試方法:

運作測試方法,會發現報錯:

java.lang.classcastexception: com.sun.proxy.$proxy8 cannot be cast to com.yc.service.mybaseserviceimpl 意思是我們的代理類無法轉換成我們自定義的service實作類。究其原因,是因為我們的beannameautoproxycreator沒有預設使用cglib代理,這樣我們的代理類是利用jdk動态代理基于接口建立的,而非基于類建立,我們有以下兩種解決方法: 1. 将代理類轉換成mybaseserviceimpl所實作的接口mybaseservice而非mybaseserviceimpl: <code>mybaseservice mybaseservice= (mybaseservice) ac.getbean("mybaseserviceimpl");</code> 2. 在beannameautoproxycreator配置下添加: <code>&lt;property name="proxytargetclass" value="true"/&gt;</code>,即

然後,再運作測試程式,我們會得到正确的結果,部分列印資訊如下所示:

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 這和我們使用程式設計式事務的結果基本是一緻的。

現在,在我們的攔截器中稍微修改一行:

<code>&lt;prop key="*"&gt;propagation_required,readonly&lt;/prop&gt;</code>

我們将其設定為隻讀模式,這時候,調用我們的測試方法,queryupdateuser(1,”newname3”)(因為前面測試已将name修改成newname2,為了顯示不同的結果,這裡射程newname3做參數)。顯然,前面的<code>add*,update*,delete*</code>都不能比對。這時候必定啟動<code>key="*"</code>所屬事務。運作方法,我們會發現結果:

user [id=1, name=newname3] 這似乎和我們沒設定readonly應有的結果一緻,但我們再次運作,程式沒有抛出異常,而且會發現結果仍是: 說明我們的修改實際上并沒有生效!這時在看debug資訊,發現在: debug: org.hibernate.engine.transaction.spi.abstracttransactionimpl - begin資訊上面多了一行: debug: org.springframework.jdbc.datasource.datasourceutils - setting jdbc connection [jdbc:mysql://localhost:3306/yc, username=yc@localhost, mysql connector java] read-only 說明目前事務确實為隻讀模式

這裡單獨拿出readonly來分析,主要是針對實際開發中可能遇到的麻煩。設想我們哪天隻讀屬性配置錯了。但我們沒發現,而當我們試圖進行相應的寫資料操作時,發現程式并沒有出現異常,但資料無論怎麼都寫不進去。這個時候就要好好看看我們的隻讀屬性有沒有跑到它不該到的地方去了!