연관관계 설정이 되어있을 시에 생성 매서드 사용해 보기(엔티티 내에서)

Kim Dong Kyun·2023년 1월 27일
0

Today I learned

목록 보기
39/43

개요

김영한님의 실전! 스프링 부트와 JPA 활용1 편을 듣다가 처음 보는 내용을 발견했다. 그동안 내가 쓰던 방식과 너무 달라서 기록으로 남겨본다. 너무 신기해

내가 쓰던 방식

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Category_ID")
    private Long id;
    @Column(nullable = false)
    private String tag; // ex) 고양이 / 고양이모래 등등 ~
    @ManyToOne
    @JoinColumn(name="User_ID", nullable = false)
    private Seller seller;
    

위와 같이 Seller와 Category는 양방향 연관관계를 맺고 있고, cascadeType은 All이다.

  • 내가 알고 있던 점은 다음과 같다.
  1. 양방향 연관관계, Cascade.ALL 은 객체의 생성 주기와 삭제 주기가 완전히 부모 타입에 종속적이어야 한다.

  2. OneToMany는 저 경우에 사용 할 필요가 없었다.

  3. Category는 Seller에 완전히 종속적인데도 불구하고 본인의 repository를 가진다.

public interface CategoryRepository extends JpaRepository<Category, Long> {
    @Query("delete from Category c where c.seller.id = :sellerId")
    @Modifying(clearAutomatically = true)
    void deleteAllBySellerId(@Param("sellerId") Long sellerId);
}

위와 같이. 그리고 이것을 어디서 쓸까? 바로 Seller를 지울 때 쓴다.

    @Transactional
    public MessageResponseDto deleteSeller(User user) {
        Seller seller = sellerRepository.findByUsername(user.getUsername()).orElseThrow(
                () -> new IllegalArgumentException("그런 사람 없습니다")
        );
        // 쿼리가 긴 이유 -> 조인해서 날라가니까. left outer join 입니다.
        orderRepository.deleteAllByStoreName(seller.getStoreName());
        categoryRepository.deleteAllBySellerId(seller.getId());
        productRepository.deleteAllByUsername(seller.getUsername());
        // 위에놈들은 내가 직접 쿼리짜서 하나만 날라가지만
        sellerRepository.delete(seller);
        // 이놈은 JPA 에서 상속 구현할 때의 한계. 자식 객체 조회/삭제등에 쿼리 두번씩 날라감.(큰 단점은 아니라고 하긴 함~)
        return new MessageResponseDto("삭제 완료");
    }

내가 봐도 너무 더럽다. 셀러 하나 삭제하는데 이렇게까지 해야하나?


갓영한님의 방식

  1. 연관관계 설정
@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    @Id
    @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @JsonIgnore // 이거 왜 썼을까?
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    @JsonIgnore
    @OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;
    }

위와 같이 member, delivery, orderItems 등에 연관관계를 맺고 있다. 내가 썼던 방식대로 모든 친구들에게 repository를 넣어줄까? 아니다! (MemberRepository는 꼭 필요하므로 쓰시긴 한다.)

갓영한님은 다음과 같이 "생성 매서드"를 사용하셨다.


    //==생성 메서드==//
    public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);
        for (OrderItem orderItem : orderItems) {
            order.addOrderItem(orderItem);
        }
        order.setStatus(OrderStatus.ORDER);
        order.setOrderDate(LocalDateTime.now());
        return order;
    }

위에서 보는 바와 같이, Order라는 객체를 설정 해 줄 때 member, deliver, orderitems 등등을 모두 한번에 설정 할 수 있게 해뒀다. 이것은 어떻게 쓰이느냐? 바로 다음과 같이 쓰인다.

    @Transactional
    public Long order(Long memberId, Long itemId, int count) {

        //엔티티 조회
        Member member = memberRepository.findOne(memberId);
        Item item = itemRepository.findOne(itemId);

        //배송정보 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());
        delivery.setStatus(DeliveryStatus.READY);

        //주문상품 생성
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

        //주문 생성
        Order order = Order.createOrder(member, delivery, orderItem);

        //주문 저장
        orderRepository.save(order);

        return order.getId();
    }

위와 같이 order 객체를 생성 할 때 save를 통한 persist 호출은 "단 한번만 일어난다". 진짜 기똥차고 기가 맥히다...너무 좋아


배운 점

  1. 연관관계 설정 시에 "연관관계 편의 매서드" 나, 생성 매서드 등등의 것들은 엔티티 내에서 설정되어서 사용되면 매우 깔끔해진다.

  2. 위와 같이 order가 가지는 책임을 정확히 분리하면 코드가 너무너무 편해지고 깔끔해진다. 생성 -> 수정 -> 삭제 모두 편해진다.

  3. 반성 할 점은, 내가 JPA를 계속 써오면서 너무 테이블 관점으로 생각했다는 것이다. JPA는 ORM이고, 자바 엔티티를 테이블로 최대한 바꿔주는 역할을 한다. 자바 객체를 어떻게 잘 만지느냐도 굉장히 굉장히 중요할 듯 하다.

0개의 댓글