- OneToOne 관계의 경우 어느쪽에 FK를 둬도 상관은 없음, 보통 액세스를 많이 하는쪽에 FK를 지정함.
- FK와 가까운 곳을 주인으로 지정하면 됨.
주인이 아닌 쪽에 MappedBy를 지정해줌. (mappedBy = "member") Order Table에 있는 member 필드를 통해서 매핑된거라는 의미- FK 를 꼭 지정해야하는지? 시스템마다, 상황마다 다르다.
시스템의 실시간 트래픽이 굉장히 중요하고 정합성보다는 성능 등 유연하게 잘 서비스 되는것이
더 중요한 시스템의 경우, FK를 지정하지 않고 Index만 잘 구성해주면 됨.
돈과 관련된 너무 중요한 데이터이기 때문에 정합성이 잘 맞아야한다 이런 이유가 있다면
FK 지정에 대해 진지하게 고민해볼 필요가 있음.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- JOINED - 가장 정규화된 스타일
- SINGLE_TABLE - 한 테이블에 모든 정보를 다 넣음.
- TABLE_PER_CLASS - CLASS 별로 각각 TABLE을 만듬.
@DiscriminatorColumn(name = "dtype")
SINGLE_TABLE 전략을 사용하여 모든 CLASS 정보들을 한 테이블에 넣을거기 때문에 구분값으로 사용할 컬럼을 지정해준다.
Repository는 DAO와 비슷한 개념이라고 보면 됨.
@Repository 어노테이션은 스프링이 제공하는 기본타입, 컴포넌트 스캔의 대상이 되는 어노테이션
컴포넌트 스캔의 대상이 되서 자동으로 스프링 빈에 등록이 됨.
기본키 자동 생성 방법
ex) @GeneratedValue(strategy = GenerationType.IDENTITY) 와 같은 방식으로 표기한다.
기본 키를 직접 할당하기 위해서는 @Id 어노테이션만 사용하면 되고, 자동 생성 전략을 사용하기 위해서는 @Id에 @GeneratedValue를 추가하고 원하는 키 생성 전략을 선택하면 됨.
EnumType은 (EnumType.ORDINAL), (EnumType.STRING) 두가지가 있음.
- ORDINAL - enum value를 1, 2, 3, .... sequential 하게 지정한다. 중간에 enum value가 추가될 경우, 순서가 맞지 않아 장애가 발생할 수 있으므로 운영 환경에 적합하지 않음.
- STRING - enum value를 string 그대로 지정한다.
-> ORDINAL을 쓰지말고 STRING을 써야함.
JAP 스펙 상 엔티티나 임베디드 타입(@Embeddable)은
자바 기본 생성자(Default Constructor)를 public 또는 protected 로 설정해야한다.
이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원해야 하기 때문
리플렉션은 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
JPA의 기본 스펙은 엔티티에 기본 생성자가 필수로 있어야 한다. 하지만 하이버네이트 같은 구현체들은 조금 더 유연하게 바이트 코드를 조작하는 라이브러리등을 통해서 이런 문제를 회피한다. 완벽한 해결책은 아니므로 상황에 따라 다르다.
기본 가이드를 따라 기본 생성자를 넣어주어야 한다.
엔티티에는 가급적 Setter를 사용하지 X , 별도의 create 비즈니스 로직을 만들어야 함.
Setter가 모두 열려있다면, 변경 포인트가 너무 많기 때문에 유지보수가 어렵다.
모든 연관관계는 지연 로딩(LAZY
)으로 설정하자.
즉시로딩(
EAGER
) 은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어려움.
JPQL을 실행할 때 N+1 문제 자주 발생
하나의 엔티티를 조회하면 연관된 엔티티를 모두 DB 에서 끌고옴. -> 사용하지 않는것이 좋음
실무에서 모든 연관관계는 지연로딩으로 설정해야한다.
지연로딩으로 설정해도 연관된 데이터를 DB에서 조회할 수 있음 -> fetch join or 엔티티 그래프 사용
@XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이기 때문에 직접 지연로딩으로 설정해야함.
(@XToMany는 기본 값이 지연로딩임)
보통 즉시로딩을 사용하는 이유 : Transaction 밖에서 Lazy loading이 실행되지 않아서..
-> 트랜잭션을 좀 빨리 가져오거나 Open-Session-In-View 를 통해 해결할 수 있음.
궁극적으로 fetch join을 통해 모두 해결할 수 있음.
Open-Session-In-View (Default는 true) : OSIV 설정을 true 로 지정했을 경우,
사용자에게 응답 또는 View가 렌더링 될 때까지 영속성 컨텍스트를 유지함
-> 영속성 컨텍스트를 유지한다는 것은 곧 DB Connection을 계속 소유하고 있다는 것
실시간 트래픽이 중요한 어플리케이션에서는 문제가 될 수 있다.
컬렉션은 필드에서 초기화
하이버네이트는 엔티티를 영속화할때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경
임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있으니 필드 레벨에서 초기화 하는 것이 가장 안전하고 코드도 간결, null 문제에서도 자유로울 수 있음.
테이블, 컬럼명 생성 전략
하이버네이트 기존 구현 : 엔티티의 필드명을 그대로 테이블 명으로 사용
(SpringPhysicalNamingStrategy)
스프링 부트 신규 설정 : 엔티티(필드) -> 테이블(컬럼)
카멜 케이스 -> 언더스코어 (memberPoint -> member_point)
.(dot) -> _(underScore)
대문자 -> 소문자
사내에 전사 전략이 정해져 있는 경우, SpringPhysicalNamingStrategy를 참고하여 수정, 적용할 수 있음.
논리명 생성 : 명시적으로 컬럼, 테이블명을 직접 적지 않으면 implicitNamingStrategy 사용
물리명 생성 : 컬럼, 테이블명을 명시하거나 명시하지 않거나 모든 상황에 적용됨.
Cascade 전략
모든 Entity는 저장하려면 각각 Persist 해야함.
하지만 Cascade 전략을 ALL로 지정했을때는 Persist를 전파하기 때문에 각각 할 필요가 없음.
Delete 할때도 함께 지움.
public class Order {
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
//위와 같은 구조일 경우 Cascade 전략을 ALL로 지정하지 않았을 경우
persist(orderItemA)
persist(orderItemB)
persist(orderItemC)
persist(order)
//Cascade 전략을 ALL 로 지정했을 경우
persist(order)
}