JPA 활용편 - 엔티티 설계 정리

Stella·2022년 4월 27일
0

Java

목록 보기
6/18

엔티티 설계

OneToOne:

  • 두 테이블 중 아무곳이나 FK 둬도 됨. 많이 Access 하는 테이블에 생성하는 것 추천.

@Enumerated(EnumType.STRING):

  • ORDINAL, STRING 옵션.
  • Default: ORDINAL-컬럼이 숫자로 들어감 1,2,3,4 -> 문제: 중간에 다른 상태가 생기면 문제생기기 때문에(순서가 바뀜) ORDINAL 사용 지양
  • EX) READY-0, COMP-1 -> READY-0, XXX-1, COMP-2 처럼 중간에 다른 상태 생성 시 순서가 바뀌는 문제

ManyToMany: 실무에서는 사용하지말자!

  • 객체는 다 collection에 있어서 다대다 관계가 가능한데, 관계형 db는 collection 관계를 양쪽에 가질 수 없기 때문에 다대다 관계를 일대다,다대일로 풀어낼 중간 테이블 필요. -> 두 테이블의 pk를 fk로 가짐. 더 이상 field를 추가할 수 없기 때문에 실무에서 사용하지 않음, 실무에서는 field를 더 추가하기 때문에 단순하게 mapping이 끝나지 않음.
  • 중간 entity를 만들고 @ManyToOne, @OneToMany로 매핑해서 사용.
    @ManyToMany
    @JoinTable(name="category_item") // 중간 table mapping, 객체는 다 collection에 있어서 다대다 관계가 가능한데, 관계형 db는 collection 관계를 양쪽에 가질 수 없기 때문에 다대다 관계를 일대다,다대일로 풀어낼 중간 테이블 필요.
    private List<Category> categories = new ArrayList<>();

FK 연결 유무:

  • 시스템마다 다름.
  • 실시간 트래픽, 정확성보다 서비스가 잘 되는게 더 중요하면 fk 빼고 index만 잘 잡아주면 됨.
    데이터가 항상 맞아야하고 연관관계가 중요하면 fk 연결.

Getter, Setter:

  • 이론적으로 Getter,Setter 모두 제공하지 않고, 필요한 메서드만 별도로 제공하는게 이상적. - 실무에서는 조회할 일이 많기 때문에 Getter는 모두 열어주는게 편리.
  • Getter는 data를 조회만 하기 때문에 별다른 문제가 없음.
  • 하지만 Setter는 data의 변경 유발. Setter를 막 열어두면 엔티티가 왜 변경되었는지 추적하기 점점 힘들어짐. 그래서 entity를 변경할 때는 Setter를 다 열어두기보다는 변경지점이 명확하도록 변경을 위한 비즈니스 method를 별도로 제공해야함. 그래야 유지보수성이 올라감.

DB의 pk를 table이름_id로 한 이유:

  • 객체는 타입이 있기 때문에 어디 소속인지 명확(Member.id).
  • table은 단순히 id라고만 하면 실무에서는 찾기가 쉽지않고 join할 때도 명확하게 확인가능(fk랑 이름을 맞춤).

값 타입:

  • 변경불가능하게 설계. immutable.
  • Setter 제공 하지말 것.
  • 생성할 때만 값을 셋팅. 생성자에서 값을 모두 초기화해서 변경불가능한 클래스로 만듦.
  • JPA 스펙상 entity나 embedded 타입은 자바 기본생성자(default constructor)를 public 또는 protected로 설정. protected로 설정하는게 더 안전.
  • JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야하기 때문에. -> 기본생성자 만들어줘야함.

cascade

// Order class
 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
 private List<OrderItem> orderItems = new ArrayList<>();
  • orderItems에 data를 넣어두고 order를 저장하면 같이 저장됨.
  • 원래는 OrderItems를 먼저 저장한 후 order를 저장해야함, entity 당 각각 persist 호출해야하는데 cascade를 사용하면 persisit를 전파해서 orderItem도 같이 persist해줌.

엔티티 설계시 주의사항

Entity:

  • 가급적 Setter 사용 하지말기.
  • Setter가 모두 열러있으면, 변경 포인트가 너무 많아서, 유지 보수 어려움.

모든 연관관계는 지연 로딩 :

  • 즉시 로딩(EAGER)은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어려움. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생.
// Order class에 member를 지정해 준 경우
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="member_id")
private Member member;

// order를 조회 할때마다 member도 같이 가지고옴
// em.find() 처럼 한건 조회 하는 경우는 괜찮지만, 
// JPQL을 사용해서 select o from order o -> SQL로 그대로 변역 select * from order
// order를 100개를 가지고 오고, member를 가져오기 위해 100개의 query 사용 -> n+1 문제 
// -> 이 경우는 100(member불러오는 query)+1(첫번째 order query)
  • 실무에서 모든 연관관계는 지연로딩(LAZY) 설정.
  • 연관된 entity를 함께 db에서 조회해야 하면, fetch join 또는 entity graph 기능을 사용.
  • @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시 로딩이라서 직접 지연로딩 설정 필요.

Collection은 필드에서 초기화:

  • 필드에서 바로 초기화 하는 것이 안전.
  • null 문제에서 안전.
// 이 방법이 best pratice
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();

// 이렇게도 할 수 있지만 위 방법 사용
public Member() {
	orders = new ArrayList<>();
    }
  • hibernate는 entity를 영속화 할 때, collection을 감싸서 hibernate가 제공하는 내장 collection으로 변경한다. 만약 getOrders() 처럼 임의의 method에서 collection을 잘못 생성하면 hibernate 내부 메커니즘에서 문제가 발생. 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도간결. collection을 생성하고 가급적이면 변경 X.
Member member = new Member();
System.out.println(member.getOrders().getClass());
em.persist(member);
System.out.println(member.getOrders().getClass());

// 출력 결과
class java.util.ArrayList
class org.hibernate.collection.internal.PersistentBag

테이블,컬럼명 생성전략

  • Spring Boot에서 hibernate 기본 mapping 전략을 변경해서 실제 테이블 필드명은 다름.
  • hibernate 기존 구현 : entity의 필드명을 그대로 테이블명으로 사용(SpringPhysicalNamingStrategy).
  • 스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
    1. camle case -> underscore(memberPoint->member_point)
    2. .(점) -> underscore
    3. 대문자 -> 소문자
  • 논리명 생성: 명시적으로 컬럼, 테이블명을 직접 적지 않으면 ImplicitNamingStrategy 사용.
    spring.jpa.hibernate.naming.implicit-strategy : 테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용.
  • 물리명 적용:
    spring.jpa.hibernate.naming.physical-strategy : 모든 논리명에 적용됨, 실제 테이블에 적용. (username -> usernm 등으로 임의로 바꿀 수 있음)
    스프링 부트 기본 설정
  • spring.jpa.hibernate.naming.implicit-strategy:
    org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
  • spring.jpa.hibernate.naming.physical-strategy:
    org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
profile
Hello!

0개의 댓글