[toc]

[toc]

Spring事务管理

1. Spring事务管理概述

1.1 编程式事务管理

​ 使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
*
* 编程式事务:
* TransactionFilter{
* try{
* // 获取连接
* // 设置非自动提交
* chain.doFiler();
* //提交
* }catch(){
* //异常回滚
* }finally{
* //释放资源
* }
* }
*/

1.2 声明式事务管理

以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可;

  • Spring自动进行事务控制
  • 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。

1.3 Spring提供的事务管理器

  1. Spring既支持编程式事务管理,也支持声明式的事务管理。
  2. Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
  3. Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
  4. 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

1.4 事务管理器的主要实现

  1. DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
  2. JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
  3. HibernateTransactionManager:用Hibernate框架存取数据库

img

2. 基于注解的事务配置

2.1 测试需要数据表

account

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
`username` varchar(50) NOT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;

INSERT INTO `account` VALUES ('Tom', 1000);

SET FOREIGN_KEY_CHECKS = 1;

book

1
2
3
4
5
6
7
8
9
10
11
12
DROP TABLE IF EXISTS `book`;

CREATE TABLE `book` (
`isbn` varchar(50) NOT NULL,
`book_name` varchar(100) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;

INSERT INTO `book` VALUES ('ISBN-001', 'book01', 100);

SET FOREIGN_KEY_CHECKS = 1;

book_stock

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS `book_stock`;

CREATE TABLE `book_stock` (
`isbn` varchar(50) NOT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;


INSERT INTO `book_stock` VALUES ('ISBN-001', 1000);
SET FOREIGN_KEY_CHECKS = 1;

2.2 添加依赖

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
<dependencies>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<!-- <scope>test</scope>-->
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>

<!--AOP-->
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.2.0</version>
</dependency>

<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.6.8.RELEASE</version>
</dependency>

<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
</dependencies>

2.3 配置文件

db.properties

1
2
3
4
jdbc.user=root
jdbc.password=******
jdbc.jdbcUrl=jdbc:mysql://cdb-o6r75r3g.bj.tencentcdb.com:10015/tx
jdbc.driverClass=com.mysql.jdbc.Driver

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
<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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">


<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

<!--组件扫描-->
<context:component-scan base-package="cn.justweb"></context:component-scan>

<!--数据源的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.jdbcUrl}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--1. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"></property>
</bean>

<!--2.开启基于注解的书屋控制模式;-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

<!--3.给事务方法加注解-->
</beans>

2.4 BookDao

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
/**
* @Date 2020/5/28 14:08
* @Version 10.21
* @Author DuanChaojie
*/
@Repository
public class BookDao {

@Autowired
private JdbcTemplate jdbcTemplate;

/**
* 减去消费的钱数
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE account set balance=balance-? where username=?";
// 测试使用
//int a = 10/0;
jdbcTemplate.update(sql,price,userName);
}

/**
* 获取余额
* @return
*/
public int getPrice(String isbn){
String sql = "select price from book where isbn=?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}

/**
* 减去库存数量
*/
public void updateStock(String isbn){
String sql = "update book_stock set stock=stock-1 where isbn=?";
jdbcTemplate.update(sql,isbn);
}
}

2.5 BookService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @Date 2020/5/28 14:22
* @Version 10.21
* @Author DuanChaojie
*/
@Service
public class BookService {

@Autowired
private BookDao bookDao;

@Transactional
public void checkout(String username,String isbn){
// 更新库存
bookDao.updateStock(isbn);

int price = bookDao.getPrice(isbn);

// 更新余额
bookDao.updateBalance(username,price);
}
}

2.6 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @Date 2020/5/28 14:25
* @Version 10.21
* @Author DuanChaojie
*/
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");

/**
* 没有事物的情况分析:使updateBalance()方法出错测试其结果:
* 结果就是:库存更新了而余额没有更新
* 添加过@Transactional后就解决了以上问题。
*/
@Test
public void test(){
BookService bookService = ioc.getBean(BookService.class);
System.out.println("bookService = " + bookService);
System.out.println(bookService.getClass().getClassLoader());

bookService.checkout("Tom","ISBN-001");

System.out.println("结账完成!");
}
}

3. 基于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
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

<context:component-scan base-package="cn.justweb"></context:component-scan>


<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />

<!-- 1、配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.jdbcUrl}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!-- 2、配置JdbcTemplate操作数据库 value="#{pooledDataSource}" ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{dataSource}"></property>
</bean>

<!-- 3、配置声明式事务
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、开启基于注解的事务式事务;依赖tx名称空间
3)、给事务方法加注解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--
基于xml配置的事务;依赖tx名称空间和aop名称空间
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、配置出事务方法;
3)、告诉Spring哪些方法是事务方法;
(事务切面按照我们的切入点表达式去切入事务方法)
-->
<aop:config>
<aop:pointcut expression="execution(* cn.justweb.ser*.*.*(..))" id="txPoint"/>
<!--
事务建议;事务增强
advice-ref:指向事务管理器的配置
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>

<!--
配置事务管理器;
事务建议;事务增强;事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;
切入点表达式只是说,事务管理器要切入这些方法,
哪些方法加事务使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="10"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>

<!-- 都用;重要的用配置,不重要的用注解 -->

</beans>

4. Spring事务管理细节

@Transactional中的属性,比如说隔离级别,事务传播行为,超时时间,是否只读等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 事务细节:
*
* 异常分类:
* 运行时异常:可以不用处理默认都回滚
* 编译时异常: 要么try要么throws 默认不回滚。
*
* noRollbackForClassName String[] 哪些异常事务可以不回滚
* noRollbackForClassName String[]
*
* rollbackFor String[] 哪些异常事务需要回滚
* rollbackForClassName String[]
*
*/

4.1 事务的隔离级别

  1. isolation 事务的隔离级别
  2. 数据库事务详解

4.2 事务的传播行为

  1. 事务传播属性可以在@Transactional注解的propagation属性中定义。
  2. 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
  3. img
4.2.1 REQUIRED传播行为

@Transactional(propagation = Propagation.REQUIRED)

required

4.2.2 REQUIRES_NEW传播行为

@Transactional(propagation = Propagation.REQUIRES_NEW)

requires_new

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
/**
* 有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象
* @throws FileNotFoundException
*
*
* multx(){
* //REQUIRED
* A(){
* //REQUIRES_NEW
* B(){}
* //REQUIRED
* c(){}
* }
*
* //REQUIRES_NEW
* D(){
* DDDD()// REQUIRES_NEW不崩,REQUIRED崩
* //REQUIRED
* E(){
* //REQUIRES_NEW
* F(){
* //int num = 10/0(E崩,G崩,D崩,A,C崩)
* }
* }
* //REQUIRES_NEW
* G(){}
* }
*
*
* 10/0(B成功,D整个分支下全部成功)
* 任何处崩,已经执行的REQUIRES_NEW都会成功;
*
* 如果是REQUIRED;事务的属性都是继承于大事务的;
* 而propagation=Propagation.REQUIRES_NEW可以调整
* 默认:REQUIRED;
*
* REQUIRED:将之前事务用的connection传递给这个方法使用;
* REQUIRES_NEW:这个方法直接使用新的connection;
* }
*
*/

4.3 事务的超时和只读属性

@Transactional(timeout = 10,readOnly = true)

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

  • 超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
  • 只读事务属性 : 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。