事務
事務全稱叫數據庫事務,是數據庫并發控制時的基本單位,它是一個操作集合,這些操作要么不執行,要么都執行,不可分割。例如我們的轉賬這個業務,就需要進行數據庫事務的處理。
轉賬中至少會涉及到兩條 sql 語句:
1
2
|
update acoount set balance = balance - money where id = 'a' ; update acoount set balance = balance + money where id = 'b' |
上面這兩條 sql 就可以要看成是一個事務,必須都執行,或都不執行。如何保證呢,一般這樣表示:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 開啟事務 begin transaction update account set balance = balance - money where id = 'a' ; update account set balance = balance + money where id = 'b' # 提交事務 commit transaction exception # 回滾事務 rollback transaction |
事務的特性(筆試的時候會有)
atomic(原子性):事務中包含的操作被看做一個邏輯單元,這個邏輯單元中的操作要么全部成功,要么全部失敗。
consistency(一致性):只有合法的數據可以被寫入數據庫,否則事務應該將其回滾到最初狀態。在轉賬的時候不會出現一當少錢了,另一方沒有增加的情況。
isolation(隔離性):事務允許多個用戶對同一個數據進行并發訪問,而不破壞數據的正確性和完整性。同時,并行事務的修改必須與其他并行事務的修改相互獨立。
durability(持久性):事務完成之后,它對于系統的影響是永久的,該修改即使出現系統故障也將一直保留,真實的修改了數據庫。
以上 4 個屬性常被簡稱為 acid(酸的)。
事務并發的問題
臟讀:事務二讀取到事務一中已經更新但是還沒有提交的數據,這就是臟讀。
不可重復讀:一個事務兩次讀取同一個行數據結果不同,因為有其它的事務對數據進行了更新。此時的數據即為不可重復讀數據。
幻讀:同一事務執行兩次查詢,結果不一致,因為中間有其它的事務對數據進行更改。
如何解決這些問題呢?數據庫系統為事務設置了 4 種不同的隔離級別。
事務隔離級別
讀未提交(read uncommitted):最低級別,可能會導入臟讀。
讀已提交(read committed):可以避免臟讀,只能查詢到已經提交的數據。且具有良好的性能,但是不能避免不可重復讀和幻讀。
可重復讀(repeatable):解決了不可重復讀,可能會出現幻讀。
串行化(serializable):通過加鎖,使同一時間只能執行一個事務,不出現上述問題,但是可能會導致大量的超時現象和鎖競爭。
另外,mysql 中默認的隔離級別是可重復讀。oracle 中默認的事務隔離級別是讀已提交。
說完事務,想想我們曾經為了處理事務而寫過的那些代碼。最后在說說 spring 中是如何處理的,學完 spring 再也不用擔心事務操作了。
在 jdbc 時代我們需要這樣手動的處理事務。
1
2
3
4
5
6
7
8
|
// 獲取連接 conn conn.setautocommit( false ); 設置提交方式為手工提交 // 業務代碼 // 減錢 // 加錢 conn.commit(); 提交事務 // 出現異常 conn.rollback(); 回滾事務 |
我們說處理事務那是處理數據庫事務,所以肯定要先有數據庫連接才能說事務的事,而在 java 中我們連接數據庫無非就是 jdbc,或是對 jdbc 進一步的封裝,比方說 hibernate ,mybatis 或是 dbutils 這些框架,所以萬變不離其宗,就是這么回事,就是看誰封裝的好罷了。你們有興趣可以看看它們都是如何封裝的。
spring 中的如何管理事務呢
首先,我們知道 spring 是一個容器,不同的框架在處理事務時用到的對象不同,原生的 jdbc 使用 connection ,而 mybatis 中使用 sqlsession 對象。而 spring 為了整合這些不同的框架,定義了一個 platformtransactionmanager 接口來統一標準,對不同的框架又有不同的實現類。
在 spring 中根據 dao 層技術的不同來選擇不同的事務處理的對象,是 jdbc 時使用 datasourcetransactionmanager,是 hibernate 時使用 hibernatetransitionmanager 對象,核心對象就是 transitionmanager。
在 spring 中管理事務會涉及到這幾個屬性,事務隔離級別、是否只讀、事務的傳播行為,說到事務的傳播行為,指的就是不同的業務方法之間相互調用時,應該如何管理事務。spring 中一共定義了 7 種傳播行為,無腦記住使用 required ,表示支持當前事務,若是不存在事務,就創建一個。例如在 a 調用 b 的時候,會首先使用 a 的事務,若 a 沒有事務,則新創建一個,不管 b 有沒有事務。
下面就是要實際操作一下,需要有具體的業務邏輯,還是那個轉賬的例子。來看看如何使用 spring 來管理事務,有兩種常見的管理方式,我們一種一種的說。
使用 xml 配置
1 首先是導包,spring 涉及的包是真的多,我有一個省事的方法,可能用到的 jar 包一下子導入。
2 導入新的約束文件,不然在 xml 無法使用 tx 標簽。
3 準備目標對象和通知并配置。
目標對象 accountserviceimpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class accountserviceimpl implements accountservice { private accountdao ad; @transactional (isolation=isolation.repeatable_read,propagation = propagation.required,readonly= false ) @override public void transfer(integer from, integer to, double money) { ad.decreasemoney(from, money); //int i = 1/0; ad.increasemoney(to, money); } public void setad(accountdao ad) { this .ad = ad; } } |
在 aop 中通知即為增強的代碼,而在處理事務時,要增強的代碼無非就是開啟事務,提交事務和回滾事務,所以 spring 已經為我們封裝好了處理事務的通知,我們只需要配置一下即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<!-- 導入 properties 配置文件 --> <context:property-placeholder location= "classpath:db.properties" /> <!-- 配置連接數據庫的核心處理對象--> <bean name= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "datasource" ></property> </bean> <!-- 配置通知--> <tx:advice id= "txadvise" transaction-manager= "transactionmanager" > <tx:attributes> <tx:method name= "transfer" isolation= "default" propagation= "required" read-only= "false" /> </tx:attributes> </tx:advice> <!-- 配置 aop ,以達成自動處理事務的要求--> <aop:config> <aop:pointcut expression= "execution(* yu.transation.*serviceimpl.*(..))" id= "txpointcut" /> <aop:advisor advice-ref= "txadvise" pointcut-ref= "txpointcut" /> </aop:config> <bean name= "datasource" class = "com.mchange.v2.c3p0.combopooleddatasource" > <property name= "jdbcurl" value= "${jdbc.jdbcurl}" ></property> <property name= "driverclass" value= "${jdbc.driverclass}" ></property> <property name= "user" value= "${jdbc.user}" ></property> <property name= "password" value= "${jdbc.password}" ></property> </bean> <!-- 配置 dao 層對象--> <bean name= "ad" class = "yu.transation.accountdaoimpl" > <property name= "datasource" ref = "datasource" ></property> </bean> <!-- 配置 service 層對象--> <bean name = "accountservice" class = "yu.transation.accountserviceimpl" > <property name= "ad" ref = "ad" ></property> </bean> |
上面的配置文件中,在配置通知時,我們具體到不同的方法會有不同的配置,在項目應用時,會使用通配符來進行配置。下面介紹一下使用注解來處理事務,看起來會比較簡單。
步驟和上面有重復的部分,需要導包導入約束,接下來就是配置一下使用注解管理事務的開關
使用注解配置
1
2
|
<!-- 打開注解配置 aop 事務 --> <tx:annotation-driven/> |
下面是使用注解為 service 中的方法配置事務處理的屬性,當然,每一個方法都寫會比較麻煩,也可以在類上面使用注解,若是某個方法的處理規則不一致就單獨使用注解配置一下。
1
2
3
|
@transactional (isolation=isolation.repeatable_read,propagation = propagation.required,readonly= false ) @override public void transfer(integer from, integer to, double money) {...} |
回顧一下,以上主要說了事務以及 spring 中處理事務的方式,而這也正是 aop 思想在 spring 中的應用,我們可以看到不管是前面說的 ioc 還是 aop 在這里都有體現。
注解的出現是為了替換配置文件,所以我就以配置文件為主,并說一下與之對應的注解方式。
spring 中的配置主要在核心配置文件 applicationcontext.xml 中,由不同的標簽來表示,所以首先我們就需要導入各種約束,常用的約束有 bean、context、aop、tx 。
bean 標簽是最基本的標簽,主要用來配置各種對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- 屬性介紹: id: 為對象命名,唯一性標識,不能重復,不能使用特殊字符。 name: 和 id 的作用類似,區別在于可是使用特殊字符,可重復,但是不建議重復。 class : 指定對象的全類名。 init-method: 對象初始化之后立即執行的方法。 destroy-method: 對象銷毀之前執行的方法。 scope: 對象的作用范圍,可以設置單例 singleton 和多例 prototype。默認為單例 --> <bean name= "userservice" class = "yu.service.userserviceimpl" > <property name= "" value= "" ></property> <property name= "" ref= "" ></property> </bean> |
對應的注解有以下幾個,但是想要使用注解之前要首先配置一下……
1
2
|
<!-- 打開注解配置,掃描包及其子包 --> <context:component-scan base- package = "yu" ></context:component-scan> |
使用注解的時候,我們可以使用 @component 來表示將這個對象交由 spring 管理,@scope 來指定對象的作用域。之后便可以使用 @resource 來獲取對象。
在注冊對象的時候我們可以使用 @component ,但是若是每一個對象都是用這個注解,不能很好的分辨出對象屬于哪一層,所以 spring 又提供了 @controller @service @repository 來分別表示控制器層,service 層和 dao 層的對象,功能和 @component 是一模一樣的。
同樣的在為對象賦值的時候,我們可以使用注解 @autowired 來自動獲取容器中的對象,可是若是有重名的情況就需要另外一個注解 @qualifier 來具體指定叫什么名字,這樣就有點麻煩了,我們一般都是直接使用 @resource 來指定對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@component ( "user" ) @scope ( "prototype" ) public class user{ private string name; @value (value = "18" ) // 屬性注入,項目中不用。 private integer age; //@autowired 自動裝配 car 類型變量,同一類型 //@qualifier("car") 指定具體的是哪一個。 @resource (name = "car" ) // 指名道姓指定是哪個對象 private car car; ... @postconstruct public void init(){ system.out.println( "init 方法" ); } @predestroy public void destroy(){ system.out.println( "destory 方法" ); } } |
aop 相關的配置和注解
在 spring 中我們可以自定義通知和切面,下面只是展示了如何配置,但是在具體的業務中應該不會出現 5 種通知齊上陣的現象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<aop:config> <!-- 配置切點--> <aop:pointcut expression= "execution(* yu.service.*serviceimpl.*(..))" id= "pc" /> <aop:aspect ref= "myadvice" > <!-- 指定名為before方法作為前置通知 --> <aop:before method= "before" pointcut-ref= "pc" /> <!-- 后置 --> <aop:after-returning method= "afterreturning" pointcut-ref= "pc" /> <!-- 環繞通知 --> <aop:around method= "around" pointcut-ref= "pc" /> <!-- 異常攔截通知 --> <aop:after-throwing method= "afterexception" pointcut-ref= "pc" /> <!-- 后置 --> <aop:after method= "after" pointcut-ref= "pc" /> </aop:aspect> </aop:config> |
同樣的,我們也可以使用注解來達到自定義配置的方式。同樣的套路,想用注解配置實現 aop,需要打開注解配置 aop 的開關。
1
|
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> |
之后就是在通知類中進行配置即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@aspect //通知類 public class myadvice { // 快速配置切點表達式,方法直接調用即可 @pointcut ( "execution(* yu.service.*serviceimpl.*(..))" ) public void pc(){} //前置通知 @before ( "myadvice.pc()" ) public void before(){ system.out.println( "這是前置通知!!" ); } //后置通知 @afterreturning ( "myadvice.pc()" ) public void afterreturning(){ system.out.println( "這是后置通知(如果出現異常不會調用)!!" ); } |
context 主要是和全局有關的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!-- 打開注解配置對象,掃描包及其子包 --> <context:component-scan base- package = "yu" ></context:component-scan> <!-- 導入 properties 配置文件 --> <context:property-placeholder location= "classpath:db.properties" /> <bean name= "datasource" class = "com.mchange.v2.c3p0.combopooleddatasource" > <property name= "jdbcurl" value= "${jdbc.jdbcurl}" ></property> <property name= "driverclass" value= "${jdbc.driverclass}" ></property> <property name= "user" value= "${jdbc.user}" ></property> <property name= "password" value= "${jdbc.password}" ></property> </bean> |
tx 配置事務管理中的通知
tx 用來配置通知對象,而這個對象是由 spring 為我們寫好了,而事務管理依賴于數據庫連接對象,所以你能看到 transactionmanager 對象依賴于 datasource 對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<bean name= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "datasource" ></property> </bean> <tx:advice id= "txadvise" transaction-manager= "transactionmanager" > <tx:attributes> <tx:method name= "transfer" isolation= "default" propagation= "required" read-only= "false" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression= "execution(* yu.transation.*serviceimpl.*(..))" id= "txpointcut" /> <aop:advisor advice-ref= "txadvise" pointcut-ref= "txpointcut" /> </aop:config> |
使用注解配置時還是需要打開注解配置的開關
1
2
|
<!-- 打開注解配置 aop 事務 --> <tx:annotation-driven/> |
在具體的業務方法上或是類上使用注解 @transactional 來配置事務處理的方式。
1
2
3
|
@transactional (isolation=isolation.repeatable_read,propagation = propagation.required,readonly= false ) @override public void transfer(integer from, integer to, double money) {...} |
最后有一個完美的意外,那就是 import 標簽。用于導入其它的配置模塊到主配置文件中。
1
2
|
<!-- 導入其它的 spring 配置模塊 --> < import resource= "yu/transation/applicationcontext.xml" /> |
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/YJK923/p/10187081.html