摘要:本文学习了如何在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配置
创建服务类:
java1 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.xml1 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"> <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&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>
|
创建启动类:
java1 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 = context.getBean(UserService.class); System.out.println(userService.updateUserWithTransaction()); context.close(); } }
|
1.1.2.2 注解配置
修改服务类:
java1 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; } }
|
创建配置类:
java1 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()); } }
|
修改启动类:
java1 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 = context.getBean(UserService.class); System.out.println(userService.updateUserWithTransaction()); context.close(); } }
|
1.1.3 事务方法
示例:
java1 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.1 说明
直接使用PlatformTransactionManager进行更细粒度的事务控制,DataSourceTransactionManager间接实现了PlatformTransactionManager接口。
核心步骤:
- 定义事务属性:创建TransactionDefinition对象,配置隔离级别和传播行为等属性。
- 手动开启事务:获取TransactionStatus对象。
- 执行业务逻辑:编写数据库CRUD操作。
- 手动提交事务:业务成功时提交事务。
- 手动回滚事务:业务异常时回滚事务。
- 异常机制兜底:确保每个开启的事务都有对应的提交操作和回滚操作,避免事务悬挂。
1.2.2 配置方式
XML配置和注解配置与TransactionTemplate相同,TransactionTemplate使用transactionTemplate进行事务控制,PlatformTransactionManager使用transactionManager进行事务控制。
1.2.3 事务方法
示例:
java1 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命名空间。
创建服务类:
java1 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.xml1 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"> <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&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>
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <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>
|
创建启动类:
java1 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 = context.getBean(UserService.class); System.out.println(userService.updateUserWithTransaction()); context.close(); } }
|
2.1.2 半注解配置
在XML配置文件中使用tx:annotation-driven标签启用事务管理注解,支持通过@Transactional注解完成声明式事务的配置,代替在XML配置文件中配置事务通知和切面。
使用@Transactional注解管理事务,可以在类和方法上使用,这是声明式事务最常用的方式。
修改服务类:
java1 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.xml1 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&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>
<tx:annotation-driven transaction-manager="transactionManager"/> </beans>
|
2.1.3 全注解配置
使用@EnableTransactionManagement注解启用事务管理注解,代替在XML配置文件中使用tx:annotation-driven标签。
创建配置类:
java1 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()); } }
|
修改启动类:
java1 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 = 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方法,否则事务不会生效。
- 事务方法应该尽量避免过多的业务逻辑,保持方法的简洁性,以便更好地控制事务边界。
条