스프링 데이터 JPA는 JPA를 매우 편리하게 사용하도록 해준다는 것을 알았다.
이제 이런 스프링 데이터 JPA가 어떤 방식으로 이렇게 편리하게 할 수 있도록 도와주는지 알아보자.
스프링 데이터 JPA 구현체 분석
- 스프링 데이터 JPA가 제공하는 공통 인터페이스의 구현체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> ... {
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entit);
return entity;
} else {
return em.merge(entity);
}
}
//...
}
- @Repository 적용
- JPA 예외를 스프링이 추상화한 예외로 변환한다.
- @Transactional
- JPA의 모든 변경은 트랜잭션 안에서 동작한다.
- 스프링 데이터 JPA는 우선 readOnly가 기본 동작이고, 변경 메서드를 트랜잭션 처리한다.
- readOnly를 함으로써 약간의 성능 향상.
- 서비스 계층에서 트랜잭션을 시작하지 않으면 리파지토리에서 트랜잭션을 시작한다.
- 서비스 계층에서 트랜잭션을 시작하면 리파지토리는 해당 트랜잭션을 전파 받아 사용한다.
- 따라서 스프링 데이터 JPA를 사용할 때 트랜잭션이 없어도 데이터 등록과 변경이 가능했던 것이다.
save()
save() 메서드를 보면
- 새로운 엔티티면 persist
- 새로운 엔티티가 아니라면 merge
- merge보단 변경감지! (복습)
이러한 구조를 가지고 있다.
새로운 엔티티인지 구분하는 방법
- 기본 전략
- 식별자가 객체일 때 null로 판단.
- 식별자가 자바 기본 타입일 때 0으로 판단.
- Persistable 인터페이스를 구현해 판단 로직 변경 가능.
1
2
3
4
public interface Persistable<ID> {
ID getId();
boolean isNew();
}
- Persistable 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
@Id
private String id;
@CreatedDate
private LocalDateTime createdDate;
public Item(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public boolean isNew() {
return createdDate == null;
}
}
이러한 방식으로 생성날짜를 판단 기준으로 만들 수 있다.
왜 굳이 이런 작업을 할까?
만약 id를 어떠한 이유로 @GeneratedValue를 사용하지 않고, 어떤 id를 사용한다고 하자.
그리고 객체를 생성할 때 그러한 PK값을 지정해준다고 치자.
1
Item item = new Item("A"); //A = PK 값
그렇다면 save()를 호출했을 때, 이미 id값이 존재하기 때문에 객체가 null이 아니어서 merge()를 호출하게 된다.
개발자 입장에서는 새로운 객체인데 새로운 객체라고 판단하지 않고 merge()를 호출하게 되는 것이다.
- merge()
- merge()는 DB를 호출해 값을 확인하고, 값이 없으면 새로운 엔티티로 인지하기 때문에 매우 비효율적이다.
- 따라서 위와 같이 새로운 판단 조건(createdDate)을 만들어 정의해주면 이와 같은 문제를 해결할 수 있다.