编程式和声明式事务
在Spring Boot中,事务管理分为声明式事务和编程式事务两种方式。
声明式事务
声明式事务是一种基于AOP(面向切面编程)的事务管理方式,通过在方法或类上添加@Transactional
注解来实现事务的自动管理。Spring Boot会自动为带有@Transactional
注解的方法创建一个代理对象,拦截方法调用并在执行前后添加事务管理逻辑。这种方式的优点是代码简洁,易于维护,不需要在业务代码中显式地处理事务提交和回滚操作。
例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
}
在上述示例中,createUser
方法带有@Transactional
注解,因此Spring Boot会自动为其管理事务。
编程式事务
编程式事务是一种在代码中显式地处理事务提交和回滚的事务管理方式。通过使用PlatformTransactionManager
接口和TransactionTemplate
类,可以在代码中手动控制事务的开始、提交和回滚。这种方式的优点是更灵活,可以根据具体业务场景选择性地进行事务管理,但同时也带来了更多的编码工作和复杂性。
例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public void createUser(User user) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
try {
userRepository.save(user);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
return null;
});
}
}
在上述示例中,createUser
方法使用TransactionTemplate
显式地处理事务。如果userRepository.save(user)
执行过程中发生异常,则事务会被回滚。
@Transactional参数详解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, transactionManager = "transactionManager")
下面是@Transactional
注解的主要参数及其作用:
-
value:指定事务管理器的名称。当有多个事务管理器时,可以通过此参数指定使用哪个事务管理器。默认情况下,Spring Boot会自动选择一个事务管理器。
示例:
@Transactional(value = "customTransactionManager")
-
propagation:指定事务的传播行为。事务传播行为定义了事务方法与调用者之间的事务关系。传播行为包括:
REQUIRED
(默认值)、REQUIRES_NEW
、SUPPORTS
、NOT_SUPPORTED
、MANDATORY
、NEVER
和NESTED
。示例:
@Transactional(propagation = Propagation.REQUIRED)
-
isolation:指定事务的隔离级别。事务隔离级别用于定义事务间的可见性,以解决脏读、不可重复读和幻读等问题。隔离级别包括:
READ_UNCOMMITTED
、READ_COMMITTED
(默认值)、REPEATABLE_READ
和SERIALIZABLE
。示例:
@Transactional(isolation = Isolation.READ_COMMITTED)
-
timeout:指定事务的超时时间(以秒为单位)。如果在指定时间内事务未完成,则会自动回滚。默认值为
1
,表示使用事务管理器的默认超时设置。示例:
@Transactional(timeout = 30)
-
readOnly:指定事务是否为只读。只读事务能提高性能,因为它不需要追踪数据的变更。此参数对于查询操作非常有用。默认值为
false
。示例:
@Transactional(readOnly = true)
-
rollbackFor:指定哪些异常类型会导致事务回滚。接受一个或多个
Throwable
子类。默认情况下,只有运行时异常(RuntimeException
)和错误(Error
)会导致事务回滚。示例:
@Transactional(rollbackFor = CustomException.class)
-
rollbackForClassName:与
rollbackFor
功能相同,但接受异常类名的字符串数组。示例:
@Transactional(rollbackForClassName = "com.example.CustomException")
-
noRollbackFor:指定哪些异常类型不会导致事务回滚。接受一个或多个
Throwable
子类。此参数与rollbackFor
相反。示例:
@Transactional(noRollbackFor = CustomException.class)
-
noRollbackForClassName:与
noRollbackFor
功能相同,但接受异常类名的字符串数组。示例:
@Transactional(noRollbackForClassName = "com.example.CustomException")
了解并合理使用这些参数,可以让您更好地控制事务的行为和性能。
事务传播(propagation)
事务传播行为定义了事务方法与调用者之间的事务关系。
REQUIRED | 默认的传播属性,表示如果当前环境存在事务就保持此事务执行,否则新开一个事务并且在新事务中执行 |
---|---|
REQUIRES_NEW | 表示不管当前环境是否存在事务,都新建一个事务并在新事务中执行,将原有事务进行挂起 |
SUPPORTS | 表示如果当前环境存在事务,就在此事务中执行,否则不以事务方式执行 |
NOT_SUPPORTED | 表示此方法不进行事务控制,如果当前环境存在事务,则挂起 |
MANDATORY | 表示此方法必须在一个事务中进行,如果当前环境没有事务则抛出异常 |
NEVER | 表示此方法运行不能有事务控制,一旦有事务传播至此就抛出异常 |
NESTED | 表示如果事务存在,则运行在一个嵌套的事务中,如果没有事务,则按REQUIRED属性执行 |
声明式事务实现原理
- 当Spring Boot检测到一个类或方法使用了
@Transactional
注解时,会为该类创建一个代理对象(基于JDK动态代理或CGLIB代理)。 - 代理对象会拦截带有
@Transactional
注解的方法调用,在方法执行前后添加事务管理逻辑。 - 事务管理器(Transaction Manager)是事务处理的核心组件,负责事务的创建、提交和回滚。
事务失效场景
类内部访问的方式调用方法
在Spring Boot中,当使用@Transactional
注解实现声明式事务时,事务管理是基于AOP代理实现的。如果在同一个类中,一个非事务方法内部直接调用另一个带有@Transactional
注解的方法,事务会失效。这是因为类内部方法调用不会经过AOP代理,因此事务相关的逻辑不会被执行。
下面是一个示例说明事务失效的情况:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUserWithoutTransaction(User user) {
// 类内部直接调用带有@Transactional注解的方法
createUserWithTransaction(user);
}
@Transactional
public void createUserWithTransaction(User user) {
userRepository.save(user);
// 模拟异常,期望回滚事务
throw new RuntimeException("Simulated exception");
}
}
在这个示例中,createUserWithTransaction
方法带有@Transactional
注解,而createUserWithoutTransaction
方法没有。当我们调用createUserWithoutTransaction
方法时,它内部直接调用了createUserWithTransaction
。由于这是类内部方法调用,AOP代理不会拦截这次调用,因此事务失效,抛出的异常不会导致事务回滚。
@Transactional 作用于私有方法,final 方法
在Spring Boot中,将@Transactional
注解标注在非public
方法上会导致事务失效,原因在于Spring AOP的实现方式。
Spring AOP主要使用JDK动态代理(基于接口)或CGLIB代理(基于类,需要类不是final
的)来实现。当为一个类创建AOP代理时,代理对象会拦截类的public
方法调用,然后在方法执行前后添加事务管理逻辑。对于非public
方法(如private
、protected
或默认访问级别的方法),AOP代理无法拦截这些方法调用,因此事务相关逻辑不会被执行,导致事务失效。
异常不匹配
异常类型
在Spring Boot中,如果异常类型不匹配,事务可能失效。这是因为Spring Boot默认只会在遇到运行时异常(RuntimeException
)或Error
时回滚事务。对于已检查异常(checked exceptions),默认情况下Spring Boot不会回滚事务。
原因在于,已检查异常通常表示可预期的异常情况,而运行时异常和Error
通常表示程序中未预期的错误。Spring Boot的设计者认为,如果一个方法抛出已检查异常,那么该异常应该已经被调用者预料到,并且调用者应该知道如何处理这种情况,因此不需要自动回滚事务。
然而,在某些情况下,可能需要在遇到已检查异常时回滚事务。为了实现这一点,可以使用@Transactional
注解的rollbackFor
属性来指定需要回滚事务的异常类型。例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = IOException.class)
public void createUser(User user) throws IOException {
userRepository.save(user);
// ...其他逻辑,可能抛出已检查异常
throw new IOException("Simulated IOException");
}
}
在此示例中,createUser
方法可能会抛出IOException
(已检查异常)。通过将rollbackFor
属性设置为IOException.class
,告诉Spring Boot在遇到IOException
时回滚事务。
默认捕获异常代码
多线程
场景一: 父线程抛出异常,子线程没有抛异常
Spring Boot的事务管理基于ThreadLocal
存储事务上下文,这个上下文在多个线程间是不共享的。
父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚
场景二:父线程不抛出异常,子线程抛出异常
由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。