Spring Boot中悲观锁

悲观锁是数据库系统中使用的一种并发控制机制。它可以防止多个事务同时更新相同的数据。

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