스프링 데이터 JPA 분석
📌 스프링 데이터 JPA 구현체 분석
- 스프링 데이터 JPA가 제공하는 공통 인터페이스의 구현체
- org.springframework.data.jpa.repository.support.SimpleJpaRepository
SimpleJpaRepository
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
//...
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
//...
}
- @Repository 적용 : JPA 예외를 스프링이 추상화한 예외로 변환
- @Transactional 트랜잭션 적용
- JPA의 모든 변경은 트랜잭션 안에서 동작
- 스프링 데이터 JPA는 변경(등록, 수정, 삭제) 메서드를 트랜잭션 처리
- 서비스 계층에서 트랜잭션을 시작하지 않으면 리포지토리에서 트랜잭션 시작
- 서비스 계층에서 트랜잭션을 시작하면 리파지토리는 해당 트랜잭션을 전파받아서 사용
- 그래서 스프링 데이터 JPA를 사용할 때 트랜잭션이 없어도 데이터 등록, 변경이 가능했던 것이다.
(스프링 데이터 JPA에 의해 트랜잭션이 리포지토리 계층에서 시작된 것)
- @Transactional(readOnly = true)
- 데이터를 단순히 조회만 하고 변경하지 않는 트랜잭션에서 readOnly = true 옵션을 사용하면 플러시를 생략해서 약간의 성능 향상을 얻을 수 있다.
⭐ save() 메서드 ⭐
- 새로운 엔티티면 저장(persist)
- 새로운 엔티티가 아니면 병합(merge)
병합은 데이터베이스에서 데이터를 가져온 후 save 했던 데이터를 바꿔치기하고 트랜잭션 종료 시 변경된 데이터를 데이터베이스에 반영하는 메커니즘으로 동작한다. 따라서 데이터베이스를 조회하기 위한 SELECT SQL이 발생하고 SELECT 한 결과물이 없으면 새로운 객체라고 가정하고 데이터베이스에 새로운 데이터를 밀어 넣는다.
save의 update라고 생각할 수 있지만 가급적이면 데이터 변경의 목적으로 병합(merge)을 사용하면 안 된다.
병합(merge)은 수정의 목적으로 사용하는 것이 아니라 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능으로, 데이터 변경은 변경감지를 사용하여 처리하는 것이 좋다.
참고 포스팅 : 변경 감지와 병합(merge)
[Spring Boot + JPA] 웹 어플리케이션 개발 - 4. 웹 계층 개발
웹 계층 개발 웹 계층 개발 1. 홈화면과 레이아웃 2. 회원 등록 3. 회원 목록 조회 4. 상품 등록 5. 상품 목록 6. 상품 수정 7. 변경 감지와 병합(merge) 8. 상품 주문 9. 주문 목록 검색, 취소 홈 화면과
yoonseon.tistory.com
📌 새로운 엔티티를 구별하는 방법
새로운 엔티티를 판단하는 기본전략
- 식별자가 객체일 때(자바 래퍼 타입일 때) null로 판단한다.
- 식별자가 자바 기본 타입일 때 0으로 판단한다.
- Persistable 인터페이스를 구현해서 판단 로직 변경 가능
⭐ 엔티티 식별자 생성전략 직접 할당 시 주의 사항⭐
스프링 데이터 JPA 구현 save() 코드 :: SimpleJpaRepository.class
JPA 식별자 생성전략이 @GennerateValue면 save() 호출 시점에 식별자는 null이므로 entityInformation.isNew(entity) 조건이 성립되어(새로운 엔티티라고 인식해서) 정상적으로 persist()가 호출된다.
하지만, JPA 식별자 생성전략이 직접 할당이면 이미 식별자 값이 있는 상태로 save()를 호출한다.
/** Id 직접할당 예시 **/
@Id
private String id;
save() 호출 시점에 식별자가 존재하기 때문에 entityInformation.isNew(entity) 조건에 맞지 않아 merge()가 호출된다.
merge는 DB를 SELECT 하고 값이 없으면 새로운 엔티티로 인지하기 때문에 매우 비효율 적이다.
따라서 Id 직접 생성 전략인 경우, Persistable를 사용해서 새로운 엔티티 확인 여부를 직접 구현하는 것이 효과적이다.
Persistable 인터페이스
package org.springframework.data.domain;
public interface Persistable<ID> {
@Nullable
ID getId();
boolean isNew();
}
Persistable 구현
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
@Id
private String id;
@CreatedDate
private LocalDateTime createDate;
public Item(String id) {
this.id = id;
}
@Override
public String getId() {
return null;
}
@Override
public boolean isNew() {
return createDate == null;
}
}
- Id 직접 할당 경우 Persistable 인터페이스를 상속받아 isNew() 메서드를 오버라이딩하여 @CreateDate의 값이 있는지 확인한다.
- save() 메서드에서 오버라이딩한 isNew() 조건으로 새로운 엔티티 여부를 판단하여 Id 직접 할당 경우에도 데이터 생성 시 merge가 아닌 pesist 할 수 있다.
해당 글은 인프런의 [실전! 스프링 데이터 JPA] 강의를 정리한 내용입니다.
실전! 스프링 데이터 JPA - 인프런 | 강의
스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.
www.inflearn.com
'Spring > Spring Data JPA' 카테고리의 다른 글
[Spring Data JPA] 4. 확장 기능 (0) | 2023.05.22 |
---|---|
[Spring Data JPA] 3. 쿼리 메소드 기능 (0) | 2023.05.19 |
[Spring Data JPA] 2. 공통 인터페이스 기능 (0) | 2023.05.17 |
[Spring Data JPA] 1. 예제 도메인 모델 (0) | 2023.05.17 |
댓글