• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

낙관적 락 엔티티 관련

23.07.15 01:42 작성 23.07.15 01:50 수정 조회수 247

1

안녕하세요.낙관적 락을 공부하며 생긴 질문을 드립니다..

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long productId;
    private Long quantity;

    @Version
    private Long version;

    @Builder
    public Stock(Long productId, Long quantity) {
        this.id = null;
        this.productId = productId;
        this.quantity = quantity;
        this.version = 0L;
    }

    public void decrease(Long quantity) {

        if(this.quantity - quantity < 0)
            throw new IllegalArgumentException("재고가 부족합니다.");

        this.quantity -= quantity;
    }
}

위와 같이 Entity 를 작성하였습니다. 실행환경은 MySQL 입니다.

 

동작 과정은 다음과 같습니다.

  1. 엔티티를 생성하고..

  2. JpaRepository 인터페이스를 상속받은 Repository 의 save 메서드의 파라미터로 엔티티를 전달하였습니다..

저는 여기서 파라미터로 전달한 엔티티 인스턴스가 영속 상태로 관리될 것이라 생각했습니다.

(일반적으론 그렇더라구요..)

그런데 version 프로퍼티 때문인지..

영속성 컨텍스트에 관리되는 엔티티는

파라미터로 전달한 엔티티 인스턴스가 아니라.. JPA 가 만들어낸 새로운 인스턴스였습니다..

(파라미터로 전달한 엔티티 인스턴스에는 id 가 채워지지 않았습니다.)

 

해당 상황에 대해.. 원리와 이유가 궁금합니다..

 

늘 감사합니다.

 

 

 

답변 1

답변을 작성해보세요.

0

안녕하세요. starryeye님

도움을 드리고 싶지만 질문 내용만으로는 답변을 드리기 어렵습니다.

실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.

구글 드라이브 업로드 방법은 다음을 참고해주세요.

https://bit.ly/3fX6ygx

 

주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요

 

추가로 다음 내용도 코멘트 부탁드립니다.

1. 문제 영역을 실행할 수 있는 방법

2. 문제가 어떻게 나타나는지에 대한 상세한 설명

감사합니다.

starryeye님의 프로필

starryeye

질문자

2023.07.18

안녕하세요.

아래 프로젝트 전체 압축하여 공유드립니다.

https://drive.google.com/file/d/1jiRZ048K8wgRZPts2vmxJ4s3sUTt036E/view?usp=sharing

궁금한 점에 대해서 프로젝트를 만들어 공유 드립니다.
제가 가진 의문점은 RepositoryTest.java 에 존재합니다.

stockSave() 는 성공하는데.. stockWithVersionSave() 는 성공하지 못합니다..

감사합니다.

안녕하세요. starryeye님

스프링 데이터 JPA가 entity에 버전 정보가 있는 경우에는 em.persist가 아니라 em.merge를 호출합니다. merge를 호출하면 전달하는 엔티티는 영속화 되지 않습니다. 대신에 새로 영속화된 엔티티를 반환합니다.

그런데 스프링 데이터 JPA가 어떤 이유로 이렇게 동작하는지는 저도 정확히 잘 모르겠습니다. 혹시 아시는 분이 있다면 답변 부탁드립니다.

감사합니다.

starryeye님의 프로필

starryeye

질문자

2023.07.21

안녕하세요.. 알려주신 정보를 바탕으로 조사를 해봤습니다.
image위 캡쳐는 Spring Data Jpa 공식 문서의 일부 입니다.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
5.1.2 Persisting Entities 의 내용 일부입니다.

공식 문서 1번 내용을 확인하기 위해 코드를 직접 까보니..
image위와 같이 isNew 를 호출하면,

Id 프로퍼티가 null 인지 확인하는 super.isNew(entity) 를 호출하기 전에

버전 프로퍼티를 확인하는 로직이 존재합니다.

  1. 버전 프로퍼티가 애초에 없는 엔티티일 경우엔 바로 super.isNew(entity) 호출

  2. 버전 프로퍼티가 있으면 타입을 확인하여 primitive 타입이면 super.isNew(entity) 호출

  3. non-primitive 타입이면 if 문을 빠져나와서 non-primitive 타입의 값이 null 이면 isNew 리턴 값이 true 가 됩니다. (persist 호출)

 

정리해보자면..

primitive 타입일 경우..

@Version
private long version;
버전 값이 무엇이든 간에 super.isNew(entity) 를 타게 되므로
id 프로퍼티에 의존적으로 됩니다.

 

non-primitive 타입일 경우..

@Version
private Long version;
버전 값이 null 이면 id 프로퍼티가 무엇이든 간에 isNew 리턴이 true 입니다. (persist)
버전 값이 null 이 아니면 id 프로퍼티가 무엇이든 간에 isNew 리턴이 false 입니다. (merge)

 

위와 같이 분석 되었습니다...

그래서 제가 공유드린 프로젝트는

@Version private Long version; 으로 사용하며, 엔티티 생성시 version = 0L; 을 하여

merge 가 호출 된 것입니다.

그래서 version = null; 으로 하면 제가 의도한데로 persist 가 호출되는 것을 확인하였습니다.

 

추가 질문입니다.

@Version 의 타입은 무엇으로 사용하면 될까요..?
제 생각으론.. non-primitive 를 사용하면 @Id 프로퍼티의 값이 무엇이든 간에 @Version 프로퍼티의 값에 의해 persist 를 호출할지 merge 를 호출할지 결정하므로 영속성 엔티티는 @Id 에 의해 관리된다의 대전제를 없애버린 느낌이라.. non-primitive 로 사용하기가 조금 꺼려집니다. 아니면 새로운 지식으로 받아드리는게 좋을까요..? 아니면.. primitive 로 사용 하는게 좋을까요..?

안녕하세요. starryeye님

잘 분석해주셔서 감사합니다.

version = 0L을 사용하셨군요!

버전 필드는 JPA에 의해서 관리되도록 비워두는 것이 맞습니다.

저는 @Id 필드의 경우도 그렇지만 값이 없다는 것과 0은 다른 것이라 생각해서 이런 경우 non-primitive 타입을 사용하는 것이 맞다 생각합니다.

도움이 되셨길 바래요. 감사합니다.