본문 바로가기
책/도메인 주도 개발 시작하기

[도메인 주도 개발 시작하기] Chapter 4. 리포지터리와 모델 구현

by 오오오니 2025. 3. 17.

Chapter 4. 리포지터리와 모델 구현

2024년 2월 13일 오후 1:45
비어 있음
💡
JPA를 이용한 리포지터리구현에 대해 알아볼 것

🪄 JPA를 이용한 리포지터리 구현

리포지터리 인터페이스는 애그리거트 루트를 기준으로 작성
조회 : findById()는 Order or null 을 리턴한다.
Optional을 사용하면 값이 없는 Optional이 리턴됨
ID 외에 다른 조건으로 애그리커트를 조회할 때는 Criteria , JPQL 사용할 수 있다.
수정 : 애그리거트를 수정한 결과를 저장소에 반영할 필요 없음. jpa를 사용하면 트랜잭션 범위에서 변경한 데이터를 자동으로 DB에 반영.
성능 개선
dirty Checking 동작원리 & clerAutomatically = true

스프링데이터 JPA를 이용한 리포지터리 구현

리포지터리 인터페이스만 정의하면 구현 객체는 스프링 데이터 JPA가 알아서 만들어주기 때문에 리포지터리 인터페이스를 구현한 클래스를 직접 작성할 일을 거의 없음.
public interface OrderRepository extends JpaRepository<Order, OrderNo>
<T,ID> T: 엔티티 타입, ID는 식별자 타입
스프링 데이터 JPA를 사용 하려면 지정한 규칙에 맞게 메서드를 작성해야함. (5장)
엔티티 조회 : findById()
Order findById(OrderNo id) : 값x → null 리턴
Optional<Order> findById(OrderNo id) : r값x → Optional을 리턴
💡
각각의 장단점은 무엇일까? 각각 언제 쓰면 좋을까?
Optional 사용 좋은 예시

🪄 매핑 구현

엔티티와 밸류 매핑

기본 매핑 : 엔티티 - @Entity , 밸류 - @Embeddable , 밸류 타입 프로퍼티 : @Embedded
루트 엔티티에 속한 밸류는 한 테이블에 매핑할 때가 많음
도메인 주도 개발 시작하기 4장 140p 그림 4.2
ALT
복사
@Entity @Tagble(name = "purchase_order") public class Order { // ... @Embedded //밸류 타입 프로퍼티 private Orderer orderer; // ... }
복사
@Embaddable public class MemberId implements Serializable { @Column(name = "member_id") private String id; // ... }
복사
@Embeddable //밸류 public class Orderer { // MemberId에 정의된 칼럼 이름을 변경하기 위해 // @AttributeOverride 애노테이션 사용 @Embedded @AttributeOverrides( @AttributeOverride(name = "id", column = @Column(name = "orderer_id")) ) private MemberId memberId; @Column(name = "orderer_name") private String name; // ... // 기본 생성자 //jpa에서 클래스를 매핑하기 위해서 기본 생성자를 제공해야함 //jpa 프로바이더가 객체를 생성할 때만 사용됨 // 다른 코드에서 사용할 수 없게 protected로 선언 protected Orderer(){} }

필드 접근 방식

필드 접근
@Access(AccessType.FIELD)
지정 X
복사
@Id // id가 필드에 있으므로 @Access(AccessType.FIELD) private String id;
프로퍼티 접근 (메서드 방식)
@Access(AccessType.PROPERTY)
get,set 메서드 구현
지정 X
복사
@Id // id가 프로퍼티에 있으므로 @Access(AccessType.PROPERTY)와 같다 public String getId() { return id; }
💡
혼용
복사
@Entity public class Member { @Id // 기본은 필드 접근 방식 private String id; @Transient private String firstName; @Transient private String lastName; @Access(AccessType.PROPERTY) // 이 메소드만 프로퍼티 접근 방식 public String getFullName { return firstName + lastName; } }
AttributeConverter
밸류 타입 프로퍼티를 한 개 칼럼에 매핑해야 할 때
ex) Length : {길이, 값} {1000,mm} → 매핑 → {1000mm}
AttributeConverter를 구현한 클래스에 @Converter(autoApply = true ) 어노테이션 적용

밸류 컬렉션 매핑

밸류 컬렉션 매핑 : 별도 테이블 매핑

Order와 OrderLine을 저장하기 위한 테이블
order_number로 purchase_order 참조
order_number은 컬렉션이 속한 엔티티를 의미
line_idx : 인덱스 값
컬렉션
List, Set, Queue, Map
데이터 저장 자료구조 or 알고리즘 구조를 클래스화 한 것
복사
@Entity @Table(name = "purchase_order") @Access(AccessType.FIELD) public class Order { ... @ElementCollection @CollectionTable(name = "order_line", joinColumns = @JoinColumn(name = "order_number")) @OrderColumn(name = "line_idx") //리스트의 인덱스 값 저장 private List<OrderLine> orderLines; } @Embeddable public class OrderLine { @Embedded private ProductId productId; @Column(name = "price") private Money price; @Column(name = "quantity") private int quantity; @Column(name = "amounts") private Money amounts; ... }

밸류 컬렉션 매핑 : 한 개 칼럼 매핑

밸류 컬렉션을 별도 테이블이 아닌 한 개 칼럼으로 저장해야할 때
ex) 이메일 주소 목록 : 도메인 (Set) → DB(,로 구분한 한 개 칼럼 )
AttributeConverter를 구현하여 매핑

밸류를 이용한 ID 매핑

jpa에서 식별자 타입은 Serializable 타입이어야함
복사
@Embeddable public class OrderNo implements Serializable { //Serializable 상속 @Column(name = "order_number") private String number; public boolean is2ndGeneration() {//식별자에 기능을 추가할 수 있는 장점이 있음 return number.startsWith("N"); } // ... }
💡
그럼 int형 아이디는 Serializable 타입일까?

밸류 컬렉션을 @Entity로 매핑하기

상속 관계 매핑이란
이것을 @Embeddable 타입의 클래스는 지원하지 않기 때문에 @Entity로 지정
ex) Product- Image 밸류 (제품의 이미지 업로드 방식에 따라 이미지 경로가 달라짐)
Image를 엔티티로 저장하고 OneToMany매핑했을 때 Image는 Product의 라이프 사이클을 따라간다.
OneToMany 매핑에서 Image 리스트를 삭제하는 것이 쿼리를 자주 호출하게 될 수 있다.
→ 서비스 성능 측면에서 문제가 될 수 있다.
결국 상속을 포기하고 @Embeddable로 매핑된 단일 클래스로 구현

🪄 애그리거트 로딩 전략과 영속성 전파

애그리거트 로딩 전략

@OneToMany(fetch = FatchType.EAGER)
FetchType.EAGER
애그리거트 루트를 구할 때 연관된 구성요소를 DB에서 함께 읽어옴
컬렉션에 설정하면 조회 성능 문제가 생길 수 있음
→ 실행 빈도,트래피그 지연로딩 시 실행속도 검토
FetchType.LAZY
엔티티를 사용하는 시점에 로딩됨
실무에서 매우 권장됨
fetch의 디폴트 값은 @xxToOne에서는 EAGER, @xxToMany에서는 LAZY

영속성 전파

애그리거트는 완전한 상태여야 하므로 함께 저장되고 삭제 되어야 한다.
@Embeddable은 함께 저장 삭제됨
@Entity는 cascade 속성을 통해 설정해야함

🪄 식별자 생성 기능

크게 세 가지 방식이 있다.
사용자가 직접 생성
ex) 이메일
도메인 로직으로 생성
도메인 서비스에서 생성
DB를 이용한 일련 번호 사용
DB자동 증가 칼럼 : @GenertadValue
저장할 때 식별자가 생성되므로 도메인 객체를 저장한 후에 식별자를 구할 수 있음
→ 기술의 트레이드 오프를 잘 이해하는것
→ 응답 속도를 체크해보자
리팩토링
코드 품질
성능
기존 기술의 깊은 이해
조혜온
2024. 02. 13.
답글 1개 더 보기
조혜온
2024. 02. 14.
조혜온
2024. 02. 13.
@OrderColumn 의 단점@OrderColumn 은 Parent 엔티티에서 매핑하므로, Child는 index 값을 알 수 없다. • List가 변경될 시, 많은 엔티티의 index값이 변경된다. (UPDATE 쿼리 n번 실행) • 중간에 INDEX 값이 없으면, List에는 null 이 보관된다.
답글 2개 더 보기
리스트 보기