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

Chapter4. 리포지터리와 모델 구현

by YoonJong 2024. 4. 8.
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

댓글