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

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

环境

Windows 10 企业版 LTSC 21H2
MySQL 5.7.40
Java 1.8
Maven 3.6.3
Spring 5.3.23

1 编程式事务

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

1.1 TransactionTemplate

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

1.1.1 配置方式

1.1.1.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
<?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">
<constructor-arg ref="dataSource"/>
</bean>

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

<!-- 配置事务模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg 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.updateWithTransaction());
// 关闭容器
context.close();
}
}
1.1.1.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(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 配置事务模板
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
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.updateWithTransaction());
// 关闭容器
context.close();
}
}

1.1.2 事务方法

示例:

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 updateWithTransaction() {
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

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

核心步骤:

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

1.2.1 配置方式

使用PlatformTransactionManager进行事务控制,不需要使用TransactionTemplate控制事务。

1.2.2 事务方法

示例:

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 updateWithTransaction() {
// 定义事务属性
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配置

spring.xml配置文件中使用tx:advice标签配置事务通知,需要添加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 updateWithTransaction() {
// 执行业务逻辑
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
<?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">
<constructor-arg ref="dataSource"/>
</bean>

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

<!-- 配置事务通知,需要添加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.updateWithTransaction());
// 关闭容器
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 updateWithTransaction() {
// 执行业务逻辑
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
<?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">
<constructor-arg ref="dataSource"/>
</bean>

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

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

常用属性:

属性名 作用 取值
value或transactionManager 指定事务管理器 名称
propagation 指定事务传播行为 Propagation枚举值:
  • Propagation.REQUIRED:有则加入,无则新建(默认)
  • Propagation.REQUIRES_NEW:有也新建,无也新建
  • Propagation.SUPPORTS:有则加入,无则忽略
  • Propagation.NOT_SUPPORTED:有也忽略,无也忽略
  • Propagation.MANDATORY:有则加入,无则报错
  • Propagation.NEVER:有则报错,无则忽略
  • Propagation.NESTED:有则嵌套,无则新建
isolation 指定事务隔离级别 Isolation枚举值:
  • Isolation.DEFAULT:使用数据库默认的隔离级别
  • Isolation.READ_UNCOMMITTED:读未提交
  • Isolation.READ_COMMITTED:读已提交
  • Isolation.REPEATABLE_READ:可重复读
  • Isolation.SERIALIZABLE:串行化
timeout 指定事务超时时间 秒数,默认为-1表示不超时
readOnly 指定是否为只读事务 默认为false表示非只读事务,设置为true表示只读事务
rollbackFor 指定哪些异常触发回滚 异常类的Class
noRollbackFor 指定哪些异常不触发回滚 异常类的Class

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(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
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.updateWithTransaction());
// 关闭容器
context.close();
}
}

2.2 失效情况

事务失效情况:

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

评论