본문 바로가기
책/도메인 주도 개발 시작하기

Chapter 8. 애그리거트와 트랜잭션

by 오오오니 2025. 3. 17.

Chapter 8. 애그리거트와 트랜잭션

2024년 2월 27일 오전 11:03
비어 있음

💬 애그리거트의 트랜잭션

주문 사이트 정책
배송 전 상태에서 유저는 배송주소를 변경 할 수 있음
배송 중 상태에서 유저는 배송주소를 변경 할 수 없음
운영자와 고객이 동시에 수정할 때, 같은 주문 애그리거트를 나타내는 다른 객체를 구하게 된다.
각각 사용하고 있는 스레드에서 각자 수정을 하고 트랜잭션을 커밋할 때 DB에 반영하게 되는데 이 시점에서 배송 중 상태로 바뀌고 배송지 정보도 바뀐다는 문제가 생긴다.
2가지 해결책
운영자가 배송지 정보를 조회하고 상태를 변경하는 동안, 애그리거트를 수정하지 못하게 막는다.
운영자가 배송지 정보를 조회한 이후에 고객이 정보를 변경하면, 운영자가 애그리거트를 다시 조회한 뒤 수정하도록 한다.
트랜잭션 처리 기법을 알아 볼 것임

💬 애그리거트 잠금기법

선점 잠금

먼저 애그리거트를 구한 스레드가 사용이 끝날 때까지 해당 애그리거트의 수정을 막는 방식
스레드2는 스레드1이 수정한 애그리거트의 내용을 보게된다.
고객은 “이미 배송이 시작되어 배송지를 변경할 수 없습니다.” 문구를 보게 됨
DBMS가 제공하는 for update와 같은 쿼리를 사용해서 특정 레코드에 한 커넥션만 접근할 수 있는 잠금장치를 제공.
복사
-- 예시: employees 테이블에서 employee_id가 123인 레코드에 대한 잠금 설정 START TRANSACTION; SELECT * FROM employees WHERE employee_id = 123 FOR UPDATE; -- 이후 해당 레코드에 대한 변경 수행 UPDATE employees SET salary = 50000 WHERE employee_id = 123; -- 변경 완료 후 커밋 COMMIT;
스프링 데이터 JPA
lock 애너테이션을 이용해서 잠금모드 지정
복사
import org.springframework.data.jpa.repository.Lock; import javax.persistence.LockModeType; public interface MemberRepository extends Repository<Member, MemberId> { @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select m from Member m where m.id = :id") Optional<Member> findByIdForUpdate(@Param("id") MemberId memberId); }

선점 잠금과 교착상태

교착상태가 발생하지 않도록 주의해야 한다.
⇒ 잠금을 구할 때 최대 대기 시간을 지정 해야 한다.
복사
//힌트를 사용해 최대 대기 시간을 걸음 Map<String, Object> hints = new HashMap<>(); hints.put("javax.persistence.lock.timeout", 2000); Order order = entityManager.find( Order.class, orderNo, LockModeType.PRESSIMISTIC_WRITE, hints);
💡
DBMS 별로 교착상태에 빠진 커넥션을 처리하는 방식이 다르다. 쿼리별로 지정할 수 있는 DBMS가 있고 커넥션 단위로만 대기 시간을 지정할 수 있는 DBMS도 있다. 선점 잠금을 사용하려면 사용하는 DBMS에 대해 JPA가 어떤 식으로 대기 시간을 처리하는지 반드시 확인해야 한다. Q. dbms별로 어떻게 다를까?
이것저것 보다가 찾게 된 자료들
my sql이 격리성은 높고 동시성은 낮음
격리 수준 더 자세한 설명

비선점 잠금

선점 잠금으로 해결 할 수 없는 상황
비선점 잠금 : 동시에 접근하는 것을 막는 대신 변경한 데이터를 실제 DBMS에 반영하는 시점에 변경 가능 여부를 확인하는 방식
어떻게?
애그리거트에 버전 프로퍼티가 존재하고
수정할 애그리거트의 버전이
2번 애그리거트와 매핑되는 테이블의 버전과 동일할 때만 수정
복사
@Entity @Table(name = "purchage_order") @Access(AccessType.FIELD) public class Order { @EmbeddedId private OrderNo number; // 매핑되는 테이블에 버전을 저장히는 칼럼 @Version private long version; ... }
복사
// 버전이 일치하는 경우에만 수정 UPDATE purchage_order SET ..., version = version + 1 WHERE number = ? and version = 10
버전 정보를 사용자 화면에 전달해서 서버에 요청할때 버전값도 같이 전달해야함
강제 버전 증가 : 엔티티가 변경되었을 때 루티 인티티의 버전값도 증가시키기 위해 강제 버전 증가 잠금 모드를 사용할 수 있다.

오프라인 선점 잠금

여러 트랜잭션에 걸쳐 동시 변경을 막는다. 처음 트랜잭션에서 잠금을 선점하고 마지막 트랜잭션에서 잠금을 해제한다.
데이터 충돌을 엄격하게 막기 위해 누군가 수정화면을 보고 있다면 다른 사람은 수정화면을 실행하지 못하게 할 수 있음
한 트랜잭션 범위에서만 적용되는 선점 잠금 방식이나 나중에 버전 충돌을 확인하는 비선점 잠금 방식으로는 이를 구현할 수 없기 때문에 오프라인 선점 잠금 방식을 사용함
문제 : 사용자 A가 잠금을 해제하지 않으면 다른 사용자는 영원히 잠금을 수할 수 없다.
해결 : 잠금 유효시간을 가진다.
일정 주기로 유효 시간을 증가시켜야 한다.

💬 관련 CS 지식 & 면접 질문