"김영한 강사님의 JPA 활용편 1을 듣고 필요한 부분 간단하게 정리하기."
- JPA는 트랜잭션 안에서 이뤄지기 때문에 @Transactional을 선언해준다.
- 조회 시에는 @Transactional(readOnly = true)를 설정해주는 것이 조회 성능에 있어서 장점을 가진다. 따라서, 클래스 레벨에서 @Transaction(readOnly = true)로 설정해주고 C, U, D 부분에서 @Transactional을 사용해 메서드 레벨에서 관리한다.
- 메서드 레벨의 애노테이션이 클래스 레벨의 애노테이션보다 우선순위가 높다.
@Transactional(readOnly = true) // 조회 성능에서 장점을 가진다.
@Service
public class Service {
...
@Transactional // 메서드 레벨의 애노테이션이 우선 적용된다.
public void save(Member member) {
memberRepository.save(member);
}
}
- Repository에서 @PersistContext를 사용해 EntityManager를 주입한다.
- Spring Boot는 @Autowired로도 PersistenceContext를 지원하기 때문에 생성자 주입을 통해 의존관계를 주입하는 것이 가능하다.
@RequiredArgsConstructor
class Repository {
private final EntityManager entityManager;
}
class Repository {
@PersistenceContext
private EntityManager entityManager;
}
class Repository {
@Autowired
private EntityManager entityManager;
}
class Repository {
private final EntityManager entityManager;
@Autowired
public Repository(EntityManager entityManager) {
this.entityManager = entityManager
}
}
- Order 도메인과 같이 다른 엔티티와 매핑관계가 복잡한 도메인의 경우 정적 팩토리 메소드를 이용해 생성 메서드를 작성하자!
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
// orderItemA, B, C -> persist해서 저장
// order -> persist해서 저장
// cascade 사용 시 order만 persist해도 orderItem 저장 됨
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
private LocalDateTime orderDate; // 주문 시간
@Enumerated(EnumType.STRING)
private OrderStatus status; // 주문 상태 [ORDER, CANCEL]
}
위와 같은 연관관계가 매핑된 도메인의 경우 Member, OrderItem, Delivery 엔티티와 매핑되어 있어 복잡하다.
Member, OrderItem, Delivery를 포함해 새로 생성되는 생성 메서드를 작성하자.
// 생성 메서드
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) [
Order order = new Order();
order.setMember(member);
order.setDeilivery(delivery);
for(OrderItem orderItem : orderItems) {
order.addOrerItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
위와 같이 생성 메서드를 작성하면 Order가 연관관계를 걸면서 새롭게 세팅된다.
생성하는 시점을 변경할 경우 위의 생성 메서드만 변경하면 된다는 장점이있다.
현재 진행하는 프로젝트에서 DTO에 toEntity() 메서드를 builder 패턴을 이용해 DTO → Entity 하고 있는데, DTO를 엔티티 단에 넘겨서 생성 매핑까지 하는 것이 가능
💡 엔티티에 유독 생성 메서드, 비즈니스 로직 메서드가 많은데..?
도메인이 비즈니스 로직의 주도권을 가지고 개발하는 것을 "도메인 주도 설계", "도메인 모델 패턴" 이라고 한다.
기존 서비스 레이어에서 사용하던 로직이 엔티티로 이동하고 서비스는 단순 엔티티를 호출하는 얇은 비즈니스 로직을 갖는다.
information expert pattern을 지키면서 개발이 가능하다.
💡 서비스 로직이 모든 비즈니스 로직의 주도권을 갖는 다면..?
엔티티는 getter, setter 정도만 제공하고 서비스 레이어에 비즈니스 로직이 있다. 이를 "트랜잭션 스크립트 패턴"
서비스 로직이 커지고, 엔티티는 단순히 데이터를 전달하는 역할만 담당한다.
!! 정리 !!
전자의 경우 엔티티를 객체로 사용하는 것이다.
후자의 경우 엔티티를 자료구조로 사용하는 것이다.
둘다 장단점이 있으므로 상황에 맞는 적절한 방법을 사용하는 것이 중요하다.
[참고. 클린코드 6. 객체와 자료구조]
✔️ 생성 메서드외의 엔티티 생성 메서드를 막아주기 위해서 @NoArgsContstructor(access = AccessLever.PROTECTED) 혹은 protected 기본 생성자를 만들어준다.
- cascade = all로 설정하면 연관관계의 주인을 persist할때 함께 persist되기 때문에 생성 메서드를 사용할 때 cascade = all을 설정하고 한번에 persist하자.
- cascade 사용 범위 ? Order → delivery 관리, Order → OrderItem 관리 = 라이프사이클을 동일하게 관리할 때 의미 있음, 다른 엔티티에서 참조 하는 엔티티가 없을 때 사용하면 효과를 볼 수 있다.
- Cascade는 다른 엔티티에서도 참조하거나 하는 경우가 있다면 문제가 발생할 수 있으므로 범위를 제대로 확인해야한다.
- 라이프 사이클 동일
- 다른 엔티티에서 참조하는 엔티티가없는 경우
- → 두가지 조건 충족할때 Cascade를 사용하자.
- 도메인 기반 개발을 하는 경우 도메인 내에 비즈니스 로직이 들어가 있기 때문에 엔티티에 대해 순수한 단위 테스트가 가능하다는 장점이 있다.
- API개발 시 엔티티를 절대 넘기지 않고 DTO를 이용해 넘긴다!
- 엔티티를 넘기게되면 엔티티가 변경되면 스펙 자체가 변하게 되기 때문이다.
반응형
'Backend > Spring Boot' 카테고리의 다른 글
[API 개발 고급] 지연 로딩과 조회 성능 최적화 (0) | 2022.07.07 |
---|---|
API 개발 기본 간단 정리 (0) | 2022.07.07 |
RestTemplate (0) | 2022.07.01 |
Converter를 이용해 URI에 Enum 타입 매핑하기 (0) | 2022.06.26 |
Spring Cloud Config의 설정 파일 비대칭키로 암/복호화 (0) | 2022.06.03 |