转自:https://www.jb51.net/article/140158.htm#_lab2_1_0
前言
本文主要记录下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"
<
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="...
<!--对应的事物通知和切面配置-->
<
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. 参考
文档
源码
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。