前言
本文主要記錄下spring是如何支持事物的,以及在spring結(jié)合mybatis時,可以怎么簡單的實現(xiàn)數(shù)據(jù)庫的事物功能,下面話不多說了,來一起看看詳細的介紹吧。
i. 前提
case1:兩張表的的事物支持情況
首先準(zhǔn)備兩張表,一個user表,一個story表,結(jié)構(gòu)如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
create table `user` ( `id` int ( 11 ) unsigned not null auto_increment, `name` varchar( 20 ) not null default '' comment '用戶名' , `pwd` varchar( 26 ) not null default '' comment '密碼' , `isdeleted` tinyint( 1 ) not null default '0' , `created` varchar( 13 ) not null default '0' , `updated` varchar( 13 ) not null default '0' , primary key (`id`), key `name` (`name`) ) engine=innodb default charset=utf8mb4; create table `story` ( `id` int ( 11 ) unsigned not null auto_increment, `userid` int ( 20 ) unsigned not null default '0' comment '作者的userid' , `name` varchar( 20 ) not null default '' comment '作者名' , `title` varchar( 26 ) not null default '' comment '密碼' , `story` text comment '故事內(nèi)容' , `isdeleted` tinyint( 1 ) not null default '0' , `created` varchar( 13 ) not null default '0' , `updated` varchar( 13 ) not null default '0' , primary key (`id`), key `userid` (`userid`) ) engine=innodb default charset=utf8mb4; |
我們的事物場景在于用戶修改name時,要求兩張表的name都需要一起修改,不允許出現(xiàn)不一致的情況
case2:單表的事物支持
轉(zhuǎn)賬,一個用戶減錢,另一個用戶加錢
1
2
3
4
5
6
7
8
9
10
|
create table `money` ( `id` int ( 11 ) unsigned not null auto_increment, `name` varchar( 20 ) not null default '' comment '用戶名' , `money` int ( 26 ) not null default '0' comment '錢' , `isdeleted` tinyint( 1 ) not null default '0' , `created` varchar( 13 ) not null default '0' , `updated` varchar( 13 ) not null default '0' , primary key (`id`), key `name` (`name`) ) engine=innodb default charset=utf8mb4; |
相比上面那個case,這個更加簡單了,下面的實例則主要根據(jù)這個進行說明,至于case1,則留待擴展里面進行
首先是實現(xiàn)對應(yīng)的dao和entity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@data public class moneyentity implements serializable { private static final long serialversionuid = -7074788842783160025l; private int id; private string name; private int money; private int isdeleted; private int created; private int updated; } public interface moneydao { moneyentity querymoney( @param ( "id" ) int userid); // 加錢,負數(shù)時表示減錢 int incrementmoney( @param ( "id" ) int userid, @param ( "addmoney" ) int addmoney); } |
對應(yīng)的mapper文件為
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?xml version= "1.0" encoding= "utf-8" ?> <!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace= "com.git.hui.demo.mybatis.mapper.moneydao" > <sql id= "moneyentity" > id, `name`, `money`, `isdeleted`, `created`, `updated` </sql> <select id= "querymoney" resulttype= "com.git.hui.demo.mybatis.entity.moneyentity" > select <include refid= "moneyentity" /> from money where id=#{id} </select> <update id= "incrementmoney" > update money set money=money + #{addmoney} where id=#{id} </update> </mapper> |
對應(yīng)的mybatis連接數(shù)據(jù)源的相關(guān)配置
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
35
36
37
38
39
40
41
42
43
44
|
<bean class = "org.springframework.beans.factory.config.propertyplaceholderconfigurer" > <property name= "locations" > <value>classpath*:jdbc.properties</value> </property> </bean> <bean id= "datasource" class = "com.alibaba.druid.pool.druiddatasource" init-method= "init" destroy-method= "close" > <property name= "driverclassname" value= "${driver}" /> <property name= "url" value= "${url}" /> <property name= "username" value= "${username}" /> <property name= "password" value= "${password}" /> <property name= "filters" value= "stat" /> <property name= "maxactive" value= "20" /> <property name= "initialsize" value= "1" /> <property name= "maxwait" value= "60000" /> <property name= "minidle" value= "1" /> <property name= "timebetweenevictionrunsmillis" value= "60000" /> <property name= "minevictableidletimemillis" value= "300000" /> <property name= "validationquery" value= "select 'x'" /> <property name= "testwhileidle" value= "true" /> <property name= "testonborrow" value= "false" /> <property name= "testonreturn" value= "false" /> <property name= "poolpreparedstatements" value= "true" /> <property name= "maxpoolpreparedstatementperconnectionsize" value= "50" /> </bean> <bean id= "sqlsessionfactory" class = "org.mybatis.spring.sqlsessionfactorybean" > <property name= "datasource" ref= "datasource" /> <!-- 指定mapper文件 --> <property name= "mapperlocations" value= "classpath*:mapper/*.xml" /> </bean> <!-- 指定掃描dao --> <bean class = "org.mybatis.spring.mapper.mapperscannerconfigurer" > <property name= "basepackage" value= "com.git.hui.demo.mybatis" /> </bean> |
ii. 實例演示
通過網(wǎng)上查詢,spring事物管理總共有四種方式,下面逐一進行演示,每種方式是怎么玩的,然后看實際項目中應(yīng)該如何抉擇
1. 硬編碼方式
編程式事物管理,既通過transactiontemplate來實現(xiàn)多個db操作的事物管理
a. 實現(xiàn)
那么,我們的轉(zhuǎn)賬case可以如下實現(xiàn)
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
@repository public class codedemo1 { @autowired private moneydao moneydao; @autowired private transactiontemplate transactiontemplate; /** * 轉(zhuǎn)賬 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個異常, 2 表示新開一個線程,修改inuserid的錢 +200, 3 表示新開一個線程,修改outuserid的錢 + 200 */ public void transfor( final int inuserid, final int outuserid, final int paymoney, final int status) { transactiontemplate.execute( new transactioncallbackwithoutresult() { protected void dointransactionwithoutresult(transactionstatus transactionstatus) { moneyentity entity = moneydao.querymoney(outuserid); if (entity.getmoney() > paymoney) { // 可以轉(zhuǎn)賬 // 先減錢 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加錢 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "轉(zhuǎn)賬完成! now: " + system.currenttimemillis()); } } }); } // 下面都是測試用例相關(guān) private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "轉(zhuǎn)賬異常!!!" ); } else if (status == 2 ) { addmoney(inuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } else if (status == 3 ) { addmoney(outuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } } public void addmoney( final int userid) { system.out.printf( "內(nèi)部加錢: " + system.currenttimemillis()); new thread( new runnable() { public void run() { moneydao.incrementmoney(userid, 200 ); system.out.println( " sub modify success! now: " + system.currenttimemillis()); } }).start(); } } |
主要看上面的transfor方法,內(nèi)部通過 transactiontemplate 來實現(xiàn)事物的封裝,內(nèi)部有三個db操作,一個查詢,兩個更新,具體分析后面說明
上面的代碼比較簡單了,唯一需要關(guān)注的就是transactiontemplate這個bean如何定義的,xml文件中與前面重復(fù)的就不貼了,直接貼上關(guān)鍵代碼, 一個是根據(jù)datasource創(chuàng)建的transactionmanager,一個則是根據(jù)transactionmanager創(chuàng)建的transactiontemplate
1
2
3
4
5
6
7
8
|
<!--編程式事物--> <bean id= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "datasource" /> </bean> <bean id= "transactiontemplate" class = "org.springframework.transaction.support.transactiontemplate" > <property name= "transactionmanager" ref= "transactionmanager" /> </bean> |
b. 測試用例
正常演示情況, 演示沒有任何異常,不考慮并發(fā)的情況
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@runwith (springjunit4classrunner. class ) @contextconfiguration ({ "classpath*:spring/service.xml" , "classpath*:test-datasource1.xml" }) public class codedemo1test { @autowired private codedemo1 codedemo1; @autowired private moneydao moneydao; @test public void testtransfor() { system.out.println( "---------before----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); codedemo1.transfor( 1 , 2 , 10 , 0 ); system.out.println( "---------after----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); } } |
輸出如下,兩個賬號的錢都沒有問題
---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉(zhuǎn)賬完成! now: 1526130394266
---------after----------
id: 1 money = 10010
id: 2 money = 49990
轉(zhuǎn)賬過程中出現(xiàn)異常,特別是轉(zhuǎn)賬方錢已扣,收款方還沒收到錢時,也就是case中的status為1的場景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 內(nèi)部拋異常的情況 @test public void testtransforexception() { system.out.println( "---------before----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); try { codedemo1.transfor( 1 , 2 , 10 , 1 ); } catch (exception e) { e.printstacktrace(); } system.out.println( "---------after----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); } |
對此,我們希望把轉(zhuǎn)賬方的錢還回去, 輸出如下,發(fā)現(xiàn)兩個的錢都沒有變化
---------before----------
id: 1 money = 10010
id: 2 money = 49990
---------after----------
id: 1 money = 10010
java.lang.illegalargumentexception: 轉(zhuǎn)賬異常!!!
... // 省略異常信息
id: 2 money = 49990
當(dāng)status為2,表示在轉(zhuǎn)賬人錢已扣,收款人錢沒收到之間,又有人給收款人轉(zhuǎn)了200,此時根據(jù)mysql的鎖機制,另外人的轉(zhuǎn)賬應(yīng)該是立馬到的(因為收款人賬號沒有被鎖住),且金額不應(yīng)該有問題
輸出結(jié)果如下:
---------before----------
id: 1 money = 10010
id: 2 money = 49990
## 右邊是注釋: 轉(zhuǎn)賬過程中,另外存錢立馬到賬,沒有被鎖住
內(nèi)部加錢: 1526130827480
sub modify success! now: 1526130827500
## 存錢結(jié)束
轉(zhuǎn)賬完成! now: 1526130830488
---------after----------
id: 1 money = 10220
id: 2 money = 49980
當(dāng)status為3, 表示在轉(zhuǎn)賬人錢已扣,收款人錢沒收到之間,又有人給轉(zhuǎn)賬人轉(zhuǎn)了200,這時因為轉(zhuǎn)賬人的記錄以及被加了寫鎖,因此只能等待轉(zhuǎn)賬的事物提交之后,才有可能+200成功,當(dāng)然最終的金額也得一致
輸出結(jié)果如下
---------before----------
id: 1 money = 10220
id: 2 money = 49980
## 右邊是注釋:內(nèi)部存錢了,但沒有馬上成功
## 直到轉(zhuǎn)賬完成后,才立馬存成功,注意兩個時間戳
內(nèi)部加錢: 1526131101046
轉(zhuǎn)賬完成! now: 1526131104051
sub modify success! now: 1526131104053
---------after----------
id: 1 money = 10230
id: 2 money = 50170
c. 小結(jié)
至此,編程式事物已經(jīng)實例演示ok,從上面的過程,給人的感覺就和直接寫事物相關(guān)的sql一樣,
start transaction;
-- 這中間就是 transactiontemplate#execute 方法內(nèi)部的邏輯
-- 也就是需要事物管理的一組sqlcommit;
2. 基于transactionproxyfactorybean方式
接下來的三個就是聲明式事物管理,這種用得也比較少,因為需要每個事物管理類,添加一個transactionproxyfactorybean
a. 實現(xiàn)
除了將 transactiontemplate 干掉,并將內(nèi)部的sql邏輯移除之外,對比前面的,發(fā)現(xiàn)基本上沒有太多差別
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
public class factorybeandemo2 { @autowired private moneydao moneydao; /** * 轉(zhuǎn)賬 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個異常, 2 表示新開一個線程,修改inuserid的錢 +200, 3 表示新開一個線程,修改outuserid的錢 + 200 */ public void transfor( final int inuserid, final int outuserid, final int paymoney, final int status) { moneyentity entity = moneydao.querymoney(outuserid); if (entity.getmoney() > paymoney) { // 可以轉(zhuǎn)賬 // 先減錢 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加錢 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "轉(zhuǎn)賬完成! now: " + system.currenttimemillis()); } } private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "轉(zhuǎn)賬異常!!!" ); } else if (status == 2 ) { addmoney(inuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } else if (status == 3 ) { addmoney(outuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } } public void addmoney( final int userid) { system.out.println( "內(nèi)部加錢: " + system.currenttimemillis()); new thread( new runnable() { public void run() { moneydao.incrementmoney(userid, 200 ); system.out.println( "sub modify success! now: " + system.currenttimemillis()); } }).start(); } } |
重點來了,主要是需要配置一個 transactionproxybeanfactory,我們知道beanfactory就是我們自己來創(chuàng)建bean的一種手段,相關(guān)的xml配置如下
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
|
<!--編程式事物--> <bean id= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "datasource" /> </bean> <bean id= "factorybeandemo2" class = "com.git.hui.demo.mybatis.repository.transaction.factorybeandemo2" /> <!-- 配置業(yè)務(wù)層的代理 --> <bean id= "factorybeandemoproxy" class = "org.springframework.transaction.interceptor.transactionproxyfactorybean" > <!-- 配置目標(biāo)對象 --> <property name= "target" ref= "factorybeandemo2" /> <!-- 注入事務(wù)管理器 --> <property name= "transactionmanager" ref= "transactionmanager" /> <!-- 注入事務(wù)的屬性 --> <property name= "transactionattributes" > <props> <!-- prop的格式: * propagation :事務(wù)的傳播行為 * isotation :事務(wù)的隔離級別 * readonly :只讀 * -exception :發(fā)生哪些異常回滾事務(wù) * +exception :發(fā)生哪些異常不回滾事務(wù) --> <!-- 這個key對應(yīng)的就是目標(biāo)類中的方法--> <prop key= "transfor" >propagation_required</prop> <!-- <prop key= "transfer" >propagation_required,readonly</prop> --> <!-- <prop key= "transfer" >propagation_required,+java.lang.arithmeticexception</prop> --> </props> </property> </bean> |
通過上面的配置,大致可以了解到這個通過transactionproxyfactorybean就是創(chuàng)建了一個factorybeandemo2的代理類,這個代理類內(nèi)部封裝好事物相關(guān)的邏輯,可以看做是前面編程式的一種簡單通用抽象
b. 測試
測試代碼與前面基本相同,唯一的區(qū)別就是我們使用的應(yīng)該是上面beanfactory生成的bean,而不是直接使用factorybeandemo2
正常演示case:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@runwith (springjunit4classrunner. class ) @contextconfiguration ({ "classpath*:spring/service.xml" , "classpath*:test-datasource2.xml" }) public class factorybeandemo1test { @resource (name = "factorybeandemoproxy" ) private factorybeandemo2 factorybeandemo2; @autowired private moneydao moneydao; @test public void testtransfor() { system.out.println( "---------before----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); factorybeandemo2.transfor( 1 , 2 , 10 , 0 ); system.out.println( "---------after----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); } } |
輸出
---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉(zhuǎn)賬完成! now: 1526132058886
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status為1,內(nèi)部異常的情況下,我們希望錢也不會有問題
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@test public void testtransforexception() { system.out.println( "---------before----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); try { factorybeandemo2.transfor( 1 , 2 , 10 , 1 ); } catch (exception e) { system.out.println(e.getmessage());; } system.out.println( "---------after----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); } |
輸出為
---------before----------
id: 1 money = 10010
id: 2 money = 49990
轉(zhuǎn)賬異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status為2 時,分析結(jié)果與上面應(yīng)該相同,輸出如下
---------before----------
id: 1 money = 10010
id: 2 money = 49950
內(nèi)部加錢: 1526133325376
sub modify success! now: 1526133325387
轉(zhuǎn)賬完成! now: 1526133328381
---------after----------
id: 1 money = 10220
id: 2 money = 49940
status為3時,輸出
---------before----------
id: 1 money = 10220
id: 2 money = 49940
內(nèi)部加錢: 1526133373466
轉(zhuǎn)賬完成! now: 1526133376476
sub modify success! now: 1526133376480
---------after----------
id: 1 money = 10230
id: 2 money = 50130
c. 小結(jié)
transactionproxyfactorybean 的思路就是利用代理模式來實現(xiàn)事物管理,生成一個代理類,攔截目標(biāo)方法,將一組sql的操作封裝到事物中進行;相比較于硬編碼,無侵入,而且支持靈活的配置方式
缺點也顯而易見,每個都要進行配置,比較繁瑣
3. xml使用方式
spring有兩大特點,ioc和aop,對于事物這種情況而言,我們可不可以使用aop來做呢?
對于需要開啟事物的方法,攔截掉,執(zhí)行前開始事物,執(zhí)行完畢之后提交事物,出現(xiàn)異常時回滾
這樣一看,感覺還是蠻有希望的,而下面兩種姿勢正是這么玩的,因此需要加上aspect的依賴
1
2
3
4
5
|
<dependency> <groupid>org.aspectj</groupid> <artifactid>aspectjweaver</artifactid> <version> 1.8 . 7 </version> </dependency> |
a. 實現(xiàn)
java類與第二種完全一致,變動的只有xml
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
|
<!-- 首先添加命名空間 --> xmlns:tx= "http://www.springframework.org/schema/tx" xmlns:aop= "http://www.springframework.org/schema/aop" xsi:schemalocation="... http: //www.springframework.org/schema/tx http: //www.springframework.org/schema/tx/spring-tx.xsd" <!--對應(yīng)的事物通知和切面配置--> <tx:advice id= "txadvice" transaction-manager= "transactionmanager" > <tx:attributes> <!-- propagation :事務(wù)傳播行為 isolation :事務(wù)的隔離級別 read-only :只讀 rollback- for :發(fā)生哪些異常回滾 no-rollback- for :發(fā)生哪些異常不回滾 timeout :過期信息 --> <tx:method name= "transfor" propagation= "required" /> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 配置切入點 --> <aop:pointcut expression= "execution(* com.git.hui.demo.mybatis.repository.transaction.xmldemo3.*(..))" id= "pointcut1" /> <!-- 配置切面 --> <aop:advisor advice-ref= "txadvice" pointcut-ref= "pointcut1" /> </aop:config> |
觀察上面的配置,再想想第二種方式,思路都差不多了,但是這種方式明顯更加通用,通過切面和切點,可以減少大量的配置
b. 測試
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
|
@runwith (springjunit4classrunner. class ) @contextconfiguration ({ "classpath*:spring/service.xml" , "classpath*:test-datasource3.xml" }) public class xmlbeantest { @autowired private xmldemo3 xmldemo; @autowired private moneydao moneydao; @test public void testtransfor() { system.out.println( "---------before----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); xmldemo.transfor( 1 , 2 , 10 , 0 ); system.out.println( "---------after----------" ); system.out.println( "id: 1 money = " + moneydao.querymoney( 1 ).getmoney()); system.out.println( "id: 2 money = " + moneydao.querymoney( 2 ).getmoney()); } } |
這個測試起來,和一般的寫法就沒啥兩樣了,比第二種的factorybean的注入方式簡單點
正常輸出
---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉(zhuǎn)賬完成! now: 1526135301273
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=1 出現(xiàn)異常時,輸出
---------before----------
id: 1 money = 10010
id: 2 money = 49990
轉(zhuǎn)賬異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=2 轉(zhuǎn)賬過程中,又存錢的場景,輸出,與前面預(yù)期一致
---------before----------
id: 1 money = 10010
id: 2 money = 49990
內(nèi)部加錢: 1526135438403
sub modify success! now: 1526135438421
轉(zhuǎn)賬完成! now: 1526135441410
---------after----------
id: 1 money = 10220
id: 2 money = 49980
status=3 的輸出,與前面預(yù)期一致
---------before----------
id: 1 money = 10220
id: 2 money = 49980
內(nèi)部加錢: 1526135464341
轉(zhuǎn)賬完成! now: 1526135467349
sub modify success! now: 1526135467352
---------after----------
id: 1 money = 10230
id: 2 money = 50170
4. 注解方式
這個就是消滅xml,用注解來做的方式,就是將前面xml中的配置用 @transactional注解替換
a. 實現(xiàn)
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
@repository public class annodemo4 { @autowired private moneydao moneydao; /** * 轉(zhuǎn)賬 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個異常, 2 表示新開一個線程,修改inuserid的錢 +200, 3 表示新開一個線程,修改outuserid的錢 + 200 * * * transactional注解中的的屬性 propagation :事務(wù)的傳播行為 isolation :事務(wù)的隔離級別 readonly :只讀 * rollbackfor :發(fā)生哪些異常回滾 norollbackfor :發(fā)生哪些異常不回滾 * rollbackforclassname 根據(jù)異常類名回滾 */ @transactional (propagation = propagation.required, isolation = isolation. default , readonly = false ) public void transfor( final int inuserid, final int outuserid, final int paymoney, final int status) { moneyentity entity = moneydao.querymoney(outuserid); if (entity.getmoney() > paymoney) { // 可以轉(zhuǎn)賬 // 先減錢 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加錢 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "轉(zhuǎn)賬完成! now: " + system.currenttimemillis()); } } private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "轉(zhuǎn)賬異常!!!" ); } else if (status == 2 ) { addmoney(inuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } else if (status == 3 ) { addmoney(outuserid); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } } } private void addmoney( final int userid) { system.out.println( "內(nèi)部加錢: " + system.currenttimemillis()); new thread( new runnable() { public void run() { moneydao.incrementmoney(userid, 200 ); system.out.println( "sub modify success! now: " + system.currenttimemillis()); } }).start(); } } |
因此需要在xml中配置,開啟事物注解
1
2
3
4
5
6
|
<!--編程式事物--> <bean id= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "datasource" /> </bean> <tx:annotation-driven transaction-manager= "transactionmanager" /> |
這樣一看,就更加清晰了,實際項目中,xml和注解方式也是用得最多的場景了
b. 測試case
和第三種測試case完全相同, 輸出結(jié)果也一樣,直接省略
iii. 小結(jié)
上面說了spring中四種使用事物的姿勢,其中硬編碼方式可能是最好理解的,就相當(dāng)于將我們寫sql中,使用事物的方式直接翻譯成對應(yīng)的java代碼了;而factorybean方式相當(dāng)于特殊情況特殊對待,為每個事物來一個代理類來增強事物功能;后面的兩個則原理差不多都是利用事物通知(aop)來實現(xiàn),定義切點及相關(guān)信息
編程式:
- 注入 transactiontemplate
-
將利用事物的邏輯封裝到
transactiontemplate#execute
方法內(nèi)
代理beanfactory:
- 利用 transactionproxyfactorybean 為事物相關(guān)類生成代理
- 使用方通過factorybean獲取代理類,作為使用的bean
xml配置:
- 利用 tx標(biāo)簽 + aop方式來實現(xiàn)
- <tx:advice> 標(biāo)簽定義事物通知,內(nèi)部可有較多的配置信息
- <aop:config> 配置切點,切面
注解方式:
- 在開啟事物的方法or類上添加 @transactional 注解即可
-
開啟事物注解 <
tx:annotation-driven transaction-manager="transactionmanager"/>
iv. 其他
1. 參考
文檔
源碼
- 項目源碼:study-demo
- 主要查看包路徑: 事物demo
- 測試相關(guān)代碼: 測試demo
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對服務(wù)器之家的支持。
原文鏈接:https://liuyueyi.github.io/hexblog/2018/05/12/Spring學(xué)習(xí)之事物的使用姿勢/