한 주문 애그리거트에 대해 운영자는 배송 준비 상태로 변경할 때 사용자는 배송지 주소를 변경한다면 어떻게 될까
이런 문제를 방지하기 위해서는 아래와 같은 방법을 사용해야 함
위와 같이 일관성이 깨지지도 않도록 하는 방법은 트랜잭션 처리와 관련이 있음
애그리거트에 대해 사용할 수 있는 대표적인 트랜잭션 처리 방식에는 의 두 가지 방식이 존재함
선점 잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식
한 스레드가 애그리거트를 구하고 수정하는 동안 다른 스레드가 수정할 수 없도록 하여 일관성을 지킬 수 있음
선점 잠금은 보통 DBMS 가 제공하는 행 단위 잠금을 사용해서 구현
LockModeType.PESSIMISTIC_WRITE
사용선점 잠금 기능을 사용할 때는 잠금 순서에 주의하여 교착 상태(dealock)가 발생하지 않도록 해야 함
예를 들어, 위 같은 순서로 두 스레드가 잠금 시도를 한다면 스레드2가 B 애그리거트에 대한 잠금을 이미 선점하고 있기 때문에 스레드1은 영원히 B 애그리거트에 선점 잠금을 구할 수 없음
이러한 문제를 방지하기 위해, 선점 잠금을 시도하는 최대 대기 시간을 지정한다.
//JPA 에서는 아래와 같이 최대 대기 시간을 걸 수 있음
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000);
Order order = entityManager.find( Order.class, orderNo,
LockModeType.PRESSIMISTIC_WRITE, hints);
선점 잠금을 사용한다고 해서 모든 트랜잭션 충돌 문제가 해결되는 것은 아님
위와 같이 1번에서 조회한 주소를 4번 배송 상태 변경에 사용하게 된다면 변경 이전 주소로 배송이 될 수 있음
이러한 문제는 비선점 잠금을 활용하여 해결 할 수 있음
비선점 잠금은 애그리거트에 버전을 추가하여 수정할 애그리거트와 매핑되는 테이블의 버전 값이 현재 애그리거트의 버전과 동일한 경우에만 데이터를 수정함.
//JPA에서의 비선점 잠금
@Entity
@Table(name = "purchage_order")
@Access(AccessType.FIELD)
public class Order {
@EmbeddedId
private OrderNo number;
@Version // 매핑되는 테이블에 버전을 저장
private long version;
...
}
JPA는 엔티티가 변경되어 UPDATE 쿼리를 실행할 때 @Version에 명시한 필드를 이용해서 비선점 잠금 쿼리를 실행 함
// 버전이 일치하는 경우에만 수정
UPDATE purchage_order SET ..., version = version + 1
WHERE number = ? and version = 10
@Controller
public class OrderController {
...
@RequestMapping(value = "/changeShipping", method = RequestMethod.POST)
public String changeShipping(ChangeShippingRequest changeReq) {
try {
changeShippingService.changeShipping(changeReq);
return "changeShippingSuccess";
} catch(optimisticLockingFailureException ex) {
// 누군가 같은 주문 애그리거트를 수정했다면 트랜잭션 충돌이 일어났다는 메시지를 보여줌
return "changeShippingExConflic";
}
}
강제 버전 증가
루트 엔티티와 연관된 엔티티의 값이 변경될 경우 루트 엔티티 자체의 값은 바뀌는 것이 없으므로 버전 값을 갱신하지 않음
때문에 버전 값을 강제로 증가시켜야 비선점 잠금이 올바르게 동작함
데이터 충돌을 엄격하게 막기 위해 누군가 수정화면을 보고 있다면 다른 사람은 수정화면을 실행하지 못하게 할 수 있음
한 트랜잭션 범위에서만 적용되는 선점 잠금 방식이나 나중에 버전 충돌을 확인하는 비선점 잠금 방식으로는 이를 구현할 수 없기 때문에 오프라인 선점 잠금 방식을 사용함
오프라인 선점 잠금은 여러 트랜잭션에 걸쳐 동시 변경을 막는 방식이며 잠금을 해제하기 전까지 다른 사용자는 잠금을 구할 수 없음
위와 같이 오프라인 선점 잠금 방식을 사용하지만 사용자 A 가 3의 수정 요청을 수행하지 않고 프로그램을 종료하면 다른 사용자는 영원히 잠금을 구할 수 없는 상황이 발생함