Entity 설계시 주의할 점
- Setter 사용 금지
- Setter가 모두 열려있으면 변경 포인트가 많아져서 유지 보수가 힘들어짐, 어디서 해당 값이 변경되었는지 추적하기 힘듬
- Setter가 필요할 경우에는 추적하기 쉽도록 메서드를 따로 생성해야함
- 모든 연관관계는 지연로딩(LAZY)로 설정
- 즉시로딩은 예측이 어렵고, 어떤 SQL이 실행될 지 예측하기가 어려움 특히 JPQL 실행 시 N+1 문제가 자주 발생하게 됨
- OneToOne, ManyToOne 관계는 기본이 즉시로딩이므로, 꼭 지연로딩으로 설정해야한다.
- 연관관계에 필요한 entity를 한번에 가져오고 싶은 경우에는 FETCH JOIN을 사용하면 된다.
- 컬렉션은 필드에서 바로 초기화 해야지 NullPointerException으로 안전함
- NPE 문제에서 안전함
- 하이버네이트는 엔티티 영속화할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경함. 만약 getOrders()처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수도 있음
- 필드에 Enum 타입 사용 시 유의점무조건 String을 사용하는게 좋다. 왜냐면 ORDINAL을 사용해 중간에 새로운 type을 추가할 경우, 기존에 만들어놨던 0,1,2 순서와 꼬이게 된다면 난리가 난다.
- @Enumberated(EnumType.ORDINAL) vs @Enumberated(EnumType.STRING)
연관관계
연관관계 매핑
- Entity 클래스 간의 관계를 대상 Entity의 참조를 가지는 형태로 만들어 줄 수 있고, 양 Entity가 서로의 참조를 가지는 경우 양방향 연관관계, 한 Entity만 다른 Entity의 참조를 가지는 경우는 단방향 연관관계라고 한다.
양방향 연관관계
일대다, 다대일
- “한 팀에는 여러 회원이 존재한다”는 경우에 회원 Entity 입장은 팀에 대해 다대일 관계가, 팀 Entity 입장에서는 회원에 대해 일대다 양방향 연관관계를 만들 수 있다.
- 일대다 관계에서는 ‘다’부분에 FK가 존재해야하고, 즉 회원이 팀의 FK를 가진다.
- 양방향 연관관계에서는 연관관계의 주인을 정하는 것이 중요한데, 객체에는 양방향 연관관계라는 것은 사실 없고, 서로 다른 단방향 연관관계 2개를 잘 묶어서 보이게 하는것이다. 반면 DB는 FK 하나로 연관관계가 성립되므로 객체와 DB 둘 간에 차이점이 생긴다. 이 때문에 JPA에서는 연관관계 중 한 객체를 정해서 FK를 관리하게 해야하고, 이를 연관관계의 주인이라고 한다.
- FK가 있는 곳을 연관관계의 주인으로 정해야한다.
- 연관관계의 주인이 FK를 관리(등록, 수정, 삭제)할 수 있고 연관관계의 거울 에서는 mappedBy 속성을 통해 읽기만 가능하다.
public class Member {
/*...*/
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Team team;
}
// 연관관계의 거울
public class Team {
/*...*/
@OneToMany(mappedBy = "member") // Member의 team 필드와 매핑
private List<Member> members = new ArrayList<>();
}
// 실제 team.getMembers().add(member) 를 하고 persist() 해도 null이 저장된다.
// (연관관계의 주인인 Member.team 에 들어가지 않았기 때문)
일대일
1:1 관계는 둘 중 어디든 FK를 둘 수 있다. 더 많이 조회하는 Entity에 둘 것
다대다
실무에서는 @ManyToMany를 사용하지 않는다.
값 타입(임베디드 타입)
Address 같은 객체를 값을 표현하기 위한 타입으로 만들 수 있다. 하지만 값 타입은 절대 변경하면 안된다는걸 명심해야한다.
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
protected Address() {}
public Address(String city, String street, String zipcode) { /*...*/}
}
public class Member {
/*...*/
@Embedded
private Address address;
}
- Setter를 쓰지 않고 생성자를 통해서만 생겨야 함
- default 생성자는 protected 로 만들어서 조금 더 안전하게 해야함
- 하이버네이트로 객체를 생성할 때 리플렉션을 사용할 수 있어야 해서 private로 만들수는 없고, protected로 만들어야 한다.
Collection은 필드에서 초기화
Collection은 생성자나 setter아닌 필드에서 초기화해야한다.
public class Member {
/*...*/
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
- 객체의 초기화에 대한 고민을 안해도 됨
- Hibernate가 Entity를 Persist 하는 순간 collection을 감싸서 hibernate용 내장 컬렉션으로 변경하는데, Hibernate가 collection의 변경사항을 추적하기 위함인데 이걸 setter등을 이용해 초기화를 시켜버릴 경우 원하는 메커니즘으로 동작하지 않을 수 있다.
Cascade 설정
모든 엔티티는 저장하고 싶으면 각각 persist를 해줘야한다.
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
- 위와 같이 써주면 collection에 있는 orderItem을 각각 persist할 필요 없이 persist(order)로 한방에 insert 또는 delete 할 수 있다.
연관관계 편의 메서드
양방향 연관관게에서 setting을 할 경우, 양쪽 다 해줘야 하는 불편함이 있는데, 이를 원자적으로 처리할 수 있게 편의 메서드를 만들어 둘 수 있다.
public class Order {
/*...*/
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
// member에 대한 order setting 도 원자적으로 일어나게 함
}
}
Naming
예약어
- 주문을 나타내는 Order의 경우 예약어를 피하기 위해 객체는 Order로, table은 Orders로 나타내는게 관례이다.
- Table 네이밍
- 소문자의 snake_case가 일반적이다
- Entity의 id
- 객체의 필드는 id라고 만들고 @Column(name = “member_id” 이런식으로 만드는게 좋다.
참고 링크
'Spring > JPA' 카테고리의 다른 글
JPA Repository 메서드 (0) | 2022.07.29 |
---|---|
JPA 소개, 특징, 장점 및 단점 (0) | 2022.07.19 |