728x90
낙관적 락
- @Version을 사용
- 트랜잭션을 커밋하는 시점에 충돌을 알 수 있음
비관적 락
- 선점 잠금
- 데이터베이스 트랜잭션 락 메커니즘에 의존하는 방식
- SQL 쿼리에 select for update 구문을 사용하면서 시작
- 버전 정보는 사용하지 않음
JPA 락 옵션
낙관적 락 | OPTIMISTIC | 낙관적 락을 사용한다. |
낙관적 락 | OPTIMISTIC_FORCE_INCREMENT | 낙관적 락 + 버전정보를 강제로 증가한다. |
비관적 락 | PESSIMISTIC_READ | 비관적 락, 읽기 락을 사용한다. (공유 잠금) |
비관적 락 | PESSIMISTIC_WRITE | 비관적 락, 쓰기 락을 사용한다.(배타적 잠금) |
비관적 락 | PESSIMISTIC_FORCE_INCREMENT | 비관적 락 + 버전 정보를 강제로 증가한다. |
기타 | NONE | 락을 걸지 않는다. |
기타 | READ | JPA 1.0 호환 기능으로 OPTIMISTIC과 동일 |
기타 | WRITE | JPA 1.0 호환 기능으로 OPTIMISTIC_FORCE_INCREMENT와 동일 |
Entity
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long idx;
private int price;
public int decreasePrice(int price) {
if (this.price - price < 0) {
throw new IllegalArgumentException("가격 부족");
}
return this.price -= price;
}
}
Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Order findById(Long idx);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select o from Order o where o.idx = :idx")
Order findByIdxForUpdate(@Param("idx") Long idx);
}
service
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderRepository repository;
@Transactional
public int getCurrentPrice(Long idx) {
Order order = homeRepository.findById(idx); // 조회
return order.getPrice();
}
@Transactional
public int decreasePrice(Long idx, int price) {
Order order = homeRepository.findByIdxForUpdate(name); // 비관적락 조회
order.decreasePrice(price); // 가격 감소
return order.getPrice();
}
}
controller
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/order")
public class HomeController {
private final HomeService homeService;
@GetMapping("/decrease")
public String decreasePrice(@RequestParam(value = "idx") Long idx, @RequestParam(value = "price") int price) {
String result;
try {
homeService.decreasePrice(idx, price);
result = "현재 가격 : " + homeService.currentPrice(idx);
} catch (Exception e) {
result = e.getMessage();
}
log.info(result);
return result;
}
}
타임아웃
비관적 락을 사용하면 락을 획득할 때까지 트랜잭션이 대기하게 되는데 무한정 기다릴 수 없으므로 타임아웃 시간을 줄 수 있음
public interface OrderRepository extends JpaRepository<Order, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select o from Order o where o.idx = :idx")
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "10000")})
Order findByIdxForUpdate(@Param("idx") Long idx);
}
728x90