抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了如何在Spring中使用事务。

环境

Windows 10 企业版 LTSC 21H2
MySQL 5.7.40
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.2.25.RELEASE

1 编程式事务

编程式事务是指通过编程的方式管理事务,主要使用TransactionTemplate或PlatformTransactionManager控制事务。

1.1 TransactionTemplate

1.1.1 说明

TransactionTemplate是Spring提供的模板类,简化了事务的编程式使用。

1.1.2 配置方式

1.1.2.1 XML配置

创建服务类:

java
1
2
3
4
5
6
7
8
9
10
11
public class UserService {
private JdbcTemplate jdbcTemplate;
private TransactionTemplate transactionTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
// 事务方法
}

创建配置文件:

spring.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建UserService对象 -->
<bean id="userService" class="com.example.service.UserService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 配置连接池 -->
<property name="initialSize" value="10"/>
<property name="minIdle" value="10"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
</bean>

<!-- 配置数据模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务管理器 -->
<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>
</beans>

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 获取UserService对象
UserService userService = context.getBean(UserService.class);
// 调用事务方法
System.out.println(userService.updateUserWithTransaction());
// 关闭容器
context.close();
}
}
1.1.2.2 注解配置

修改服务类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {
private JdbcTemplate jdbcTemplate;
private TransactionTemplate transactionTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Autowired
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
// 事务方法
}

创建配置类:

java
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
@Configuration
@ComponentScan("com.example")
public class DemoConfig {
// 配置数据连接
@Bean
public DataSource dataSource() {
// 配置数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 连接池配置
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(20);
dataSource.setMaxWait(60000);
return dataSource;
}
// 配置数据模板
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
// 配置事务模板
@Bean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(transactionManager());
}
}

修改启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取UserService对象
UserService userService = context.getBean(UserService.class);
// 调用事务方法
System.out.println(userService.updateUserWithTransaction());
// 关闭容器
context.close();
}
}

1.1.3 事务方法

示例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Boolean updateUserWithTransaction() {
return transactionTemplate.execute(status -> {
try {
// 执行业务逻辑
String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
// 更新张三信息
jdbcTemplate.update(sql, "张三", 18, "zhangsan@example.com", 1);
// 手动标记回滚
if (new User().getId() == null) {
status.setRollbackOnly();
return false;
}
// 出现异常,自动标记回滚
System.out.println(new User().getId().toString());
// 更新李四信息
jdbcTemplate.update(sql, "李四", 18, "lisi@example.com", 2);
// 执行成功,自动提交
return true;
} catch (Exception e) {
// 抛出异常,自动标记回滚,如果不想抛出异常,需要手动标记回滚
throw new RuntimeException(e);
}
});
}

1.2 PlatformTransactionManager

1.2.1 说明

直接使用PlatformTransactionManager进行更细粒度的事务控制,DataSourceTransactionManager间接实现了PlatformTransactionManager接口。

核心步骤:

  1. 定义事务属性:创建TransactionDefinition对象,配置隔离级别和传播行为等属性。
  2. 手动开启事务:获取TransactionStatus对象。
  3. 执行业务逻辑:编写数据库CRUD操作。
  4. 手动提交事务:业务成功时提交事务。
  5. 手动回滚事务:业务异常时回滚事务。
  6. 异常机制兜底:确保每个开启的事务都有对应的提交操作和回滚操作,避免事务悬挂。

1.2.2 配置方式

XML配置和注解配置与TransactionTemplate相同,TransactionTemplate使用transactionTemplate进行事务控制,PlatformTransactionManager使用transactionManager进行事务控制。

1.2.3 事务方法

示例:

java
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
public Boolean updateUserWithTransaction() {
// 定义事务属性
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 开启事务
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 执行业务逻辑
String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
// 更新张三信息
jdbcTemplate.update(sql, "张三", 18, "zhangsan@example.com", 1);
// 标记回滚,返回失败
if (new User().getId() == null) {
status.setRollbackOnly();
return false;
}
// 出现异常,回滚事务
System.out.println(new User().getId().toString());
// 更新李四信息
jdbcTemplate.update(sql, "李四", 18, "lisi@example.com", 2);
// 提交事务,返回成功
transactionManager.commit(status);
return true;
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
// 是否抛出异常不会影响事务
throw new RuntimeException(e);
} finally {
// 根据标记执行事务,确保事务结束,防止连接泄漏
if (!status.isCompleted()) {
transactionManager.rollback(status);
}
}
}

2 声明式事务

声明式事务是通过配置的方式管理事务,无需将事务管理代码嵌入到业务逻辑中,底层基于AOP实现。

2.1 配置方式

2.1.1 XML配置

在XML配置文件中使用tx:advice标签配置事务通知,需要在XML文件中添加tx命名空间。

创建服务类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserService {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 执行业务操作
public Boolean updateUserWithTransaction() {
// 执行业务逻辑
String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
// 更新张三信息
jdbcTemplate.update(sql, "张三", 18, "zhangsan@example.com", 1);
// 出现异常,回滚事务
System.out.println(new User().getId().toString());
// 更新李四信息
jdbcTemplate.update(sql, "李四", 18, "lisi@example.com", 2);
// 执行成功,提交事务
return true;
}
}

创建配置文件:

spring.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
<?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: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/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">
<!-- 创建UserService对象 -->
<bean id="userService" class="com.example.service.UserService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 配置连接池 -->
<property name="initialSize" value="10"/>
<property name="minIdle" value="10"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
</bean>

<!-- 配置数据模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务通知,需要在XML文件中添加tx命名空间 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 更新方法,开启事务,默认传播行为REQUIRED,异常回滚 -->
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<!-- 查询方法,只读事务,优化数据库性能,无回滚 -->
<tx:method name="select*" read-only="true"/>
<!-- 其他方法,默认开启事务 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- 配置切面,将事务通知织入到服务层所有方法 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="txPointcut" expression="execution(* com.example.service.*.*(..))"/>
<!-- 配置事务通知与切入点绑定 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 获取UserService对象
UserService userService = context.getBean(UserService.class);
// 调用事务方法
System.out.println(userService.updateUserWithTransaction());
// 关闭容器
context.close();
}
}

2.1.2 半注解配置

在XML配置文件中使用tx:annotation-driven标签启用事务管理注解,支持通过@Transactional注解完成声明式事务的配置,代替在XML配置文件中配置事务通知和切面。

使用@Transactional注解管理事务,可以在类和方法上使用,这是声明式事务最常用的方式。

修改服务类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class UserService {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 执行业务操作
@Transactional
public Boolean updateUserWithTransaction() {
// 执行业务逻辑
String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
// 更新张三信息
jdbcTemplate.update(sql, "张三", 18, "zhangsan@example.com", 1);
// 出现异常,回滚事务
System.out.println(new User().getId().toString());
// 更新李四信息
jdbcTemplate.update(sql, "李四", 18, "lisi@example.com", 2);
// 执行成功,提交事务
return true;
}
}

修改配置文件:

spring.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
<?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"
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">
<!-- 启用注解扫描 -->
<context:component-scan base-package="com.example"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 配置连接池 -->
<property name="initialSize" value="10"/>
<property name="minIdle" value="10"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
</bean>

<!-- 配置数据模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 启用事务管理注解,默认使用DataSourceTransactionManager事务管理器,可以省略 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

2.1.3 全注解配置

使用@EnableTransactionManagement注解启用事务管理注解,代替在XML配置文件中使用tx:annotation-driven标签。

创建配置类:

java
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
@Configuration
@EnableTransactionManagement
@ComponentScan("com.example")
public class DemoConfig {
// 配置数据连接
@Bean
public DataSource dataSource() {
// 配置数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 连接池配置
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(20);
dataSource.setMaxWait(60000);
return dataSource;
}
// 配置数据模板
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}

修改启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取UserService对象
UserService userService = context.getBean(UserService.class);
// 调用事务方法
System.out.println(userService.updateUserWithTransaction());
// 关闭容器
context.close();
}
}

2.2 事务注解

常用属性:

属性名 作用 取值
value或transactionManager 指定事务管理器 名称
propagation 指定事务传播行为 Propagation枚举值
isolation 指定事务隔离级别 Isolation枚举值
timeout 指定事务超时时间 秒数,默认为-1表示不超时
readOnly 指定是否为只读事务 默认为false表示非只读事务,设置为true表示只读事务
rollbackFor 指定哪些异常触发回滚 异常类的Class
noRollbackFor 指定哪些异常不触发回滚 异常类的Class

传播行为:

  • Propagation.REQUIRED:有则加入,无则新建(默认)
  • Propagation.REQUIRES_NEW:有也新建,无也新建
  • Propagation.SUPPORTS:有则加入,无则忽略
  • Propagation.NOT_SUPPORTED:有也忽略,无也忽略
  • Propagation.MANDATORY:有则加入,无则报错
  • Propagation.NEVER:有则报错,无则忽略
  • Propagation.NESTED:有则嵌套,无则新建

隔离级别:

  • Isolation.DEFAULT:使用数据库默认的隔离级别
  • Isolation.READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据变更,但是更新相同数据会被阻塞。可避免脏写
  • Isolation.READ_COMMITTED:大多数系统的默认隔离级别,允许读取已提交的数据变更。可避免脏写、脏读
  • Isolation.REPEATABLE_READ:MySQL默认隔离级别,对同一字段的多次读取结果是一致的,除非数据被当前事务本身改变。可避免脏写、脏读、不可重复读
  • Isolation.SERIALIZABLE:最高的隔离级别,通过强制事务排序解决并发问题。在每个读操作的数据行增加共享锁,可能导致大量超时和竞争。可避免脏写、脏读、不可重复读、幻读。

注意事项:

  • 只有来自外部的方法调用才会被AOP拦截从而应用事务。同一个类内部方法之间的调用不会被事务拦截。
  • 默认情况下,只有遇到运行时异常(RuntimeException)才会回滚,而受检异常(CheckedException)不会导致回滚。可以使用rollbackFor属性指定需要回滚的异常类型。
  • @Transactional注解标记的方法必须是public方法,否则事务不会生效。
  • 事务方法应该尽量避免过多的业务逻辑,保持方法的简洁性,以便更好地控制事务边界。

评论