服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring + mybatis + mysql使用事物的几种方法总结

Spring + mybatis + mysql使用事物的几种方法总结

2021-04-29 10:57一灰灰Blog Java教程

这篇文章主要给大家总结介绍了关于Spring + mybatis + mysql使用事物的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

本文主要记录下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 方法内部的逻辑
-- 也就是需要事物管理的一组sql

commit;

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. 参考

文档

spring事务管理的四种方式

源码

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://liuyueyi.github.io/hexblog/2018/05/12/Spring学习之事物的使用姿势/

延伸 · 阅读

精彩推荐