Home 스프링 데이터 JPA의 내부 구조 (em.merge)
Post
Cancel

스프링 데이터 JPA의 내부 구조 (em.merge)

스프링 데이터 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)을 만들어 정의해주면 이와 같은 문제를 해결할 수 있다.
This post is licensed under CC BY 4.0 by the author.