悲观锁是数据库系统中使用的一种并发控制机制。它可以防止多个事务同时更新相同的数据。
Spring悲观锁应用:
@SpringBootApplication public class SpringPessimisticLockApplication {
public static void main(String[] args) { SpringApplication.run(SpringPessimisticLockApplication.class, args); }
@Bean public ApplicationRunner runner(BalanceRepository balanceRepository) { return args -> {
Balance balance = Balance.builder(). owner("mert") .balance(0) .build(); balanceRepository.save(balance); };
}
}
|
应用程序启动时,运行程序 Bean 会将 BalanceRepository 作为参数,并将Balance余额保存到数据库中。@Entity @Table @NoArgsConstructor @AllArgsConstructor @Builder @Data public class Balance {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private int balance; private String owner; @Version private int version;
}
@RestController @RequestMapping("/api/v1/balances") public class BalanceController {
private final BalanceService balanceService;
public BalanceController(BalanceService balanceService) { this.balanceService = balanceService; }
@GetMapping public String incrementBalance() { balanceService.incrementBalance(); return "balance incremented."; } }
@Service @Slf4j public class BalanceService {
private final BalanceRepository balanceRepository;
public BalanceService(BalanceRepository balanceRepository) { this.balanceRepository = balanceRepository; }
public void incrementBalance(){ Balance balance = balanceRepository.findBalanceByOwner("mert") .orElseThrow(() -> new EntityNotFoundException("Balance not found"));
log.info("balance incrementing..");
balance.setBalance(balance.getBalance() + 1); balanceRepository.save(balance); } }
@Repository public interface BalanceRepository extends JpaRepository<Balance, Integer> {
Optional<Balance> findBalanceByOwner(String owner);
}
|
源码
启动应用,发送并发 HTTP 请求
ab -n 100 -c 10 host.docker.internal:8080/api/v1/balances
|
Complete Requests: 100
Failed Requests: 73
余额应为 100,但数据不一致。
Exception: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction
org.hibernate.StaleObjectStateException:该异常是一种机制,用于防止一个事务覆盖另一个事务的更改,从而确保数据的一致性。
解决方案
要处理这种情况,通常需要实施某种形式的并发控制,如乐观锁或悲观锁。
本文将使用悲观锁定。
悲观锁定的缺点:
在BalanceRepository加入锁:
@Repository public interface BalanceRepository extends JpaRepository<Balance, Integer> {
@Lock(LockModeType.PESSIMISTIC_WRITE) Optional<Balance> findBalanceByOwner(String owner);
}
|
@Lock(LockModeType.PESSIMISTIC_WRITE):此注解用于为方法指定锁定策略。它会阻止其他事务同时写入相同的记录,直到锁被释放。
再次测试,发送100 请求
Complete Requests: 100
Failed Requests: 0
还有错误:
jakarta.persistence.TransactionRequiredException:当您试图执行需要活动事务的操作,但当前没有事务正在进行时,通常会出现该异常。
Exception: jakarta.persistence.TransactionRequiredException: Query requires transaction be in progress, but no transaction is known to be in progress
解决方案
在服务方法中添加 @Transactional 注解。
@Service @Slf4j public class BalanceService {
private final BalanceRepository balanceRepository;
public BalanceService(BalanceRepository balanceRepository) { this.balanceRepository = balanceRepository; }
@Transactional public void incrementBalance(){ Balance balance = balanceRepository.findBalanceByOwner("mert") .orElseThrow(() -> new EntityNotFoundException("Balance not found"));
log.info("balance incrementing..");
balance.setBalance(balance.getBalance() + 1); balanceRepository.save(balance); } }
|
再次测试,发送2000 请求
Complete Requests: 2000
Failed Requests: 0