目录
@Transactional
基于注解的事务管理是一种常用的方式,可以通过在方法或类上添加注解来声明事务的传播行为、隔离级别、超时时间以及回滚规则等属性。
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public void updateUser(User user) {
// 更新用户信息
userRepository.save(user);
}
@Transactional(rollbackFor = Exception.class)
public void deleteUser(Long userId) {
// 删除用户
userRepository.deleteById(userId);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updateUserAndDeleteUser(User user, Long userId) {
// 更新用户信息
userRepository.save(user);
// 删除用户
userRepository.deleteById(userId);
}
}
在上面的示例中,@Transactional
注解被添加到了 updateUser()
、deleteUser()
和 updateUserAndDeleteUser()
方法上。这些方法声明了不同的事务传播行为、回滚规则等属性:
updateUser()
方法使用默认的事务传播行为,默认情况下,如果当前没有事务,会开启一个新的事务;如果已经存在事务,则加入到当前事务中。如果方法执行过程中发生异常,事务会回滚。deleteUser()
方法使用了rollbackFor
属性来指定了回滚的异常类型为Exception
,即当发生任何异常时都会回滚事务。updateUserAndDeleteUser()
方法使用了propagation
属性来指定了事务的传播行为为REQUIRED
,即如果当前已经存在事务,则加入到当前事务中;如果当前没有事务,则开启一个新的事务。同时,方法中的两个操作都会被包含在同一个事务中,如果方法执行过程中发生异常,事务会回滚。
通过使用 @Transactional
注解,我们可以很方便地在方法级别上声明事务的管理策略,而不需要显式地编写事务管理的代码。
事务的传播行为
事务的传播行为是指在一个事务方法调用另一个事务方法时,事务如何传播的规则。Spring框架提供了几种不同的事务传播行为,可以通过 @Transactional
注解的 propagation
属性来指定。下面是常用的事务传播行为:
-
REQUIRED(默认):如果当前没有事务,则开启一个新的事务;如果当前已经存在事务,则加入到当前事务中。
-
SUPPORTS:如果当前存在事务,则加入到当前事务中;如果当前没有事务,则以非事务的方式执行。
-
MANDATORY:必须在事务中执行,如果当前没有事务,则抛出异常。
-
REQUIRES_NEW:无论当前是否存在事务,都会开启一个新的事务,如果当前存在事务,则将当前事务挂起。
-
NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则将当前事务挂起。
-
NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
-
NESTED:如果当前存在事务,则在当前事务的嵌套事务中执行,如果当前没有事务,则开启一个新的事务。如果外部事务提交,那么嵌套事务也会被提交;如果外部事务回滚,那么嵌套事务也会被回滚。
通过合适地选择事务的传播行为,可以实现不同粒度的事务控制,并确保事务的一致性和可靠性。
隔离级别、超时时间以及回滚规则
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, rollbackFor = Exception.class)
public void updateUser(User user) {
userRepository.update(user);
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, rollbackFor = Exception.class)
public void deleteUser(int userId) {
userRepository.delete(userId);
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, rollbackFor = Exception.class)
public void createUser(User user) {
userRepository.create(user);
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, rollbackFor = Exception.class)
public User getUser(int userId) {
return userRepository.getById(userId);
}
}
在上面的示例中,@Transactional
注解被应用于 updateUser
、deleteUser
、createUser
和 getUser
方法上,并指定了事务的传播行为为 REQUIRED
,隔离级别为默认级别,超时时间为 30 秒,回滚规则为遇到任何异常都回滚。
隔离级别
事务的隔离级别定义了一个事务对数据库中数据的可见性程度,以及事务之间的相互影响程度。在数据库中,通常有以下四种隔离级别:
-
读未提交(Read Uncommitted):一个事务可以读取到另一个事务未提交的数据。这种隔离级别下可能会发生脏读、不可重复读和幻读的问题。
-
读已提交(Read Committed):一个事务只能读取到已经提交的数据。这种隔离级别下可以避免脏读,但仍可能发生不可重复读和幻读的问题。
-
可重复读(Repeatable Read):一个事务在其执行期间可以多次读取相同的数据,并且其他事务在该事务执行期间不能对这些数据进行修改。这种隔离级别下可以避免脏读和不可重复读,但仍可能发生幻读的问题。
-
串行化(Serializable):最高的隔离级别,确保每个事务都完全独立运行,事务之间不会相互干扰。串行化隔离级别可以避免所有类型的并发问题,但性能较低,因为它会对数据库中的数据进行锁定。
-
附:不可重复读强调的是同一行数据的内容在事务执行过程中发生了变化,而幻读强调的是查询结果集的行数在事务执行过程中发生了变化。不可重复读是对同一行数据的更新操作,而幻读是对数据集合的插入或删除操作。
在Spring框架中,使用 @Transactional
注解来管理事务,可以通过 isolation
属性来指定事务的隔离级别。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUser(User user) {
userRepository.update(user);
}
在这个示例中,事务的隔离级别被设置为 READ_COMMITTED
,表示事务只能读取到已经提交的数据。
超时时间
例:timeout = 30
回滚规则
事务回滚规则定义了在何种情况下事务应该回滚(撤销)已经执行的操作。通常,当事务执行过程中出现异常或者特定条件不满足时,系统会根据事务回滚规则来决定是否回滚事务。常见的回滚规则包括:
-
默认回滚:当事务执行过程中出现未捕获的异常时,默认情况下事务会自动回滚。这是最常见的回滚规则,可以保证在异常情况下数据的一致性和完整性。
-
自定义回滚条件:有时候需要根据特定的条件来决定是否回滚事务。这种情况下,可以在代码中编写逻辑来判断是否满足回滚条件,然后通过编程的方式调用事务管理器来手动回滚事务。
在Spring框架中,可以通过 @Transactional
注解的 rollbackFor
和 noRollbackFor
属性来定义回滚规则。例如:
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) {
// 更新用户信息的代码
}
在这个示例中,rollbackFor
属性指定了当出现 Exception
类型的异常时需要回滚事务。如果不指定 rollbackFor
属性,则默认情况下只有运行时异常会触发事务回滚。
除了使用 rollbackFor
属性外,还可以使用 noRollbackFor
属性来指定哪些异常不应该触发事务回滚。例如:
@Transactional(noRollbackFor = {CustomException.class})
public void updateUser(User user) throws CustomException {
// 更新用户信息的代码
}
在这个示例中,noRollbackFor
属性指定了当出现 CustomException
类型的异常时不需要回滚事务。
// 定义自定义异常类
class CustomException extends Exception {
// 定义一个错误码属性
private int errorCode;
// 构造方法,接收错误消息和错误码
public CustomException(String message, int errorCode) {
super(message); // 调用父类的构造方法
this.errorCode = errorCode;
}
// 获取错误码的方法
public int getErrorCode() {
return errorCode;
}
}
// 测试类
public class Main {
// 模拟一个方法,可能会抛出自定义异常
public static void divide(int dividend, int divisor) throws CustomException {
if (divisor == 0) {
// 如果除数为0,抛出自定义异常
throw new CustomException("除数不能为0", 1001);
} else {
// 计算除法
int result = dividend / divisor;
System.out.println("结果:" + result);
}
}
// 主方法
public static void main(String[] args) {
try {
// 调用 divide 方法,可能抛出自定义异常
divide(10, 0);
} catch (CustomException e) {
// 捕获自定义异常
System.out.println("发生自定义异常,错误消息:" + e.getMessage());
System.out.println("错误码:" + e.getErrorCode());
}
}
}
在这个示例中,定义了一个 CustomException
自定义异常类,模拟了一个 divide
方法可能抛出异常的情况。在 main
方法中调用 divide
方法时,通过 try-catch
块捕获可能抛出的自定义异常,并进行相应的处理。