업무 중에 동시에 입력되는 데이터를 시간으로 역추적하는 기능을 만들려다가 기존 레거시 코드에서 문제를 확인했다. 문제를 정리하자면
- 동시에 입력되는 데이터가 List형식으로 동시에 서버로 들어오는 것이 아닌 프론트에서 하나씩 들어온다.
- 두개의 조인된 테이블에
@PrePersist
어노테이션이 걸려있는데 데이터 생성 시간이 다르다.
start.spring.io에서 위와 같은 설정으로 예제 프로젝트 생성
유저가 상품을 구매할 때 주문 결제
와 구매 내역
에 각각 저장하는데 후에 두 테이블의 정보를 연결해서 조회 가능할 수 있도록 최대한 생성 시간
에 초점을 맞추어 조회하려고 한다.
생성 시간은 부모 엔티티의 기본 정보의 createDateTime
에 @PrePersist
어노테이션을 걸어 자동으로 생성되게 되어있다.
문제는 현재 상태로 저장이 되면 각각의 반복문에서 저장되는 주문 결제
와 구매 내역
이 다른 생성 시간
으로 저장되는 것이다.
어떻게 해결할지는 몇가지 떠올랐다.
결제 시간 칼럼을 추가한다.
-> 좋은 방법이라고 생각되지만 가장 마지막으로 고려해야된다고 생각되는게 DB에 칼럼을 추가하는 일이어서 최대한 있는 칼럼으로 해결할 수 있는지 먼저 고려하려한다.
영속성 컨텍스트의 flush()
와 @PrePersist
어노테이션을 잘 활용해서 잘 활용해서 한번에 일괄적으로 들어가는 방법이 있지 않을까? -> 할 수 있으면 최선
임의의 Date 변수를 만들어서 일괄적으로 모두 저장후 한번에 saveAll()
로 변경한다. -> 좋은 방법. saveAll()
사용으로 성능 향상을 할 수 있다. 하지만 기존에 부모에서 사용되는 어노테이션인 @PrePersist
의 문제를 해결해야함
1번은 최후의 보루이므로 2번을 먼저 시도해보자
일단 이전에 JPA 강의에서 배웠지만 다시 한번 영속성 컨텍스트에 대해 복습해보자
엔티티를 영구 저장하는 환경
EntityManager.persist(entity);
: persist()로 db에 객체를 저장하는 것이라고 배웠지만 실제로는 저장하는 것이 아니라, 영속성 컨텍스트를 통해서 엔티티를 영속화 한다는 뜻- 영속성 컨텍스트는 논리적인 개념
key
: @Id
로 선언한 필드 값, value
: 해당 엔티티 자체로 캐시에 저장find()
가 일어나는 순간, 엔티티 매니저 내부의 1차 캐시를 먼저 찾는다. -> 존재시 DB 접근 없이 반환commit()
하는 시점에 DB에 동시에 쿼리를 보낸다. (옵션에 따라 다를 수 있음)flush()
- 1차 캐시를 지우지 않음flush()
+ `commit()``Dirty Checking
flush()
가 일어날 때 엔티티와 스냅샷을 비교, 변경사항 있을시 UPDATE SQL 만들어서 DB 저장영속성 컨텍스트 변경 내용을 DB에 반영
-> 영속성 컨텍스트의 변경 사항들과 DB를 싱크하는 역할
내가 궁금한 구체적인 내용은 @PrePersist 어노테이션이 걸리면 1차 캐시에 들어갈 때 해당 어노테이션이 동작할까?
와 1차 캐시가 아닌 플러시에서 동작하게 변경할 수는 있을까?
이다.
첫번째 물음을 해결하기 위해 예제 테스트 코드를 만들었다.
결과는
두 값이 같고, Null
이 아닌 것을 보아 @PrePersist
는 persist()
동작에서 호출되는 것으로 보여진다. 혹시 다른 예외가 있을까봐 찾아보았지만 @PrePersist
는 persist()
동작에서 엔티티에 동작하고, flush()
에서 DB에 저장된다.
그렇다면 두번째 물음을 실행할 방법을 찾아보았다.
부모 @PrePersist
에 개입 또는 무시, 그러면 saveAll()
을 사용하면 동시에 동작하는거 아닌가?
방법을 찾지 못했다. (혹시나 아시는 분 있으면 댓글로 알려주시길 바랍니다.)
saveAll()
은 코드를 보면 개별로 save()
를 호출하지만 그 일련의 과정이 트랜잭션으로 하나로 묶여있는 장점이 있는데 위 상황에서는 개별로 호출할 때 @PrePersist
가 동작하므로 해결 방법이 아니다.
결론적으로 상속을 포기하는 방식으로 결론지었다.
상속을 포기해도 다른 코드와 DB가 영향을 받지 않도록 구현한다면 가장 좋은 방법이라고 생각들었다.
코드를 비교하자면 아래와 같다.
Entity
BEFORE
AFTER
결과적으로 saveAll()
사용으로 성능의 약 1/3 정도가 개선되었고, 모두 한개의 변수 값을 builder()
에서 지정할 수 있어 정확성이 높아진다. 시간과 정확도가 모두 올라갈 수 있어 이 방식을 적용했다.