Home > Java-Ecosystem > JPA > JPA 활용 팁 1

JPA 활용 팁 1
Java ORM JPA

JPA 테이블 설계 Tips

  • 주문 테이블은 orders로 주로 사용 (예약어 order by 때문에)
  • 테이블 이름은 소문자 + _ 스타일 사용
  • 실무에서 @ManyToMany는 사용하지 말자
    • 중간 테이블에 컬럼을 추가할 수 없고 세밀한 쿼리가 어려움
    • @ManyToOne, @OneToMany로 풀어내서 사용
  • 연관 관계에서 외래 키가 있는 곳을 연관 관계의 주인으로 정하기 (One-to-Many에서는 Many가 주인)
  • Getter, Setter는 모두 제공하지 않고, 꼭 필요한 별도 메서드만 제공하는게 가장 이상적이지만 실무는 다름
    • Getter는 모두 열어놓으면 실무상 편리
    • 엔티티 변경은 Setter를 모두 열어두기 보다 비즈니스 메서드를 별도 제공해 변경 지점이 명확하도록 함
  • 엔티티의 식별자는 id로 쓰더라도 PK 컬럼명은 테이블명_id로 사용하자
    • Foreign key와 이름을 맞출 수 있는 장점
    • DBA들도 선호
  • Cascade=ALL
    • 엔티티를 persist하면 다른 연관관계 엔티티까지 persist를 전파
    • Delete할 때는 모두 같이 지워짐
  • 값 타입(임베디드 타입)은 변경 불가능하게 설계
    • @Setter를 제거하고 생성자에서 초기화 강제
    • 기본 생성자를 protected로 두어 안전 향상
      • JPA 스펙 상 엔티티 및 임베디드 타입은 기본 생성자를 public 혹은 protected로 두어야 함
      • JPA가 객체 생성시 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문
  • 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해 변경 컬럼에 유니크 제약 조건 추가하는 것이 안전

엔티티 설계 시 주의점

  • 모든 연관관계는 지연로딩(Lazy)으로 설정
    • 즉시로딩(Eager)은 예측이 어렵고 N + 1 문제가 자주 발생
    • 연관 관계 엔티티 로딩 시 fetch join 혹은 엔티티 그래프 기능 사용
    • @XToOne 관계는 기본이 즉시로딩이므로 직접 지연로딩 설정을 해야 함
  • 컬렉션은 필드에서 초기화
    • null 문제에서 안전
    • Hibernate은 엔티티 영속화 시 컬렉션을 감싸서 Hibernate이 제공하는 내장 컬렉션으로 변경 (PersistentBag) - 필드 초기화가 내부 매커니즘을 안전하게 지켜줌
  • 테이블, 컬럼명 생성 전략
    • 기본 전략
      • 하이버네이트 기존 구현
        • 엔티티의 필드명을 그대로 테이블 컬럼명으로 사용
        • SpringPhysicalNamingStrategy
      • 스프링 부트 신규 설정
        • Camel case -> Snake case
        • . -> _
    • 추가 전략
      • 명시적으로 컬럼, 테이블명을 적으면 실제 테이블에 물리명 적용 (physical-strategy)
      • 적지 않은 경우 논리명 적용 (implicit-strategy)

애노테이션 Tips

  • @PersistenceContext
    • 엔티티 매니저(EntityManger) 주입
    • Lombok 생성자 주입 사용시 애노테이션 생략 가능
  • @Transactional
    • readOnly=true
      • 플러시를 하지 않으므로 약간의 성능 향상
    • 디폴트는 readOnly=false이므로, 큰 스코프에서 readOnly=true를 설정하고 커맨드성 작업에 @Transactional을 붙이는 방식으로 사용하면 편리
    • 테스트에 붙으면, 테스트 종료 후 자동으로 트랜잭션 롤백

테스트를 환경을 위한 설정 파일

  • 테스트 케이스에는 메모리 DB 사용이 효율적
  • 데이터 소스나 JPA 관련 별도 추가 설정을 하지 않아도 됨
    • 스프링 부트는 datasource 설정이 없으면 기본적으로 메모리 DB 사용
    • 스프링 부트는 jpa 설정이 없으면 ddl-auto: create-drop 모드로 동작
  • 설정 파일 읽기 전략
    • 테스트에서 스프링을 실행하면, test/resources/application.yml을 읽음
    • 해당 위치에 없을 경우, src/resources/application.yml 읽음

도메인 모델 패턴 VS 트랜잭션 스크립트 패턴

  • 도메인 모델 패턴
    • 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 패턴 (서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할)
  • 트랜잭션 스크립트 패턴
    • 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 패턴

변경 감지(Dirty Checking) & 병합(merge)

  • 준영속 엔티티
    • 영속성 컨텍스트가 더이상 관리하지 않는 엔티티
    • 이전에 DB에 한 번 저장되어서 식별자가 존재하나 JPA가 현재 추적하고 있지 않는 객체
  • 준영속 엔티티를 수정하는 2가지 방법
    • 변경 감지(Dirty Checking) - Recommendation
      • 동작
        • 식별자로 엔티티를 조회(find)한 후 데이터 수정
        • 컨텍스트가 종료되면서 트랜잭션 커밋 시점에 변경 감지가 동작하고, 데이터베이스에 UPDATE SQL 실행
    • 병합(merge)
      • 동작
        • 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회
        • 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체 (병합)
        • 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터 베이스에 UPDATE SQL 실행
      • 병합은 모든 필드를 변경해버리고 데이터가 없으면 null로 업데이트하므로 위험
  • Best Practice: 엔티티 변경시 항상 변경 감지 사용하기
    • 컨트롤러에서 엔티티 생성하지 말기
    • 서비스 계층에 식별자(id)와 변경할 데이터를 명확히 전달 (파라미터 or DTO)
    • 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경
    • 트랜잭션 커밋 시점에 변경 감지 자동 실행