関連関係マッピング1-関連関係マッピングタイプ

39569 ワード

ほとんどのエンティティは他のエンティティに関連付けられています.JPAでは、関連付けをエンティティにマッピングし、必要に応じてエンティティに関連付けられたエンティティを使用してオブジェクト向けにプログラミングできます.
🐢연관 관계 매핑をするときは、二つのことを覚えておいてください.太鼓の槌
1.関連関係マッピングのタイプ:一対一(1:1)、一対多(1:N)、多対一(N:1)、多対一(N:M)
2.エンティティマッピングの方向性:一方向、双方向
1対1の一方向のマッピング
カート(Cart)エンティティを作成し、既存のメンバーエンティティとの関連マッピングを設定します.
package me.jincrates.gobook.domain.carts;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import me.jincrates.gobook.domain.members.Member;

import javax.persistence.*;

@NoArgsConstructor
@Getter
@Table(name = "cart")
@Entity
public class Cart {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "cart_id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @Builder
    public Cart(Member member) {
        this.member = member;
    }
}
  • @JoinColumn:マッピングする外部キーを指定します.@JoinColumnnameが指定されていない場合は、JPAが独自に検索しますが、必要に応じて対応するカラム名を生成できない場合があります.

  • アプリケーションを実行すると、コンソールウィンドウでcartテーブルを作成するクエリー文と、外部キーを持つmember id列のクエリー文が表示されます.このような利点は、カートエンティティを参照しながらメンバーエンティティの情報を取得できることです.
    実際には、カートエンティティを表示すると、関連するメンバーエンティティがインポートされたかどうかを判断するテストコードを作成します.まず、JpaRepositoryを継承するCartRepositoryインタフェースを作成します.
    package me.jincrates.gobook.domain.carts;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface CartRepository extends JpaRepository<Cart, Long> {
    }
    package me.jincrates.gobook.domain.carts;
    
    import me.jincrates.gobook.domain.members.Member;
    import me.jincrates.gobook.domain.members.MemberRepository;
    import me.jincrates.gobook.web.dto.MemberFormDto;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.test.context.TestPropertySource;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.persistence.Entity;
    import javax.persistence.EntityManager;
    import javax.persistence.EntityNotFoundException;
    import javax.persistence.PersistenceContext;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    @SpringBootTest
    @Transactional
    @TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
    public class CartTest {
    
        @Autowired
        CartRepository cartRepository;
    
        @Autowired
        MemberRepository memberRepository;
    
        @Autowired
        PasswordEncoder passwordEncoder;
    
        @PersistenceContext
        EntityManager em;
    
        public Member createMember() {
            MemberFormDto memberFormDto = MemberFormDto.builder()
                    .email("[email protected]")
                    .name("테스트")
                    .address("서울시 강서구")
                    .password("1111")
                    .build();
    
            return Member.createMember(memberFormDto, passwordEncoder);
        }
    
        @Test
        @DisplayName("장바구니 회원 엔티티 매핑 조회 테스트")
        public void findCartAndMemberTest() {
            Member member = createMember();
            memberRepository.save(member);
    
            Cart cart = Cart.builder()
                    .member(member)
                    .build();
            cartRepository.save(cart);
    
            em.flush();
            em.clear();
    
            Cart savedCart = cartRepository.findById(cart.getId())
                    .orElseThrow(EntityNotFoundException::new);
            assertEquals(savedCart.getMember().getId(), member.getId());
        }
    
    }
  • em.flush():JPAは、データを永続コンテキストに格納し、トランザクションの終了時にflush()を呼び出してデータベースに反映します.上記では、メンバーエンティティとカートエンティティを永続性コンテキストに格納し、엔티티 매니저(em)強制呼び出しflush()からデータベースに反映します.
  • em.clear():JPAは永続性コンテキストからエンティティを問合せ、永続性コンテキストにエンティティがない場合はデータベースを問合せます.実際のデータベースからカートエンティティをインポートする場合は、永続コンテキストをクリアして、メンバーエンティティも一緒にインポートされるようにします.
  • 上でcartRepository.findById(cart.getId())コードを実行すると、cartテーブルとmemberテーブルを結合してインポートしたクエリー文が実行されます.cartエンティティを問合せながらメンバーエンティティをインポートします.
    これらのエンティティをクエリーする場合、エンティティとマッピングされたエンティティを一度にクエリーすることをインスタント・ロードと呼びます.「1対1」または「複数対1」にマッピングされている場合は、「即時ロード」をデフォルトのFetchポリシーに設定します.

    複数対1の一方向のマッピング
    1つのショッピングバスケットには複数の商品が収容できます.また、同じ商品を複数注文できるので、いくつかセットするように設定します.ショッピングカートアイテムCartItemエンティティを作成します.
    package me.jincrates.gobook.domain.carts;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import me.jincrates.gobook.domain.items.Item;
    
    import javax.persistence.*;
    
    @NoArgsConstructor
    @Getter
    @Table(name = "cart_item")
    @Entity
    public class CartItem {
    
        @Id
        @GeneratedValue
        @Column(name = "cart_item_id")
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "cart_id")
        private Cart cart;
    
        @ManyToOne
        @JoinColumn(name = "item_id")
        private Item item;
        
        private int count;
        
        @Builder
        public CartItem(Cart cart, Item item, int count) {
            this.cart = cart;
            this.item = item;
            this.count = count;
        }
    }
    エンティティにマッピングされたテーブル@JoinColumn외래키(FK)が名前に設定された値が追加されていることがわかります.どのテーブルにカラムが追加されたかが混同される場合がありますが、@JoinColumnで宣言されたエンティティにカラムを追加できます.


    マルチペア/ペアマルチ双方向マッピング
    デュアルポートマッピングには、2つの一方向マッピングが考えられる.受注と受注商品のマッピングを双方向に設定します.まず、設計は주문エンティティおよび주문 상태enumを表す.
    package me.jincrates.gobook.domain.orders;
    
    public enum OrderStatus {
        ORDER, CANCEL
    }
    package me.jincrates.gobook.domain.orders;
    
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    import me.jincrates.gobook.domain.members.Member;
    
    import javax.persistence.*;
    import java.time.LocalDateTime;
    
    @Getter @ToString
    @NoArgsConstructor
    @Table(name = "orders")
    @Entity
    public class Order {
        @Id
        @GeneratedValue
        @Column(name = "order_id")
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "member_id")
        private Member member;
    
        private LocalDateTime orderDate;    //주문일
    
        @Enumerated(EnumType.STRING)
        private OrderStatus orderStatus;    //주문상태
    }
  • @Table(name = "orders"):ソート時にキーワード「order」が使用されるため、Orderエンティティにマッピングされたテーブルとして「orders」を指定します.
  • 1人のメンバーは複数回注文できるので、注文エンティティ다대일 단방향 매핑に従います.
  • 受注エンティティはカートエンティティとほぼ同じです.まず、受注エンティティと受注エンティティの一方向マッピングを設定します.
    package me.jincrates.gobook.domain.orders;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    import me.jincrates.gobook.domain.items.Item;
    
    import javax.persistence.*;
    import java.time.LocalDateTime;
    
    @Getter @ToString
    @NoArgsConstructor
    @Table(name = "order_item")
    @Entity
    public class OrderItem {
    
        @Id
        @GeneratedValue
        @Column(name = "order_item_id")
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "item_id")
        private Item item;
    
        @ManyToOne
        @JoinColumn(name = "order_id")
        private Order order;
    
        private int orderPrice;  //주문가격
    
        private int count;  //수량
    
        @Builder
        public OrderItem(Item item, Order order, int orderPrice, int count) {
            this.item = item;
            this.order = order;
            this.orderPrice = orderPrice;
            this.count = count;
        }
    }
    1つのテーブルでは、外部キーを使用して双方向にクエリーできますが、エンティティはテーブルとは異なります.エンティティが双方向の関連付けに設定されている場合、オブジェクトの参照は2つであり、外部キーは1つであるため、誰が外部キーを管理するかを決定する必要があります.
  • 関連関係の所有者が外部キー所在地
  • に設定
  • 関連関係の所有者管理(登録、修正、削除)
  • 非所有者関連関係の所有者を関連関係マッピング時のmapperdBy属性の値
  • に設定
  • マスター以外のページは読み取り専用
  • OrderItemと関連関係マッピングをOrderエンティティに追加します.関連関係の所有者設定をよく確認してください.
    package me.jincrates.gobook.domain.orders;
    
    ///...impoert 생략
    import java.util.ArrayList;
    import java.util.List;
    
    @Getter @ToString
    @NoArgsConstructor
    @Table(name = "orders")
    @Entity
    public class Order {
        //...코드 생략
        
        @OneToMany(mappedBy = "order")
        private List<OrderItem> orderItems = new ArrayList<>();
    
        //...코드 생략
    }
  • @OneToMany(mappedBy = "order"):受注エンティティとの一対のマルチマッピング.外部キー(order id)はorder_itemテーブルにあるため、関連関係の所有者はOrderItemエンティティである.Orderエンティティは所有者ではありませんので、関連付けられた関係の所有者を「mappedBy」属性で設定してください.
  • 無条件の双方向マッピング関連関係の場合、エンティティは多数のテーブルに関連付けられ、エンティティクラス自体が複雑になるため、関連関係の一方向マッピングとして設計した後、必要に応じて双方向マッピングを追加することをお勧めします.
    マルチペアマルチマッピング-Xの使用
    多くのJPA書では多対多マッピングについても言及されているが,それらは実際の操作では使用されないマッピング関係である.複数対のマルチマッピングを使用しないのは、カラムを接続テーブルに追加できないためです.「接続」(Connection)テーブルには、通常、追加のカラムと結合カラムが必要です.また、エンティティをクエリーする場合、どのクエリー文が実行されるかを予測するのは難しいです.したがって、マルチマッピングではなく、1対のマルチマッピングに設定できます.