728x90
모듈 위치
// 인터페이스는 도메인 하위에 위치
domain/
└── repository/
└── UserRepository.java
// 구현체는 인프라스트럭처 하위에 위치
infrastructure/
├── repository/
└── JpaUserRepository.java
- JPA 를 사용하면 수정한 결과를 반영하는 메서드를 따로 추가하지 않아도 자동으로 DB 에 반영한다 ( 변경 감지 )
- 스프링 데이터 JPA 예제
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private int age;
}
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
public class UserService {
@Autowired
private UserRepository userRepository;
public void changeName(User user, String newName) {
user.setName(newName);
userRepository.save(user);
}
}
- 애그리거트(엔티티) 와 밸류 기본 매핑
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
private String orderNumber;
private List<OrderItem> orderItems; // 밸류 타입
}
@Embeddable // 밸류 타입 정의
public class OrderItem {
private String itemName;
private int quantity;
private int price;
}
- JPA에서 @Entity와 @Embeddable 사용 규칙
- @Entity 어노테이션은 엔터티 클래스에만 사용
- 엔터티 클래스는 기본 생성자 필수
- 엔터티 클래스는 ID 속성을 포함해야 한다.
- ID 속성은 @Id 어노테이션으로 표시 필수
- ID 속성은 JPA에서 지원하는 데이터 타입을 사용
- AttributeConverter : 밸류 타입과 칼럼 데이터 간의 변환을 처리하기 위한 기능 정의
ex ) DB 에 어떤 값을 저장할 때 전/후처리가 필요한 요구가 있을 경우
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@Convert(converter = GenderConverter.class) // 사용
private Gender gender;
}
public class GenderConverter implements AttributeConverter<Gender, String> {
// Java 객체 속성을 데이터베이스에 저장할 때 사용
@Override
public String convertToDatabaseColumn(Gender gender) {
if (gender == Gender.WOMAN) {
return "w";
} else if (gender == Gender.MAN) {
return "m";
} else {
throw new IllegalArgumentException("Unknown gender: " + gender);
}
}
// 데이터베이스에서 읽은 값을 Java 객체 속성으로 변환할 때 사용
@Override
public Gender convertToEntityAttribute(String dbData) {
if ("w".equals(dbData)) {
return Gender.WOMAN;
} else if ("m".equals(dbData)) {
return Gender.MAN;
} else {
throw new IllegalArgumentException("Unknown gender code: " + dbData);
}
}
}
public enum Gender {
WOMAN,
MAN
}
- 독자적인 라이플 사이클을 갖지 않고 완전히 하나의 엔티티에 의존적일 경우 cascade 와 orphanRemoval 옵션을 사용한다.
- orphanRemoval 속성을 true로 설정하면 Product 엔터티를 삭제할 때 관련된 Image 엔터티도 자동으로 삭제된다.
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images;
}
@Entity
public class Image {
@Id
@GeneratedValue
private Long id;
private String filename;
@ManyToOne
private Product product;
}
- 애그리거트의 로딩 전략
- JPA 매핑을 설정할 때는 애그리거트에 속한 객체가 모두 모여야 완전한 하나가 된다.
- 즉시로딩 예제
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders;
}
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
private String product;
@ManyToOne
private Member member;
}
--
// 조회시 N+1 문제가 발생할 수 있다.
Member member = em.find(Member.class, 1L);
// Member 엔터티를 조회하면 Order 엔터티도 모두 함께 조회
// N + 1 쿼리 발생: 1개의 Member 쿼리 + N개의 Order 쿼리
List<Order> orders = member.getOrders();
for (Order order : orders) {
System.out.println(order.getProduct());
}
- 지연로딩 예제
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
}
--
Member member = em.find(Member.class, 1L);
// Member 엔터티를 조회해도 Team 엔터티는 아직 조회되지 않는다.
System.out.println(member.getName());
// Team 엔터티를 실제로 사용할 때 지연 로딩으로 조회된다.
// 1개의 SELECT 쿼리만 발생
System.out.println(member.getTeam().getName());
728x90
'Books > 도메인 주도 개발 시작하기' 카테고리의 다른 글
Chapter6. 응용 서비스와 표현 영역 (0) | 2024.04.08 |
---|---|
Chapter5. 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2024.04.08 |
Chapter3. 애그리거트 (0) | 2024.04.07 |
Chapter2. 아키텍처 개요 (0) | 2024.04.05 |
Chapter1. 도메인 모델 시작하기 (0) | 2024.04.05 |
댓글