前言
本文主要记录下spring是如何支持事物的,以及在spring结合mybatis时,可以怎么简单的实现数据库的事物功能,下面话不多说了,来一起看看详细的介绍吧。
i. 前提
case1:两张表的的事物支持情况
首先准备两张表,一个user表,一个story表,结构如下
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 '故事内容' , `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都需要一起修改,不允许出现不一致的情况
case2:单表的事物支持
转账,一个用户减钱,另一个用户加钱
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,这个更加简单了,下面的实例则主要根据这个进行说明,至于case1,则留待扩展里面进行
首先是实现对应的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); // 加钱,负数时表示减钱 int incrementmoney( @param ( "id" ) int userid, @param ( "addmoney" ) int addmoney); } |
对应的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> |
对应的mybatis连接数据源的相关配置
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. 实例演示
通过网上查询,spring事物管理总共有四种方式,下面逐一进行演示,每种方式是怎么玩的,然后看实际项目中应该如何抉择
1. 硬编码方式
编程式事物管理,既通过transactiontemplate来实现多个db操作的事物管理
a. 实现
那么,我们的转账case可以如下实现
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; /** * 转账 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常转账, 1 表示内部抛出一个异常, 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) { // 可以转账 // 先减钱 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加钱 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "转账完成! now: " + system.currenttimemillis()); } } }); } // 下面都是测试用例相关 private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "转账异常!!!" ); } 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( "内部加钱: " + system.currenttimemillis()); new thread( new runnable() { public void run() { moneydao.incrementmoney(userid, 200 ); system.out.println( " sub modify success! now: " + system.currenttimemillis()); } }).start(); } } |
主要看上面的transfor方法,内部通过 transactiontemplate 来实现事物的封装,内部有三个db操作,一个查询,两个更新,具体分析后面说明
上面的代码比较简单了,唯一需要关注的就是transactiontemplate这个bean如何定义的,xml文件中与前面重复的就不贴了,直接贴上关键代码, 一个是根据datasource创建的transactionmanager,一个则是根据transactionmanager创建的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. 测试用例
正常演示情况, 演示没有任何异常,不考虑并发的情况
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
转账完成! now: 1526130394266
---------after----------
id: 1 money = 10010
id: 2 money = 49990
转账过程中出现异常,特别是转账方钱已扣,收款方还没收到钱时,也就是case中的status为1的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 内部抛异常的情况 @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()); } |
对此,我们希望把转账方的钱还回去, 输出如下,发现两个的钱都没有变化
---------before----------
id: 1 money = 10010
id: 2 money = 49990
---------after----------
id: 1 money = 10010
java.lang.illegalargumentexception: 转账异常!!!
... // 省略异常信息
id: 2 money = 49990
当status为2,表示在转账人钱已扣,收款人钱没收到之间,又有人给收款人转了200,此时根据mysql的锁机制,另外人的转账应该是立马到的(因为收款人账号没有被锁住),且金额不应该有问题
输出结果如下:
---------before----------
id: 1 money = 10010
id: 2 money = 49990
## 右边是注释: 转账过程中,另外存钱立马到账,没有被锁住
内部加钱: 1526130827480
sub modify success! now: 1526130827500
## 存钱结束
转账完成! now: 1526130830488
---------after----------
id: 1 money = 10220
id: 2 money = 49980
当status为3, 表示在转账人钱已扣,收款人钱没收到之间,又有人给转账人转了200,这时因为转账人的记录以及被加了写锁,因此只能等待转账的事物提交之后,才有可能+200成功,当然最终的金额也得一致
输出结果如下
---------before----------
id: 1 money = 10220
id: 2 money = 49980
## 右边是注释:内部存钱了,但没有马上成功
## 直到转账完成后,才立马存成功,注意两个时间戳
内部加钱: 1526131101046
转账完成! now: 1526131104051
sub modify success! now: 1526131104053
---------after----------
id: 1 money = 10230
id: 2 money = 50170
c. 小结
至此,编程式事物已经实例演示ok,从上面的过程,给人的感觉就和直接写事物相关的sql一样,
start transaction;
-- 这中间就是 transactiontemplate#execute 方法内部的逻辑
-- 也就是需要事物管理的一组sqlcommit;
2. 基于transactionproxyfactorybean方式
接下来的三个就是声明式事物管理,这种用得也比较少,因为需要每个事物管理类,添加一个transactionproxyfactorybean
a. 实现
除了将 transactiontemplate 干掉,并将内部的sql逻辑移除之外,对比前面的,发现基本上没有太多差别
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; /** * 转账 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常转账, 1 表示内部抛出一个异常, 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) { // 可以转账 // 先减钱 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加钱 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "转账完成! now: " + system.currenttimemillis()); } } private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "转账异常!!!" ); } 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( "内部加钱: " + 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就是我们自己来创建bean的一种手段,相关的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" /> <!-- 配置业务层的代理 --> <bean id= "factorybeandemoproxy" class = "org.springframework.transaction.interceptor.transactionproxyfactorybean" > <!-- 配置目标对象 --> <property name= "target" ref= "factorybeandemo2" /> <!-- 注入事务管理器 --> <property name= "transactionmanager" ref= "transactionmanager" /> <!-- 注入事务的属性 --> <property name= "transactionattributes" > <props> <!-- prop的格式: * propagation :事务的传播行为 * isotation :事务的隔离级别 * readonly :只读 * -exception :发生哪些异常回滚事务 * +exception :发生哪些异常不回滚事务 --> <!-- 这个key对应的就是目标类中的方法--> <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就是创建了一个factorybeandemo2的代理类,这个代理类内部封装好事物相关的逻辑,可以看做是前面编程式的一种简单通用抽象
b. 测试
测试代码与前面基本相同,唯一的区别就是我们使用的应该是上面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
转账完成! now: 1526132058886
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status为1,内部异常的情况下,我们希望钱也不会有问题
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
转账异常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status为2 时,分析结果与上面应该相同,输出如下
---------before----------
id: 1 money = 10010
id: 2 money = 49950
内部加钱: 1526133325376
sub modify success! now: 1526133325387
转账完成! now: 1526133328381
---------after----------
id: 1 money = 10220
id: 2 money = 49940
status为3时,输出
---------before----------
id: 1 money = 10220
id: 2 money = 49940
内部加钱: 1526133373466
转账完成! now: 1526133376476
sub modify success! now: 1526133376480
---------after----------
id: 1 money = 10230
id: 2 money = 50130
c. 小结
transactionproxyfactorybean 的思路就是利用代理模式来实现事物管理,生成一个代理类,拦截目标方法,将一组sql的操作封装到事物中进行;相比较于硬编码,无侵入,而且支持灵活的配置方式
缺点也显而易见,每个都要进行配置,比较繁琐
3. xml使用方式
spring有两大特点,ioc和aop,对于事物这种情况而言,我们可不可以使用aop来做呢?
对于需要开启事物的方法,拦截掉,执行前开始事物,执行完毕之后提交事物,出现异常时回滚
这样一看,感觉还是蛮有希望的,而下面两种姿势正是这么玩的,因此需要加上aspect的依赖
1
2
3
4
5
|
<dependency> <groupid>org.aspectj</groupid> <artifactid>aspectjweaver</artifactid> <version> 1.8 . 7 </version> </dependency> |
a. 实现
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" <!--对应的事物通知和切面配置--> <tx:advice id= "txadvice" transaction-manager= "transactionmanager" > <tx:attributes> <!-- propagation :事务传播行为 isolation :事务的隔离级别 read-only :只读 rollback- for :发生哪些异常回滚 no-rollback- for :发生哪些异常不回滚 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
转账完成! now: 1526135301273
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=1 出现异常时,输出
---------before----------
id: 1 money = 10010
id: 2 money = 49990
转账异常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=2 转账过程中,又存钱的场景,输出,与前面预期一致
---------before----------
id: 1 money = 10010
id: 2 money = 49990
内部加钱: 1526135438403
sub modify success! now: 1526135438421
转账完成! now: 1526135441410
---------after----------
id: 1 money = 10220
id: 2 money = 49980
status=3 的输出,与前面预期一致
---------before----------
id: 1 money = 10220
id: 2 money = 49980
内部加钱: 1526135464341
转账完成! now: 1526135467349
sub modify success! now: 1526135467352
---------after----------
id: 1 money = 10230
id: 2 money = 50170
4. 注解方式
这个就是消灭xml,用注解来做的方式,就是将前面xml中的配置用 @transactional注解替换
a. 实现
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; /** * 转账 * * @param inuserid * @param outuserid * @param paymoney * @param status 0 表示正常转账, 1 表示内部抛出一个异常, 2 表示新开一个线程,修改inuserid的钱 +200, 3 表示新开一个线程,修改outuserid的钱 + 200 * * * transactional注解中的的属性 propagation :事务的传播行为 isolation :事务的隔离级别 readonly :只读 * rollbackfor :发生哪些异常回滚 norollbackfor :发生哪些异常不回滚 * rollbackforclassname 根据异常类名回滚 */ @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) { // 可以转账 // 先减钱 moneydao.incrementmoney(outuserid, -paymoney); testcase(inuserid, outuserid, status); // 再加钱 moneydao.incrementmoney(inuserid, paymoney); system.out.println( "转账完成! now: " + system.currenttimemillis()); } } private void testcase( final int inuserid, final int outuserid, final int status) { if (status == 1 ) { throw new illegalargumentexception( "转账异常!!!" ); } 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( "内部加钱: " + 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完全相同, 输出结果也一样,直接省略
iii. 小结
上面说了spring中四种使用事物的姿势,其中硬编码方式可能是最好理解的,就相当于将我们写sql中,使用事物的方式直接翻译成对应的java代码了;而factorybean方式相当于特殊情况特殊对待,为每个事物来一个代理类来增强事物功能;后面的两个则原理差不多都是利用事物通知(aop)来实现,定义切点及相关信息
编程式:
- 注入 transactiontemplate
-
将利用事物的逻辑封装到
transactiontemplate#execute
方法内
代理beanfactory:
- 利用 transactionproxyfactorybean 为事物相关类生成代理
- 使用方通过factorybean获取代理类,作为使用的bean
xml配置:
- 利用 tx标签 + aop方式来实现
- <tx:advice> 标签定义事物通知,内部可有较多的配置信息
- <aop:config> 配置切点,切面
注解方式:
- 在开启事物的方法or类上添加 @transactional 注解即可
-
开启事物注解 <
tx:annotation-driven transaction-manager="transactionmanager"/>
iv. 其他
1. 参考
文档
源码
- 项目源码:study-demo
- 主要查看包路径: 事物demo
- 测试相关代码: 测试demo
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://liuyueyi.github.io/hexblog/2018/05/12/Spring学习之事物的使用姿势/