포스트

2023-06-26-TIL

2023-06-26-TIL

Today I Learned

JPA findAll 안티 패턴

Vlad Mihalcea는 Spring Data의 모든 리포지토리가 상속받는 findAll()을 안티 패턴이라고 부른다. 메서드가 늘 손 닿는 곳에 있으니 테이블 전체를 메모리에 올린 뒤 Java에서 거르는 코드가 쉽게 만들어지고, 개발 데이터에서는 문제가 없다가 운영 규모에서 성능 문제가 된다는 것이다. 글은 연관관계까지 지연 로딩되며 쿼리가 수십 개로 불어나는 예를 보여 주고, findAll()을 막아 둔 기본 리포지토리(Hypersistence Utils의 HibernateRepository나 Repository를 직접 상속한 인터페이스)를 대안으로 제시한다.

  • https://vladmihalcea.com/spring-data-findall-anti-pattern/

@ManyToOne 연관관계 모범 사례

같은 저자의 @ManyToOne 정리는 다음과 같다. @ManyToOne의 기본 fetch 전략은 EAGER라서 N+1 문제를 일으키기 쉬우므로 FetchType.LAZY를 명시한다. 자식을 저장할 때 부모는 외래 키 값만 필요하므로 find() 대신 getReference()(Spring Data JPA에서는 getReferenceById())로 프록시를 넘긴다. 부모와 자식을 함께 읽어야 하면 쿼리에서 JOIN FETCH를 쓴다. fetch 전략은 전역이 아니라 사용 사례마다 정해야 한다는 것이 글의 원칙이고, 이 관계는 대부분 단방향으로 충분하다.

  • https://vladmihalcea.com/manytoone-jpa-hibernate/

양방향 연관관계 동기화

양방향 연관관계에서는 객체 양쪽을 함께 갱신해야 한다. Vlad Mihalcea는 양쪽을 동기화하지 않으면 엔티티 상태 전이가 제대로 동작한다고 보장할 수 없다고 경고한다. 그래서 부모 엔티티에 addComment(), removeComment() 같은 편의 메서드를 두어 컬렉션과 자식의 부모 참조를 한 번에 바꾸게 한다. 이 메서드들은 컬렉션의 contains(), remove()에 의존하므로 equals()와 hashCode()를 식별자나 자연 키(@NaturalId) 기준으로 올바르게 구현해야 한다.

1
2
3
4
5
6
7
8
9
public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}
  • https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/

기본 키가 아닌 컬럼으로 @ManyToOne 매핑하기

외래 키가 부모의 기본 키가 아니라 ISBN 같은 다른 유니크 컬럼을 가리키는 레거시 스키마라면 @JoinColumn(name = "isbn", referencedColumnName = "isbn")으로 매핑한다. 글에 따르면 참조되는 엔티티는 Serializable을 구현해야 이 매핑이 가능하고, 참조 컬럼은 비즈니스 키이므로 @NaturalId로 표시하기를 권한다. 조인 조건이 더 복잡하면 Hibernate 전용 @JoinFormula를 쓸 수 있다.

  • https://vladmihalcea.com/how-to-map-a-manytoone-association-using-a-non-primary-key-column/

추가 컬럼이 있는 다대다 연관관계 매핑

연결 테이블에 생성 시각 같은 추가 컬럼이 필요해지면 @ManyToMany로는 표현할 수 없다. 글의 방법은 연결 테이블을 엔티티로 승격하는 것이다. 두 외래 키를 담은 @Embeddable 복합 키(Serializable, equals/hashCode 구현)를 @EmbeddedId로 쓰고, 각 연관관계는 @MapsId로 그 키에 연결한다. 양쪽 부모에는 @OneToMany를 두고 편의 메서드로 동기화한다. 글은 양쪽 모두에 컬렉션을 두면 삭제 시 SELECT가 더 나가므로, 양방향 탐색이 꼭 필요하지 않다면 한쪽에만 컬렉션을 두는 편이 SQL이 적다고 비교한다.

  • https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/

Spring Data JPA findById 안티 패턴

자식 엔티티를 만들면서 부모를 연결하려고 postRepository.findById(postId).orElseThrow()를 부르면 외래 키 값만 필요한데도 부모 전체를 SELECT한다. 글은 이를 getReferenceById(postId)로 바꾸면 ID만 가진 프록시로 충분하다고 설명한다. 또 findById로 부모가 있는지 확인해도 그 직후 다른 트랜잭션이 부모를 지울 수 있으므로, 존재 검증은 결국 외래 키 제약이 맡는다는 점을 지적한다.

  • https://vladmihalcea.com/spring-data-jpa-findbyid/

JPA와 Hibernate의 연관관계 매핑 정리

연관관계 매핑을 전반적으로 정리한 자료들이다. 공통적으로 다루는 내용은 단방향과 양방향의 차이, 연관관계의 주인(mappedBy가 없는 쪽만 외래 키를 갱신한다), 지연 로딩 기본화, 컬렉션 타입 선택(@ManyToMany에는 Set), cascade와 orphanRemoval의 범위다. velog 글 중 하나는 양방향보다 단방향 연관관계를 기본으로 두어야 하는 이유를 다루고, JPA Buddy 글은 이런 내용을 면접 질문 형태로 묶는다.

  • https://thorben-janssen.com/ultimate-guide-association-mappings-jpa-hibernate/
  • https://www.baeldung.com/jpa-hibernate-associations
  • https://velog.io/@dnjscksdn98/JPA-Hibernate-Bidirectional-Unidirectional-Relationships
  • https://velog.io/@jhp1115/%EC%96%91%EB%B0%A9%ED%96%A5%EB%B3%B4%EB%8B%A4-%EB%8B%A8%EB%B0%A9%ED%96%A5-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84%EA%B0%80-%EC%A2%8B%EB%8B%A4
  • https://www.youtube.com/watch?v=rYj8PLIE6-k
  • https://developer-ping9.tistory.com/255
  • https://jpa-buddy.com/blog/spring-data-jpa-interview-questions-and-answers/

Spring Data JPA DTO 프로젝션을 가져오는 방법

Vlad Mihalcea의 글은 DTO 프로젝션 방법을 비교한다. JPA Tuple은 타입 캐스팅이 필요해 타입 안전하지 않고, 인터페이스 기반 프록시는 타입 안전하지만 equals/hashCode를 정할 수 없다. JPQL 생성자 표현식으로 만드는 Java record와 POJO DTO는 타입 안전하고, 그중 record가 간결하면서 동등성 메서드까지 갖춰 균형이 좋다고 평가한다. 부모-자식 계층 구조의 DTO가 필요하면 네이티브 쿼리와 Hibernate의 ResultTransformer를 쓴다.

  • https://vladmihalcea.com/spring-jpa-dto-projection/

Spring Data와 Hibernate를 쓰지 않을 때의 장점

ORM 없이 관계형 데이터를 다루는 쪽의 주장을 담은 글이다. 원문은 열어 보지 못해 제목과 일반적인 논점으로 정리하면, 영속성 컨텍스트나 지연 로딩 같은 추상화가 숨기는 동작 대신 실행되는 SQL을 직접 통제할 수 있고, 엔티티 매핑 규칙에 도메인 모델이 끌려가지 않는다는 점이 주로 거론된다. 대신 매핑 코드와 변경 감지를 직접 책임져야 한다. 앞의 글들이 대부분 JPA를 잘 쓰는 방법을 다룬 만큼, 반대 관점을 함께 읽어 둔 것이다.

  • https://itnext.io/advantages-of-not-using-spring-data-and-hibernate-with-relational-data-8a509faf0c48
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다