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)
- 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경
- 트랜잭션 커밋 시점에 변경 감지 자동 실행