요구사항 분석
기능목록
- 회원 기능
- 회원 등록
- 회원 조회
- 상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
- 주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
- 기타 요구사항
- 상품은 재고 관리가 필요하다.
- 상품의 종류는 도서, 음반, 영화가 있다.
- 상품 카테고리로 구분할 수 있다.
- 상품 주문 시 배송 정보를 입력할 수 있다.
도메인 분석 설계
JPA 는 도메인 모델을 다루는 데에 있어서 Mybatis 보다 상당한 강점이 있다.
그 예시를 도메인과 테이블 분석, 엔티티설계로 순차적으로 요구사항을 적용시켜 보자.
회원은 여러 상품을 주문할 수 있다.
그리고 한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품은 N:M 관계다.
하지만 이런 N:M 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않는다.
따라서 그림처럼 주문상품이라는 엔티티를 추가해서 N:M 관계를 1:N, N:1 관계로 풀어내 가는 방식이 많이 사용된다.
엔티티(Entity)란
- JPA가 관리하는 클래스
- JPA를 사용해 테이블과 매핑할 클래스
- public, protected 기본 생성자 필수
- JPA의 구현체(ex: hibernate)가 지원하는 다양한 기능을 사용하기 위함
- final, enum, interface, inner 클래스 사용 불가
- @Entity로 매핑이 불가능하기 때문
또한, @Table 어노테이션을 이용해 매핑할 테이블 이름 지정, DB catalog 매핑, DB schema 매핑, DDL 생성 시 유니크 제약 조건 생성 등을 설정할 수 있다.
(DDL: CREATE, DROP, ALTER, TRUNCATE 같은 데이터 정의어)
엔티티 클래스
도메인과 엔티티의 구성을 보면 객체와 관계형 DB의 패러다임이 일치하다.
제약사항
JPA사용에 있어서 DB는 N:M 매핑을 할 수 없다.
- N:M 매핑은 1:N, M:1로 변환해야 한다.
JPA가 Id 찾기 위해선 어노테이션을 통해 PK 키 매핑을 해야 한다.
- @GenareteValue
- PK 매핑을 DB에 위임, 키 시퀀스를 알아서 해줌
- Int의 경우 AUTO_INCREMANT
연관 관계 매핑에서는 서로 동일한 필드를 참조하므로 연관 관계의 주인이 필요하다
- 1:N 매핑의 경우 보통 FK를 가지고 있는 엔티티가 주인이 된다.
- 1:1 매핑의 경우 데이터 접근이 많은 쪽이 주인이 된다.
예제 코드
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
// @NotEmpty 별도의 dto를 활용하여 설정해주는것이 정석
private String name;
}
필드와 칼럼 매핑 annotation 정리
- @Column : 컬럼 매핑
- @Temporal : 날짜 타입 매핑 (LocalDAte 사용 시 생략 가능)
- @Enumerated : enum 타입 매핑
- Enumrated를 사용하면 Enum 매핑 가능
- 기본적 Enum 타입은 ORDINAL인데 이건 숫자를 자동으로 증가 시킴
하지만 중간에 값이 추가되거나 삭제될 시 숫자가 밀려버리는 등의 문제점 발생 - 반드시 Enum 타입은 STRING을 사용 @Enumerated(EnumType.STRING)
- @Lob : BLOB, CLOB 매핑
- @Transient : 매핑 무시
- @GeneratedValue의 속성
- IDENTITY: DB에 위임
- SEQUENCE: DB 시퀀스 오브젝트 사용
- TABLE: 키 생성용 테이블 생성해 DB 시퀀스 흉내
- AUTO: 방언에 따라 자동 지정 (default)
연관 관계 매핑
연관관계 매핑의 팁. 외래 키가 있는 곳을 연관관계의 주인으로 정해라.
양방향 연관 관계는 주인과 주인이 아닌 쪽으로 나눌 수 있다.
연관 관계의 주인은 외래 키를 관리하고, 주인이 아닌 쪽은 read 기능만 할 수 있도록 한다.
주인이 아닌 쪽은 mappedBy 속성을 통해 나타낸다.
일대일 (1:1) 매핑
- @OneToOne
- '주 테이블에 외래 키', '대상 테이블에 외래 키' 중 선택 가능
일대다 (1:N) 매핑
- @OneToMany
- 객체의 구조상 반대편 테이블의 외래 키 관리
- 되도록이면 다대일 양방향 매핑 이용
다대일 (N:1) 매핑
- @ManyToOne
- 외래 키가 있는 쪽이 연관 관계의 주인
- 양쪽을 서로 참조하도록 개발
다대다 (N:M) 매핑
- @ManyToMany
- RDB는 정규화된 테이블 2개로 N:M 관계 표현 불가능
- 많은 한계가 존재해 거의 사용하지 않음
상속 관계 매핑
관계형 DB는 상속 관계를 갖지 않는다.
슈퍼타입, 서브타입을 통해 서로 관계를 갖는다.
- @Inheritance(strategy = InheritanceType.JOINED)
- 가장 이상적인 상속 구현
- 정규화와 참조 무결성을 만족한다면 객체와 가장 비슷함
- 테이블의 정규화
- 저장공간 효율화
- 비즈니스 로직적으로 중요하면 join 하여 사용
- 조회 시 조인 많이 사용 (성능 저하 가능성)
- 조회 쿼리의 복잡성
- 데이터 저장 시 INSERT문 2번 호출
(ex: ALBUM에 칼럼 추가하려면 ALBUM과 ITEM 테이블 둘 다 INSERT 해야 함)
- @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- 조인이 필요 없으므로 일반적으로 조회 성능 빠름
- 조회 쿼리 단순
- 자식 엔티티가 매핑한 칼럼이 존재하지 않으면 null 허용
- 서비스 규모가 크지 않을 때 때려 박는 방법
- 단일 테이블에 모든 것을 저장해 테이블이 커짐 (상황에 따라 조회 성능 오히려 떨어질 수도)
- (다만 이 경우는 테이블이 임계점에 다를 때 발생하는 문제로 보통은 문제가 없어서 많이 사용)
- @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
- DBA, ORM 모두가 비추천
- 서브 타입을 명확하게 구분해 처리할 때 효과적
- not null 제약조건 사용 가능
- 여러 자식 테이블을 함께 조회할 때 UNION 필요 (성능 저하)
- 자식 테이블을 통합해 쿼리 하기 어려움
엔티티 설계 시 주의점
엔티티에는 가급적 Setter를 사용하지 말자
- Setter가 모두 열려있다. 변경 포인트가 너무 많아서, 유지보수가 어렵다. 나중에 리펙토링으로 Setter 제거
모든 연관관계는 지연로딩으로 설정!
- 즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생
- 실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정
- 연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.
- @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정
컬렉션은 필드에서 초기화하자.
- null 문제에서 안전하다.
- 하이버네이트는 엔티티를 영속화할 때, 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로
변경한다. 만약 getOrders()처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부
메커니즘에 문제가 발생할 수 있다. 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.
'Back-End > Spring' 카테고리의 다른 글
[MVC] DTO 역할 구분, 프로젝트 흐름도 분석 (0) | 2023.05.09 |
---|---|
[JPA] Entity를 Dto로 변환(리턴)의 중요성 (0) | 2023.05.08 |
[JPA] JPA를 사용하는 이유와 JPA에 대하여 (0) | 2023.04.18 |
[Spring] 테스트 코드 작성, Mock (0) | 2023.04.17 |
[MVC-2편] 오류 코드와 메시지 처리 (0) | 2023.04.16 |