事务的使用与事务失败的场景分析
事务(Transaction)是一个数据库管理系统中的逻辑工作单元,由一组操作组成,这些操作要么全部成功,要么全部失败。事务的主要目的是保证数据库的完整性和一致性,即使在系统故障或其他异常情况下也能如此。
事务的特性 (ACID)
事务有四个关键特性,统称为 ACID 特性:
- 原子性 (Atomicity) :事务是一个不可分割的工作单元,事务中的所有操作要么全部成功,要么全部失败。即使在出现系统故障的情况下,事务也会回滚到事务开始前的状态。
- 一致性 (Consistency) :事务执行前和执行后,数据库都必须处于一致状态。事务的执行不能违反数据库的约束条件和业务规则。
- 隔离性 (Isolation) :多个事务并发执行时,一个事务的执行不能被其他事务干扰,各个事务之间是相互隔离的。不同的隔离级别定义了事务之间的相互可见性。
- 持久性 (Durability) :事务一旦提交,其对数据库的改变是永久性的,即使系统崩溃也不会丢失。数据库系统会将事务的结果写入持久性存储。
事务的使用示例
在实际应用中,事务通常用来确保一组相关的数据库操作要么全部成功,要么全部回滚。例如,银行转账操作需要将款项从一个账户扣除并存入另一个账户,这两个操作必须在一个事务中执行,以确保数据一致性。
以下是一个简单的 Spring 框架中的事务管理示例:
1. 创建一个用户实体 (User)
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class User {
@Id
private Long id;
private String name;
private double balance;
// Getters and setters
}
2. 创建一个用户仓库 (UserRepository)
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
3. 创建一个用户服务 (UserService) 来管理事务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void transferBalance(Long fromUserId, Long toUserId, double amount) {
User fromUser = userRepository.findById(fromUserId).orElseThrow(() -> new RuntimeException("User not found"));
User toUser = userRepository.findById(toUserId).orElseThrow(() -> new RuntimeException("User not found"));
if (fromUser.getBalance() < amount) {
throw new RuntimeException("Insufficient balance");
}
fromUser.setBalance(fromUser.getBalance() - amount);
toUser.setBalance(toUser.getBalance() + amount);
userRepository.save(fromUser);
userRepository.save(toUser);
}
}
4. 创建一个控制器 (UserController) 来触发服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/transfer")
public String transferBalance(@RequestParam Long fromUserId, @RequestParam Long toUserId, @RequestParam double amount) {
try {
userService.transferBalance(fromUserId, toUserId, amount);
return "Transfer successful";
} catch (Exception e) {
return "Transfer failed: " + e.getMessage();
}
}
}
使用 @Transactional
注解来管理事务是 Spring 框架中非常常见的做法。事务没有起作用可能有多种原因。下面是一些常见的排查步骤:
事务执行失败的场景分析
1. 确保事务注解在正确的层次
- 通常,
@Transactional
注解应用于服务层(Service
层)的类或方法,而不是在 DAO 层。确保注解加在正确的类或方法上。
详细说明
- Service 层:负责业务逻辑的实现,通常会调用多个 DAO 方法。事务注解应该应用在服务层的方法上,以确保这些操作在一个事务中执行。
- DAO 层:负责具体的数据访问操作(例如查询、插入、更新、删除)。DAO 层的方法不应负责事务管理。
示例
假设我们有一个简单的应用,需要处理用户的账户余额更新操作。我们有一个 UserRepository
来访问数据库,以及一个 UserService
来处理业务逻辑。
DAO 层
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// CRUD methods provided by JpaRepository
}
Service 层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 正确的做法:在服务层的方法上使用 @Transactional 注解
@Transactional(rollbackFor = Exception.class)
public void updateUserBalance(Long userId, double amount) {
User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
user.setBalance(user.getBalance() + amount);
userRepository.save(user);
// 假设这里有更多的业务逻辑,例如记录操作日志
// 如果这些操作中的任何一个失败,整个事务应该回滚
}
}
常见的错误做法
在 DAO 层使用 @Transactional
注解是不推荐的,因为 DAO 层不应该管理事务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class UserRepositoryImpl {
@Autowired
private UserRepository userRepository;
// 错误的做法:在 DAO 层使用 @Transactional 注解
@Transactional(rollbackFor = Exception.class)
public void updateUserBalance(Long userId, double amount) {
User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
user.setBalance(user.getBalance() + amount);
userRepository.save(user);
}
}
2. 确保方法是通过 Spring 管理的 bean 调用
- 代理机制:Spring 使用代理来管理事务。如果方法直接从同一个类中的另一个方法调用的,事务管理
将不会生效
。这是因为代理对象不会拦截对自身方法的调用
。因此,确保事务方法是在通过 Spring 容器管理的 bean 间调用。(即:申明@Transactional事务的方法(doTransactionalMethod),应直接被Service调用,即service.doTransactionalMethod(),而不能间接被调用)
示例场景
我们将使用一个 UserService
,其中包含一个事务方法 updateUserBalance
,以及一个控制器 UserController
来触发这些服务。
UserService
负责更新用户的账户余额。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void updateUserBalance(Long userId, double amount) {
User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
user.setBalance(user.getBalance() + amount);
userRepository.save(user);
}
// 这个方法用于演示直接调用事务方法不会生效
public void updateUserBalanceDirect(Long userId, double amount) {
// 直接调用内部的事务方法
updateUserBalance(userId, amount);
}
}
UserController
一个控制器类,用于外部调用 UserService
的事务方法和非事务方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@GetMapping("/updateBalanceDirect")
public String updateBalanceDirect(@RequestParam Long userId, @RequestParam double amount) {
try {
// 直接调用不会触发事务
userService.updateUserBalanceDirect(userId, amount);
} catch (Exception e) {
return "Transaction failed: " + e.getMessage();
}
}
@GetMapping("/updateBalance")
public String updateBalance(@RequestParam Long userId, @RequestParam double amount) {
try {
// 通过代理调用会触发事务
userService.updateUserBalance(userId, amount);
} catch (Exception e) {
return "Transaction failed: " + e.getMessage();
}
}
}
测试
通过 HTTP 请求调用 UserController
的方法来验证事务管理的行为。
-
启动应用。
-
发送请求:
http://localhost:8080/updateBalanceDirect?userId=1&amount=1500
- 期望结果:此调用不会触发事务,因为它是直接调用的
updateUserBalance
方法
- 期望结果:此调用不会触发事务,因为它是直接调用的
-
发送请求:
http://localhost:8080/updateBalance?userId=1&amount=1500
- 期望结果:此调用会触发事务,因为它是通过代理调用的
updateUserBalance
方法
- 期望结果:此调用会触发事务,因为它是通过代理调用的
3. 检查异常类型
-
确保抛出的异常类型与
rollbackFor
参数匹配。如果没有指定rollbackFor
,默认只在RuntimeException
和Error
的情况下回滚。@Transactional(rollbackFor = Exception.class) public void myMethod() { try { // some database operations } catch (Exception e) { throw new MyCustomException(e); } }
4. 确保事务传播属性正确
- 事务的传播属性可能会影响事务行为。默认情况下,
@Transactional
的传播属性是REQUIRED
。检查是否需要其他传播属性。
5. 确保数据库支持事务
- 确保你使用的数据库支持事务,并且使用了支持事务的存储引擎(例如,在 MySQL 中使用 InnoDB 而不是 MyISAM)。
转载自:https://juejin.cn/post/7382980041398911011