Spring Boot 事务管理
事务概述
Spring Boot提供了事务的自动配置,spring-boot-starter-data-jdbc启动器会引入事务相关的包
- TransactionAutoConfiguration
- DataSourceTransactionManagerAutoConfiguration
上述两个自动配置类注册在新的自动配置文件中,前者是事务相关的自动配置,后者是事务管理器的自动配置,对应的参数绑定类为Transaction
Properties,通过一系列spring.transaction.*参数可以自定义一些事务管理。
如果没有特殊的事务要求,那么使用默认的自动配置的事务即可,只需要在代理方法上添加@Transactional注解即可
@RequiredArgsConstructor
@Service
public class UserDaoImpl implements UserDao {
public final JdbcTemplate jdbcTemplate;
@Transactional
@Override
public void update() {
jdbcTemplate.execute("update t_user set username = 'Petty' where id = 1");
jdbcTemplate.execute("update t_user set username = 'Yoga' where id = 2");
throw new RuntimeException("test exception");
}
}
上述代码中,update方法添加了@Transactional注解,当执行update方法时,两条SQL语句会被包装在一个事务中,如果抛出异常,事务会回滚。
如果将异常代码注释后,两条SQL语句会被提交到数据库中。
如果将@Transactional注解添加到类上,那么类中的所有方法都会被包装在一个事务中。例如
@RequiredArgsConstructor
@Service
@Transactional
public class UserDaoImpl implements UserDao {
public final JdbcTemplate jdbcTemplate;
@Override
public void update() {
jdbcTemplate.execute("update t_user set username = 'Petty' where id = 1");
jdbcTemplate.execute("update t_user set username = 'Yoga' where id = 2");
throw new RuntimeException("test exception");
}
// ....其他方法
}
事务失效的场景
上述的案例中,在测试事务的时候单独创建了一个DAO实现类,这是为了能够启用Spring中的代理,使用@Transactional注解。
如果直接在Controller中调用DAO的方法,那么事务是不会生效的,因为Spring的事务是通过AOP(面向切面编程)实现的,只有通过代理对象调用方法时,事务才会生效。
下面是几个事务失效的场景
1、数据库引擎不支持事务
在MySQL的MyISAM引擎下,是不支持事务的,只有在InnoDB引擎下才支持事务。
这点需要注意,如果底层引擎不支持事务,那么再怎么设置也没有用。
2、没有被Spring管理
如果这个类没有加上@Service、@Component、@Repository、@Controller等注解,那么Spring是不会对这个类进行代理的,也就是说@Transactional注解是不会生效的。
底层原因就是这个类没有被加载成一个Bean,也就没有被Spring管理,事务自然就不会生效。
3、方法不是public的
@Transactional注解只能用于public修饰的方法上,如果方法不是public的,那么事务是不会生效的。
如果需要在非public方法上使用事务,可以使用AspectJ的方式,但是这种方式不是很常用。
4、方法内部调用事务方法(发生自身调用)
在发生自身调用的情况下,事务是不会生效的,因为Spring的事务是通过AOP实现的,只有通过代理对象调用方法时,事务才会生效。
示例如下:
- 示例1:
- 调用方法上不加@Transactional注解
@Service
public class OrderServiceImpl implements OrderService {
@Override
public void update(String orderId) {
updateOrder(orderId);
}
@Override
@Transactional
public void updateOrder(String orderId) {
// update order
}
}
- 示例2:
- 调用方法上加@Transactional注解
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void update(String orderId) {
updateOrder(orderId);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(String orderId) {
// update order
}
}
上述两个示例中的事务都不会生效,因为它们都发生了自身调用,就调用了该类自己的方法,而没有经过Spring
的代理类,默认只有调用外部代理类的方法,事务才会正常生效。
如果想要避免上述情况发生,在Spring中可以在当前线程中暴露并获取当前代理类,通过在启动类上添加@EnableAspectJAutoProxy(
exposeProxy = true)注解,然后通过AopContext.currentProxy()获取当前代理类,然后调用方法。
⚠️注意:Spring默认只有调用Spring代理类的public方法,事务才会生效。
5、没有配置事务管理器
如果没有配置DataSourceTransactionManager(数据源事务管理器),那么事务是不会生效的。
@Bean
public PlatformTransactionManager transactionManager(DataSource source) {
return new DataSourceTransactionManager(source);
}
6、设置了不支持事务的传播行为
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void update(String orderId) {
updateOrder(orderId);
}
@Override
// Propagation.NOT_SUPPORTED: Spring不会在事务中执行,如果当前存在事务,Spring会将当前事务挂起(不支持事务传播)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(String orderId) {
// update order
}
}
- Propagation.NOT_SUPPORTED
- Spring不会在事务中执行,如果当前存在事务,Spring会将当前事务挂起
- 主动不支持事务方式运行
- 不支持事务传播
7、异常没有被抛出
当在方法中将异常给捕获了,但没有抛出来,事务就不会回滚,因为Spring是通过捕获异常来判断是否回滚事务的。
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void update(String orderId) {
try{
updateOrder(orderId);
}catch (Exception e){
}
}
}
8、异常类型不匹配
Spring默认只对RuntimeException和Error进行回滚,对于其他异常不会进行回滚
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void update(String orderId) {
try{
updateOrder(orderId);
}catch (Exception e){
// 事务不会回滚,因为Spring默认只对RuntimeException和Error进行回滚
throw new Exception("test exception");
}
}
}
如果需要对其他异常进行回滚,可以通过rollbackFor属性进行设置。
@Service
public class OrderServiceImpl implements OrderService {
@Override
// rollbackFor: 指定异常类型,当发生指定异常时,事务会回滚
@Transactional(rollbackFor = Exception.class)
public void update(String orderId) {
try{
updateOrder(orderId);
}catch (Exception e){
// 事务不会回滚,因为Spring默认只对RuntimeException和Error进行回滚
throw new Exception("test exception");
}
}
}