스프링 데이터 JPA가 제공하는 공통 인터페이스 구현체는 SimpleJapRepository이다.
SimpleJapRepository 내부를 보게 되면 findAll findBy, save, exists, delete, count 등의 공통 인터페이스 메서드가 구현되어있는 것을 알 수 있다.
간단히 특징을 살펴보자.


@Repository
@Transactional(readOnly=true)
readOnly=true로 트랜잭션이 설정되어 있어, 데이터를 단순히 조회하는 트랜잭션에서@Transactional
데이터의 모든 변경(등록,수정,삭제)이 트랜잭션내에서 수행될 수 있도록 @Transactional이 설정되어있다.
서비스 계층에서 트랜잭션을 시작하지 않았을 경우에 Repository에서 트랜잭션이 시작되게 된다.
만약 서비스 계층에서 트랜잭션을 시작하면 리포지토리에서 해당 트랜잭션을 전파 받아서 사용하게 된다.
먼저 구현체 메소드 중에서 save 를 살펴보자.

앞서 특징에서 말했던거와 같이 데이터 변경이 트랜잭션 내에서 일어날수 있도록 @Transactional이 설정된 것을 볼 수 있다.
내부 로직을 보게 되면 새로운 엔티티일 경우에는 persist가 동작하고,
기존에 있는 엔티티인 경우에는 merge가 동작하는 것을 볼 수 있다.
🔎
merge(병합)이란?준영속 상태의 엔티티를 영속 상태로 변경할 떄 사용하는 기능이다.
(준영속 상태란 영속성 컨텍스트가 관리하지 않은 엔티티를 의미한다.)
merge의 동작 방식을 간단히 살펴보겠다.
준영속 상태인 엔티티의 식별자 값으로 1차 캐쉬에서 엔티티를 조회한다.
-> 만약 엔티티가 1차 캐쉬에 없다면 DB에서 조회하게 된다. (select 쿼리가 발생하기 된다!!)조회했던 영속 상태 엔티티값을 준영속 상태였던 엔티티의 값으로 모두 바꾼다.
주위❗
엔티티의 값을 변경시킬 때병합(merge)인 경우에는 모든 필드가 교체된다.
즉, 병합시 특정 필드값이 없을 경우에 해당 필드값이 null로 채워지게 된다.모든 값을 바꾼 영속 상태의 객체를 반환한다.
-> 트랜잭션 커밋 시점에 변경 감지 기능이 동작하여 update 쿼리문 실행.
그럼 새로운 엔티티를 어떻게 구별하게 될까?
새로운 엔티티를 판단하는 JPA 전략은 아래와 같다.
@Entity
@Getter
public class NewEntityTest {
@Id
@GeneratedValue
private Long id;
public NewEntityTest(){
}
}
@SpringBootTest
class NewEntityTestRepositoryTest {
@Autowired NewEntityTestRepository newEntityTestRepository;
@Test
public void 새로운객체판별테스트() throws Exception {
//기본 "@GeneratedValue"일 경우
NewEntityTest entity = new NewEntityTest();
newEntityTestRepository.save(entity);
}
}
위와 같이 테스트를 진행하게 되면, @GenerateValue 면 save() 호출 시점에 식별자가 없으므로 새로운엔티티로 인식해서 persist로 동작하게 되고 Insert 쿼리만 발생하게 된다.
(save 호출 후에는 식별자 값이 들어가있게 된다.)
위의 같은 경우에는 PK 값을 @GenerateValue 전략으로 사용했을 경우이다.
만약에 UUID와 같은 전략이나 PK값을 따로 생성하여 사용하는 경우,
즉 @GenerateValue 전략을 사용하지 않은 경우에는 엔티티를 생성할 때 PK값을 직접 할당해야할 것이다.
이런 경우에는 새로운 엔티티를 판별하는 전략에 해당되지 않는다.(객체의 식별자가 null이 아니기 때문에)
그럼 merge가 동작하게 되고 DB를 호출해서 값을 찾게 되고, 값이 없으면 새로운 엔티티로 확인하는등 불필요한 select 문이 발생하게 될 것이다.
이러한 경우에는 Persistable를 사용하여 새로운 엔티티를 판별하는 전략을 새로 구현하면 된다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class NewEntityTest extends BaseTimeEntity implements Persistable<String> {
@Id
//@GeneratedValue -> 사용안하는 전략일 경우
private String id;
public NewEntityTest(String id){
this.id = id;
}
@Override //새로운 엔티티 판변하는 메서드
public boolean isNew() {
return getCreatedDate() == null;
}
}
@Getter
@MappedSuperclass //엔티티가 아닌 클래스 상속 -> 매핑 정보 속성만 제공
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
@SpringBootTest
class NewEntityTestRepositoryTest {
@Autowired NewEntityTestRepository newEntityTestRepository;
@Test
public void 새로운객체판별테스트() throws Exception {
//"@GeneratedValue"가 아닌 String 와 같은 자료형 일 경우
NewEntityTest entity = new NewEntityTest("A");
newEntityTestRepository.save(entity);
}
}
Persistable 를 사용해서 새로운 엔티티 확인 여부를 체크하게 된다.
새로운 엔티티를 체크하기 위한 좋은 방법으로는 등록시간(@CreateDate)을 조합해서 사용하면 새로운 엔티티 여부를 편리하게 판단할 수 있다.
(@CreateDate에 값이 없으면 새로운 엔티티로 판단!)