🍊

JPAの@ManyToManyとは

2022/05/15に公開

1.多対多


関係データベースは正規化されたテーブル二つで多対多関係を表すことができない。

  • 多対多関係を表す為には連想テーブル(中間テーブル)が必要である。


反面オブジェクトはコレクションを利用してオブジェクト二つで多対多関係ができる。

2.@ManyToManyとは

@ManyToManyとはRDBとオブジェクト間のパラダイムの不一致を解消し簡単にマッピングしてくれるアノテーション。

2.1 使用方法

  • @ManyToMany使用
  • @JoinTableで連想テーブル指定
  • 多対多マッピング : 片方向、双方向可能。

2.1.1 片方向

  1. @ManyToManyのみ指定した時
Member.java
@Entity @Getter
@Table(name = "MEMBER")
public class Member {
	...
  @ManyToMany
  private List<Product> products = new ArrayList<>();
	...
}

自動的に連想テーブル(中間テーブル)を作ってくれる。

  • テーブル名やカラム名も任意に設定してくれる。
  • FKも設定してくれる。
# 自動生成されたDDL文
create table MEMBER_PRODUCT (
	Member_MEMBER_ID bigint not null,
	products_PRODUCT_ID bigint not null
) engine=InnoDB

alter table MEMBER_PRODUCT 
add constraint FKeenqm751wrk56y5nn62daim8f 
foreign key (products_PRODUCT_ID) 
references PRODUCT (PRODUCT_ID)
    
alter table MEMBER_PRODUCT 
add constraint FKf8wyhskc9dlf0xlrsodwxa7wf 
foreign key (Member_MEMBER_ID) 
references MEMBER (MEMBER_ID)
  • この二つのエンティティに値を入れてみると
select MEMBER_ID, USERNAME, PRODUCT_ID, NAME
from MEMBER M
inner join MEMBER_PRODUCT MP 
	on M.MEMBER_ID = MP.Member_MEMBER_ID
inner join PRODUCT P 
	on MP.products_PRODUCT_ID = P.PRODUCT_ID;

ちゃんと入っていることを確認できる。

  1. @JoinTalbleと一緒に使用
    連組テーブル名やカラム名を指定する事できる。
Member.java
@ManyToMany
@JoinTable(
    name = "MEMBER_PRODUCT",
    joinColumns = @JoinColumn(name = "MEMBER_ID"),
    inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")
)
@Setter
private List<Product> products = new ArrayList<>();

2.1.2 双方向

Product@ManyToMany(mappedBy = “products”)を追加

@Entity @Getter
@Table(name = "PRODUCT")
public class Product {

    @Id @GeneratedValue(strategy = IDENTITY)
    @Column(name = "PRODUCT_ID")
    private Long id;
    @Column(name = "NAME", length = 50)
    @Setter
    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();

}

3.@ManyToManyの限界

本当に楽に見えるが、実務で使用してはならない、連想テーブルは単純に連結だけで終わらない。下の関係を例にするとオーダータイムやオーダー量みたいなデータが追加される可能性が高い。

だが、@ManyToManyの連組テーブルは隠れているためカラムの追加など変化に対応できない。又、どんなクエリが出るか予想できない。

4.まとめ

RDBと同じく連組テーブルを中間に置いて解決する方法を使用
連組テーブルをのエンティティを桁上げし@OneToMany, @ManyToOne関係で解決する

4.1 エンティティを桁上げ、片方向の例

Member.java
@Entity
@Getter
@Table(name = "MEMBER")
public class Member {

  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Column(name = "MEMBER_ID")
  private Long id;
  @Column(name = "USERNAME", length = 50)
  @Setter
  private String username;

  @Setter
  @OneToMany(mappedBy = "member")
  private List<MemberProduct> memberProducts = 
										new ArrayList<>();
}
MemberProduct.java
@Entity
@Table(name = "MEMBER_PRODUCT")
public class MemberProduct {

  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Column(name = "MEMBER_PRODUCT_ID")
  private Long id;

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

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "PRODUCT_ID")
  private Product product;

  @Column(name = "ORDERAMOUNT")
  private int orderAmount;

  @Column(name = "ORDERDATE")
  private LocalDateTime orderDate;
}
Product.java
@Entity
@Getter
@Table(name = "PRODUCT")
public class Product {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Column(name = "PRODUCT_ID")
  private Long id;
  @Column(name = "NAME", length = 50)
  @Setter
  private String name;

	// 片方向なのでProductには何もない。
}

5. JPAの自己結合

おまけ、JPAの自己結合

Category.java
@Entity
public class Category {
	...
	@OneToMany(mappedBy = "parent")
	private List<Category> child = new ArrayList<>();

	@ManyToOne
	@JoinColumn(name = "PARENT_ID")
	private Category parent;
	...
}
  • CATEGORY

次(@MappedSuperclass)

https://zenn.dev/dev_yoon/articles/37c3469c91dbdd

Discussion