事务

事务定义

事务是正确执行一系列的操作(或动作),使得数据库从一种状态转换成另一种状态,且保证操作全部成功,或者全部失败。我们知道在JavaEE的开发过程中,service层的方法通常用于业务逻辑处理,而业务往往涉及到对数据库的多个操作。

以生活中常见的转账为例,假设某个方法要实现将A账户转账到B账户的功能,则该方法内必定有两个操作:先将A账户的金额减去要转账的数目,然后将B账户加上相应的金额数目,这个方法看似简单,但是需要这两个操作要么全部成功(表示本次转账成功);若有任意一方失败,则另一方必须回滚(即必须回到初始状态,全部失败)。所以上面介绍的事务其实就是指:一组操作是不可分割的,要么全部成功,要么全部失败。

事务的ACID四个特性

我们知道事务具有ACID四个特性:原子性、一致性、隔离性和持久性。

原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生;一致性(Consistency):事务在完成后数据的完整性必须保持一致;隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据要相互隔离;持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变应该是永久性的,即使数据库发生故障也不应该对其有任何影响。

Java事务

Java事务其实是以Java编写的程序或系统,用于实现ACID的操作。而Java事务的实现是通过JDBC提供的相应方法来间接实现对数据库的增删改查操作,通过事务转移到Java程序代码中进行控制。

需要注意的是必须确保事务要么全部执行成功,要么撤销不执行。因此可以这么说Java事务机制和原理就是操作确保数据库操作的ACID特性。

Java事务的类型分为三种:JDBC事务JTA(Java Transaction API)事务容器事务。其中JDBC事务是用Connection对象控制的手动模式和自动模式;JTA事务是与实现无关的,与协议无关的API;容器事务是应用服务器提供的,且大多数是基于JTA完成(通常是基于JNDI(Java Naming and Directory Interface,Java命名和目录接口)的,一种非常复杂的API实现)。

三种事务的差异:JDBC事务,它控制的局限性在一个数据库连接内,但是其使用较为简单。JTA(Java Transaction API)事务,它功能强大,可跨越多个数据库或者多个DAO,但是使用起来较为复杂;容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。

Spring事务管理接口

先来看一张图,该图反映了事务在Spring中占据着较为核心的位置:

而下面这张图则是Spring事务管理提供的三个高层抽象接口,分别是TransactionProxyFactoryBeanTransactionDefinitionTransactionStatus

PlatformTransactionManager事务管理器

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事务,而是通过该接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,也就是将事务管理的职责委托给Hibernate或者Mybatis等持久层框架的事务来实现。

org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者Mybatis进行持久化数据时使用,通过调用java.sql.Connection来管理事务。

org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本进行持久化数据时使用,将事务管理的职责委托给org.hibernate.Transaction对象来管理事务,而后者是从Hibernate Session中获取到的。

org.springframework.orm.jpa.JpaTransactionManager:使用JPA进行持久化数据时使用,通过一个JPA实体管理工厂(javax.persitence.EntityManagerFactory接口的任意实现)将与工厂所产生的JPA EntityManager合作来构建事务。

org.springframework.jdo.JdoTransactionManager:当持久化机制是jdo时使用。

org.springframework.transaction.jta.JtaTransactionManager:使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用,它将事务管理的职责委托给javax.transaction.UserTransactionjavax.transaction.TransactionManager对象来管理事务。

PlatformTransactionManager接口源码:

1
2
3
4
5
6
7
8
public interface PlatformTransactionManager {
//事务管理器通过TransactionDefinition,获得“事务状态”,从而管理事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//根据状态提交
void commit(TransactionStatus var1) throws TransactionException;
//根据状态回滚
void rollback(TransactionStatus var1) throws TransactionException;
}

TransactionDefinition定义事务基本属性

org.springframework.transaction.TransactionDefinition接口用于定义一个事务,它定义了Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

隔离级别

什么是事务的隔离级别?我们知道隔离性是事务的四大特性之一,表示多个并发事务之间的数据要相互隔离,隔离级别就是用于描述并发事务之间隔离程度的大小。

如果在并发事务之间如果不考虑隔离性,会引发一些安全性问题:脏读不可重复读幻读等。

脏读是指一个事务读到了另一个事务的未提交的数据;不可重复读是指一个事务读到了另一个事务已经提交的修改的数据导致多次查询结果不一致;幻读是指一个事务读到了另一个事务已经提交的插入的数据导致多次查询结果不一致。

在Spring事务管理中,定义了如下5种隔离级别:

ISOLATION_DEFAULT:使用数据库默认的隔离级别;
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读;
ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;
ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生;
ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。

传播行为

Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则。Spring定义了七种传播行为,这里以方法A和方法B发生嵌套调用时如何传播事务为例说明:

PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务;
PROPAGATION_SUPPORTS:A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行;
PROPAGATION_MANDATORY:A如果有事务,B将使用该事务;如果A没有事务,B将抛异常;
PROPAGATION_REQUIRES_NEW:A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务;
PROPAGATION_NOT_SUPPORTED:A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行;
PROPAGATION_NEVER:A如果有事务,B将抛异常;A如果没有事务,B将以非事务执行;
PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务。

总结起来就是这张图:

是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

注意事务的是否“只读”属性,不同的数据库厂商支持不同,需要结合数据库厂商的具体说明,如Oracle的”readOnly”不起作用,不影响其增删改查;Mysql的”readOnly”为true时,只能查,增删改都会抛出异常。

事务超时

事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在TransactionDefinition中以int的值来表示超时时间,默认值是-1(单位是秒)。

设计事务时应当注意,为使应用程序很好地运行,事务不能运行太长时间,因为事务可能涉及对后端数据库的锁定,因此长时间的事务会不必要的占用数据库资源。

回滚规则

回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。

如果你需要自定义回滚,可以按照如下的策略进行:1、声明事务在遇到特定的检查型异常时,像遇到运行期异常那样回滚;2、声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

TransactionStatus事务状态

org.springframework.transaction.TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。TransactionStatus接口源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();// 是否是新的事物

boolean hasSavepoint();// 是否有恢复点

void setRollbackOnly();// 设置为只回滚

boolean isRollbackOnly();// 是否为只回滚

void flush();// 刷新

boolean isCompleted();// 是否已完成
}

Spring事务管理实现方式

Spring事务管理有两种方式:编程式事务管理和声明式事务管理。

编程式事务管理

编程式事务管理通过TransactionTemplate手动管理事务,在实际应用中很少使用,但是可以了解一下。

编程式事务管理实现方式通常有两种:事务管理器方式和模板事务方式。

事务管理器(Platform Transaction Manaager)方式,类似应用JTA UserTransaction API方式,但异常处理更简洁;其核心类为:Spring事务管理的三个接口类以及Jdbc Template类。

模板事务(Transaction Template)方式,此为Spring官方团队推荐的编程式事务管理方式;主要工具为Jdbc Template类。

声明式事务管理

声明式事务管理在实际开发中非常常见,因此需要仔细学习。声明式事务管理基于AOP模式,对方法进行前后拦截。

声明式事务管理的配置类型有5种,但是由于3中在Spring2.0后不推荐使用,因此这里主要就是两种:tx拦截器和全注释。

声明式事务管理有三种实现方式:基于TransactionProxyFactoryBean的方式基于AspectJ的XML方式基于注解的方式。接下来将以用户转账为例来学习这三种不同的实现方式。

第一步,新建数据库spring_money和表account:

1
2
3
4
5
6
7
8
9
10
11
12
drop database if exists spring_money;
create database spring_money;
use spring_money;
create table account
(
id bigint auto_increment primary key,
name varchar(32) not null,
money bigint not null,
constraint account_name_uindex
unique (name)
);
insert into account (name, money) values('小明', 2000),('小白', 2000);

第二步,新建Maven项目spring_money:
其中pom.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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.envy</groupId>
<artifactId>Spring_money</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<spring.version>4.2.7.RELEASE</spring.version>
</properties>

<dependencies>
<!--Spring核心组件-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>

<!--Spring AOP组件-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

<!--AspectJ AOP开发需要引入的两个包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>

<!--MySql驱动,注意版本号-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>

<!--JDBC Template-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>

<!-- 测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>

第三步,创建一个实体包com.envy.entity,接着在里面新建Account实体类:

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
package com.envy.entity;

public class Account {
private int id;
private String name;
private Long money;

public int getId(){
return id;
}

public void setId(int id){
this.id=id;
}

public String getName(){
return name;
}

public void setName(String name){
this.name=name;
}

public Long getMoney(){
return money;
}

public void setMoney(Long money){
this.money = money;
}

public String toString(){
return "Account:id is"+id+";name is"+ name+ ";money is"+money;
}
}

第四步,创建一个Dao包com.envy.dao,接着在里面新建TransferDao接口:

1
2
3
4
5
6
7
8
9
10
package com.envy.dao;

public interface TransferDao {
//付款,name账户名称,amount支出金额
void payMoney(String name,Long amount);

//收款,name账户名称,amount收到金额
void collectMoney(String name,Long amount);

}

然后在com.envy.dao中新建一个Impl包,在Impl中新建TransferDao接口的实现类TransferDaoImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.envy.dao.Impl;

import com.envy.dao.TransferDao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class TransferDaoImpl extends JdbcDaoSupport implements TransferDao {
//付款,name账户名称,amount支出金额
public void payMoney(String name, Long amount) {
String sql = "update account set money = money-amount where name=?";
this.getJdbcTemplate().update(sql,amount,name);
}

//收款,name账户名称,amount收到金额
public void collectMoney(String name, Long amount) {
String sql = "update account set money = money+amount where name=?";
this.getJdbcTemplate().update(sql,amount,name);
}
}

第五步,创建一个service包com.envy.service,接着在里面新建TransferService接口:

1
2
3
4
5
package com.envy.service;

public interface TransferService {
void transferMoney(String source,String destination, Long amount);
}

然后在com.envy.service中新建一个Impl包,在Impl中新建TransferService接口的实现类TransferServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.envy.service.Impl;

import com.envy.dao.TransferDao;
import com.envy.service.TransferService;

public class TransferServiceImpl implements TransferService {

private TransferDao transferDao;

public void setTransferDao(TransferDao transferDao){
this.transferDao=transferDao;
}

//转账操作,source支出方账户,destination收款方账户,amount转账金额
public void transferMoney(String source, String destination, Long amount) {
//付款操作
transferDao.payMoney(source,amount);
int i = 100/0;//此处用于测试抛异常时是否会回滚
//收款操作
transferDao.collectMoney(destination,amount);
}
}

第六步,创建一个db.properties配置文件:

1
2
3
4
5
6
7
8
#加载驱动
druid.driverClassName=com.mysql.jdbc.Driver
#加载数据库
druid.url=jdbc:mysql://localhost:3306/spring_money?useUnicode=true&characterEncoding=UTF-8
#用户名
druid.username=root
#密码
druid.password=root

第七步,创建一个applicationContext.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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--不使用外部db.properties配置文件-->
<!--配置数据源-->
<!--<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
<!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://127.0.0.1:3306/selection_course?useUnicode=true&amp;characterEncoding=utf-8"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="envy123"/>-->
<!--</bean>-->

<!--读取外部db.properties配置信息-->
<context:property-placeholder location="classpath:db.properties"/>
<!--开启包扫描-->
<context:component-scan base-package="com.envy.service.Impl"/>
<!--配置数据源-->
<bean id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close">
<!--数据库基本信息配置-->
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
<property name="driverClassName" value="${druid.driverClassName}"/>
</bean>


<bean id="transferDao" class="com.envy.dao.Impl.TransferDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>


<bean id="transferService" class="com.envy.service.Impl.TransferServiceImpl">
<property name="transferDao" ref="transferDao"/>
</bean>

</beans>

第八步,按照前面所述3种方式开始测试,注意每次测试均从此处开始。

基于TransactionProxyFactoryBean的方式

首先在applicationContext.xml文件中添加事务管理器相关配置和TransactionProxyFactoryBean代理对象(前七步的代码这里不再重复贴出,这里这贴出与本方式相关的代码):

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
<!--基于TransactionProxyFactoryBean的方式-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--配置业务层的代理-->
<bean id="transferServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--配置目标对象-->
<property name="target" ref="transferService" />
<!--注入事务管理器-->
<property name="transactionManager" ref="transactionManager" />
<!--注入事务属性-->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事务的传播行为
* ISOLATION :事务的隔离级别
* readOnly :是否只读
* -Exception :发生哪些异常回滚事务
* +Exception :发生哪些异常不回滚事务
-->
<prop key="transfer*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

接着新建一个测试类SpringTransactionApplicationTest

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
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTest {

@Autowired
private TransferService transferService;

@Resource(name="transferServiceProxy")
private TransferService transferServiceProxy;

@Test
public void TestOne(){
//注意,此处使用的是代理对象transferServiceProxy,而不是transferService
transferServiceProxy.transferMoney("小明","小白",66L);
}
}

运行结果为:

1
java.lang.ArithmeticException: / by zero

可以看到执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变:(如果将transferMoney方法中的int i = 100/0;代码注释掉,则转账会成功。)

基于AspectJ的XML方式

首先在applicationContext.xml文件中添加事务管理器的配置、事务的增强以及切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--基于AspectJ的XML方式-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut1" expression="execution(* com.envy.service.Impl.*ServiceImpl.*(..))"/>
<!--配置AOP的切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>

接着新建一个测试类SpringTransactionApplicationTestTwo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTestTwo {

@Autowired
private TransferService transferService;

@Test
public void TestTwo(){
transferService.transferMoney("小明","小白",88L);
}
}

运行结果为:

1
java.lang.ArithmeticException: / by zero

可以看到执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变:

如果将transferMoney方法中的int i = 100/0;代码注释掉,则转账会成功:

基于注解的方式

首先在applicationContext.xml文件中添加事务管理器的配置和开启事务注解:

1
2
3
4
5
6
7
8
<!--基于注解的方式-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>

接着在TransferServiceImpl类中的事务方法transferMoney中添加@Transactional注解:

1
2
3
4
5
6
7
8
@Transactional
public void transferMoney(String source, String destination, Long amount) {
//付款操作
transferDao.payMoney(source,amount);
// int i = 100/0;//此处用于测试抛异常时是否会回滚
//收款操作
transferDao.collectMoney(destination,amount);
}

最后新建一个测试类SpringTransactionApplicationTestThree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTestThree {

@Autowired
private TransferService transferService;

@Test
public void TestThree(){
transferService.transferMoney("小明","小白",99L);
}
}

运行结果为:

1
java.lang.ArithmeticException: / by zero

可以看到执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变:

如果将transferMoney方法中的int i = 100/0;代码注释掉,则转账会成功:

编程式事务管理和声明式事务管理区别

编程式事务管理允许用户在代码中精确定义事务的边界;声明式事务管理有助于用户将操作与事务规则进行解耦(基于AOP交由Spring容器来实现,实现关注点聚焦在业务逻辑上)。

概括来说,编程式事务管理侵入到了业务代码里面,但是提供了更加详细的事务管理,而声明式编程由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

那么如何选择编程式事务管理和声明式事务管理呢?小型应用,事务操作少。建议使用编程式事务管理来实现Transaction Template,因为它简单、显示操作、直观明显、可以设置事务的名称。

对于那些大型应用,事务操作多。建议使用声明式事务管理来实现,因为它业务复杂度高、关联性紧密,关注点聚焦到业务层面,实现了业务和事务的解耦。

关于事务管理器的选择,需要基于不同的数据源来选择相对应的事务管理器,并选择正确的Platform TransactionManager实现类,而全局事务的选择可以使用JtaTransactionManager

声明式事务管理的三种实现方式总结

在声明式事务管理的三种实现方式中,基于TransactionProxyFactoryBean的方式需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean对象进行增强,所以开发中很少使用;基于AspectJ的XML方式一旦在XML文件中配置后,不需要修改源代码,所以开发中经常使用;基于注解的方式开发较为简单,配置后只需要在事务类上或方法上添加@Transactiona注解即可,所以开发中也经常使用。