JpaRepository의 구현체 중 SimpleJpaRepository 가 스프링 데이터 JPA 구현체이다.
조회, 삭제 등에서 엔티티 매니저를 사용해 JPA 에서 사용하듯 기능을 구현하고 있다.
구현체 클래스를 보면, @Repository, @Transactional(readOnly = true) 어노테이션이 설정되어 있는 것을 확인할 수 있다.
@Repository 가 기본으로 설정되어 컴포넌트 스캔의 대상이 되며, 데이터베이스 간 발생하는 jdbc exception 이나 jpa exception 등을 컨트롤러나 서비스 단에서 스프링으로 제어할 수 있는 예외로 처리할 수 있도록 한다.
@Transactional 이 기본으로 설정되어 있어 만약 서비스단에서 트랜잭션 없이 리포지토리 메서드를 호출하더라도, JPA 를 사용하여 동작할 수 있도록 한다.
save 메서드
save 메서드를 살펴보면 새로운 엔티티의 경우 em.persist를, 이미 저장되었던 준영속 엔티티의 경우 em.merge 를 통해 저장하는 것을 확인할 수 있다.
em.merge의 경우 DB에서 해당 PK 값으로 조회를 하여 해당 값을 merge 메서드의 인자로 받은 엔티티로 교체하고 트랜잭션 커밋 시 update 쿼리가 나간다. (조회에 실패하면 새로운 엔티티로 인식하여 persist 한다)
merge 는 조회 쿼리가 추가로 발생하여 persist 에 비해 성능적으로 좋지 않다.
PK의 값 타입이 객체이면 null 이면 새로운 엔티티로 판단
PK의 값 타입이 자바 기본 타입이면 0 이면 새로운 엔티티로 판단
Persistable 인터페이스의 isNew 메서드로 판단
만약, @GeneratedValue 를 사용하지 않고 PK를 구성하고 싶다면?
PK에 애플리케이션 레벨에서 특정 값을 넣고 repository 에 save 메서드를 호출할 것이다.
PK에 값이 존재하므로 1, 2번 에 해당하지 않고, 3번도 따로 구현하지 않았으므로 merge가 호출되게 된다.
따라서, 별도의 PK를 사용하고 싶다면 해당 엔티티에서 Persistable 인터페이스의 isNew 메서드를 구현해주어야 한다.
tip) isNew 메서드는 어떻게 구현하면 좋을까?
해당 객체가 애플리케이션레벨에서 데이터베이스에 존재하는 값인지 개발자가 직접 판단하는 것은 쉽지 않다.
따라서, 데이터 JPA에서 제공하는 @CreateDate와 같은 정보를 사용하여 판단하면 쉽고 정확하게 구현할 수 있다.