Search

Persistable 인터페이스 구현하기

태그
Java
DB
Spring
분류
개발/구현
생성 일시
2023/08/31 09:48
프로젝트
42PAW

Presistable 인터페이스의 사용 목적

JPA Repository는 SimpleJpaRepository를 구현체로 사용하는데, SimpleJpaRepository에는 save 메서드가 다음과 같이 구현되어 있다.
save 메서드를 호출하면 isNew 메서드 반환값에 따라 persist를 하거나 merge를 하게 된다. 다시 말해 save 메서드의 매개변수로 받은 엔티티가 새로 생성된 엔티티라면 persist를 하고, 그게 아니라면 merge를 한다는 의미이다.
다만 merge의 경우 1차 캐시에서 엔티티를 조회하고, 1차 캐시에 없다면 데이터베이스에서 해당 엔티티를 조회해본다. 그렇기에 merge 동작으로 넘어가면 select 쿼리가 발생 후, 있다면 update 쿼리를 날리고 없다면 insert 쿼리를 날리게 된다. 즉 select + update 혹은 select + insert로 쿼리가 2번 발생한다. 이로 인해 성능이 저하되는 것 뿐만 아니라, 쿼리와 쿼리 사이에 데이터가 변경되는 등 오류가 발생할 가능성이 있다는 문제가 있다.
이러한 이유로 가급적 merge가 발생하지 않도록 해야하는데, isNew의 동작을 알 필요가 있다.
먼저 persistable 인터페이스를 구현하지 않는 경우, JPA Repository에서 AbstractEntityInformation의 isNew 메서드를 호출하게 된다.
AbstractEntityInformation.java의 isNew 메서드
AbstractEntityInformation의 isNew 메서드의 판단 기준은 다음과 같다.
참조 타입 : null 일 때만
원시 타입 : int나 long 타입처럼 Number의 인스턴스인 경우에 0일 때만 새로운 엔티티라 판단한다.
Persistable 인터페이스는 아래와 같이 getId와 isNew의 추상 메서드만을 가지는 인터페이스이다.
Persistable 인터페이스를 구현하게되면, JPA에서 JpaPersistableEntityInformation의 isNew 메서드를 호출하게 된다.
JpaPersistableEntityInformation.java의 isNew 메서드
JpaPersistableEntityInformation의 isNew 메서드는 위처럼 Persistable을 구현한 클래스에서 재정의한 isNew 메서드를 호출하기 때문에, 우리가 직접 isNew를 만들어서 사용할 수 있는 것이다.
@GeneratedValue를 붙여서 사용하게 되면 merge를 호출할 일이 없지만, @GeneratedValue를 사용하지 않고 @Id만 사용하면 merge를 호출하게 될 수 있다. @GeneratedValue를 사용하지 않으면, 직접 기본키를 지정해주어야 하는데, 상황에 따라 어쩔 수 없이 @GeneratedValue를 사용하지 못할 수 있다.
String 타입을 id로 사용하여 UUID를 저장하는 상황과 같이 @GeneratedValue를 사용하지 못하는 상황에서는 Persistable을 구현하고 우리가 직접 isNew를 만들어 사용해야, 새로운 엔티티가 생성되는 경우에 확실하게 merge가 호출되지 않는 것을 보장해 성능 저하와 오류의 발생 가능성을 없앨 수 있다.

Persistable 인터페이스 구현하기

진행하는 프로젝트에서는 String을 id로 사용하는 경우는 없었지만, 복합 키를 사용하여 PK를 대신하는 테이블들이 몇 개 있어 Persistable 인터페이스를 구현하기로 결정하였다.
Persistable 인터페이스를 구현하는 PersistDomain이라는 추상 클래스를 만들어, @MappedSuperclass로 공통 매핑 정보들을 묶고 isNew에 대한 처리와 함께 hashCode, equals에 대한 처리를 하도록 구현하였다.

참고