JPA를 사용할 때 나는 어떤걸 고려해봐야 할까?

무지성개발자·2024년 3월 9일
0

서론

아직 JPA를 사용해본적이 없어 JPA공부해보고 내가 JPA를 사용하게 된다면 고려해봐야 할 것들을 정리해본다. 순서와 중요도는 전혀 상관이 없다.

ToString은 조심히

엔티티를 @ToString 어노테이션으로 간단히 사용하려하면 연관관계끼리 toString을 하려고해서 무한루프에 빠질 수 있다. 때문에 연관관계 엔티티 끼리는 toString에서 제외 시켜주자

레이지 로딩 사용

XXXToOne 관계는 기본 EAGER인데 LAZY로 변경해서 사용하는게 좋다. 둘다 N+1문제를 야기하지만 LAZY는 튜닝의 여지가 있고 필요에 따라 프록시를 사용해야 할 경우도 있기으니 LAZY를 기본으로 사용하자.

단방향 설계부터 하자

단뱡향 설계가 양방향 설계보다 무조건 좋다는 건 아니다. 하지만 우선 단방향 설계부터 하고 필요시에 양방향 설계를 추가하자.

다대다 관계는 사용하지 말자

다대다 관계는 RDB에서 사용되는 개념이지 객체지향과는 거리가 있기도 하고 성능상 좋지도 않다. 그러니 다대다 관계는 중간에 엔티티를 하나 더 사용해서 일대다, 다대일로 관계를 풀어서 사용하자.

값 타입(value type)은 변경불가능하게 설계

값 타입은 변경이 불가능 해야한다. 값 타입을 클래스로 만들어서 참조해서 사용해서 헷갈릴 수도 있지만 클래스 자체가 값이라고 간주해야하며, 값은 항상 통채로 바꿔야지 클래스 속성 하나만 변경하고 그러면 안된다.

세터 사용을 지양하자

비단 JPA에서만 통용되는 말은 아니지만, 엔티티의 속성을 변경할 땐 세터로 하나씩 변경하는 것 보단 의미있는 메서드를 사용해서 변경하는 것이 유지보수 측면에서도 좋으니 이 방법을 추천한다.

컬렉션은 필드에서 초기화 하자

JPA의 구현체로 Hibernate을 많이 사용할 텐데 하이버네트는 컬랙션을 한번 더 감싸서 사용하는데 이 때 초기화 되어 있지 않다면 제대로 동작을 안할 수 도 있다고 한다.

엔티티 업데이트는 변경감지를 사용하자

엔티티 업데이트는 변경감지와 marge()를 사용할 수 있는데 marge는 준영속 상태의 엔티티를 영속상태로 변경시키는 것으로 결국 변경감지를 사용하긴 한다. 하지만 변경감지는 필요한 속성만 변경할 수 있지만 marge는 엔티티를 통으로 업데이트 되기 때문에 엔티티를 잘 못 설정하면 Null값을 업데이트를 할 수 도 있다.

JPQL은 QueryDSL을 사용하자

순수 JPQL은 결국 스트링 값이기에 런타임에서 오류를 발견하게 되고 동적 쿼리를 구성하기도 힘들다. 이 모든걸 해결해주는 것이 QueryDSL이며 컴파일 타임에 에러나 동적 쿼리에 아주 강력하다.

api요청,반환은 DTO를 사용하자

일단 api요청, 반환을 엔티티로 사용하게 될 경우 엔티티 자체가 노출 되는데 이건 지양해야하는 것 이다.

Request를 엔티티로 사용하게 될 경우

  • api 스펙이 엔티티가 되므로 엔티티 변경이 있다면 사용한 모든 api에 영향이 감.
  • 여러곳에서 사용하게 되면 유효성 검사를 모두 공용으로 하게 될 수 밖에 없음. 이러면 안됨.

Response를 엔티티로 사용하게 될 경우

  • api 스펙이 엔티티가 되므로 엔티티 변경이 있다면 사용한 모든 api에 영향이 감.
  • 양방향 관계가 있는 엔티티가 반환타입이 되면 무한 루프에 빠질 수 있음.

쿼리 리턴타입을 정하는 법

쿼리의 리턴타입을 엔티티를 받을지 DTO로 받을지는 서로 장단점이 있다. 엔티티로 받으면 일단 쿼리가 간단해지며 그래프 탐색도 하기 편하며 DTO로 받으면 쿼리문이 좀 지저분 해지고 그래프 탐색은 불가능하지만 fit한 결과를 얻을 수 있다.

JPA 일타강사이신 김영한님이 추천하는 방법은 다음과 같다.

  • 우선 엔티티로 리턴받고 필요한 값을 DTO로 변환.
  • 필요하면 페치 조인으로 성능을 최적화.
    • 대부분의 성능 이슈 해결.
  • 그래도 안되면 DTO로 직접 조회하는 방법 사용.
  • 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용.

컬렉션 조회

XXXToMany관계를 가진다면 엔티티에서 컬렉션으로 참조하게 되는데 이게 참 골때려서 생각을 잘 해봐야한다.

우선 페치조인을 사용하면 모든 값을 읽고 메모리에서 페이징을 하려 하기 때문에 페이징이 안된다고 봐야함. 2개 이상의 컬랙션은 페치조인 안됨 등 문제가 많으니 다른 방식을 생각해볼 필요가 있다.

  • 먼저 XXXToOne관계만 페치 조인으로 값을 가져옴.
    • 페이징도 여기서 함.
  • 레이지로딩을 이용해서 컬렉션 값들을 필요한 만큼 가져옴.
    • 이때 그냥 가져오면 N+1문제가 있음.
    • hibernate.default_batch_fetch_size 또는 @BatchSize를 사용해서 최소한의 쿼리만 사용해서 컬렉션값들을 가져올 수 있도록 최적화 한다면 1+1~2정도의 쿼리로 값들을 가져올 수 있음.

OSIV는 끄는게 기본같다.

OSIV를 간단하게 설명하면 영속성 컨텍스트를 트랜잭션범위를 넘겨서도 사용할 수 있게 하는 방법이다. MVC패턴을 사용하면 VIEW단에서도 레이지로딩을 사용할 수 있다.

OSIV는 default값이 ture며 OSIV가 켜져있다면 리소스를 계속 사용하게 된다는 단점이 있다. 때문에 많은 처리량이 요구되는 api 서버는 OSIV를 끄고 처리량이 적은 관리자 페이지는 열어서 사용해도 얼추 괜찮고 영속성 컨텍스트를 유연하게 사용할 수 있어 편리함도 가져갈 수 있다.

profile
no-intelli 개발자 입니다. 그래도 intellij는 씁니다.

0개의 댓글