目的
一対多で関連付けられたテーブルを扱うための手順を、整理する
SQLなら瞬殺のことが、ぜんぜんわからず、
JPAの扱いでもがき苦しんだため、
とりあえずやっと動いた最小のモデルをまとめる
(ORマッパー難しい。泣ける。危うく発狂するところだった。)
環境
STS
Java8
Spring Boot
Hibernate
構造
以下のようなデータモデルを作りたい
元(単一)
t_mitsumori_hdrテーブル
HeaderEntity ドメイン
関連テーブル(多)
t_mitsumori_dtlテーブル
MitsumoriDtl ドメイン
手順
ドメインクラス
ソース例
inport等略されているためこのままで動作するコードではありません、要点だけご確認ください
- 1側
package com.example.domain;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_mitsumori_hdr")
@ToString(exclude = "mitsumoriDtl")
public class HeaderEntity {
@Id
@GeneratedValue
private Integer t_mitsumori_hdr_seq;
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "headerEntity")
private List<MitsumoriDtl> mitsumoriDtl;
}
説明
Lambok関連の定義
@Data
@NoArgsConstructor
@AllArgsConstructorドメインクラスの定義
@Entity
@Table(name = "t_mitsumori_hdr")
@ToString(exclude = "mitsumoriDtl")
Tableで紐づくテーブル名を指定
ToStringは循環参照エラーを防ぐため、多側のドメインクラスを指定している。
このドメインクラスは、元ドメインクラスのプロパティとして、Listに格納される想定
@Id
@GeneratedValue
private Integer t_mitsumori_hdr_seq;
IdとGeneratedValueでこのプロパティが主キーであること、
値はJPAが生成することを指定
自動生成の動きとしては、
保存のためPOST時の段階では、主キー項目はnullの状態でコントローラに送り、.saveしてしまってよい
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "headerEntity")
private List<MitsumoriDtl> mitsumoriDtl;
featchにFetchType.EAGERを指定
ここで、データをDBから取得するとき、関連テーブルの方も一緒に取り出すかを指定している
Lazyの場合、取得しない
EAGERの場合、元を検索すると関連テーブルも格納されて取得される、関連テーブルは配列で取得されtくる
→正確には、Lazyは遅延ロードが行われている。
元ドメインが取得selectされたタイミングでは、関連テーブルはまだ読み込まれない
そこから、関連テーブルがアクセスされたタイミングでselectが関連関連テーブルに走る
mappedByに、関連先のドメイン(mitsumoriDtl)で指定されている元テーブルのプロパティ名を指定
まとめると
@OneToMany(cascade = 元が消えたら関連テーブルはどうするか, fetch = 一緒に取り出すか, mappedBy = 関連ドメインクラス)
余談)この辺で、[どっちがどっちだ!?] でパニクる
- 多側
package com.example.domain;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_mitsumori_dtl")
public class MitsumoriDtl {
@Id
@GeneratedValue
private Integer t_mitsumori_dtl_seq;
Integer t_mitsumori_hdr_seq; //見積HDRSEQ
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(nullable = false,insertable=false, updatable=false, name = "t_mitsumori_hdr_seq")
private HeaderEntity headerEntity;
}
説明
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "t_mitsumori_dtl")
同じ↑
Table nameでテーブルとこのドメインクラスを紐づける
@Id
@GeneratedValue
private Integer t_mitsumori_dtl_seq;
Integer t_mitsumori_hdr_seq; //元テーブルのキーにする
このドメインクラスの主キーと
元テーブルを特定するためのキーを保持するプロパティを用意しておく
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(nullable = false,insertable=false, updatable=false, name = "t_mitsumori_hdr_seq")
private HeaderEntity headerEntity;
nameで元テーブルを特定するために、このドメインクラスで持っているプロパティを指定
(ここで指定したプロパティの値を元に、元テーブルへの検索がかかる)
利用
ソース例
HeaderEntity entH = mitsumoriHdrService.findOne(id);
説明
mitsumoriHdrServiceは、リポジトリを操作する@Serviceクラスで、リポジトリクラスに検索を行っている
idに指定した値で検索をかけるが、このとき、ドメイン側の主キーに対して検索を行う
ドメインクラスでは、HeaderEntity(元クラス)のキーは以下だった
@Id
@GeneratedValue
private Integer t_mitsumori_hdr_seq;
ため、t_mitsumori_hdr_seqに対して、idの値で検索がかかる
多側の関連テーブルは、この検索時に同時に検索される
元ドメインに、getterすると
entH.getMitsumoriDtl()
[元ドメインのオブジェクト.get関連テーブルの入るプロパティ名();]
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "headerEntity")
private List<MitsumoriDtl> mitsumoriDtl;
に従って、
mitsumoriDtlがListクラスで取得されてくる。
HeaderEntity entH = mitsumoriHdrService.findOne(id);
System.out.println("entH" + entH);
System.out.println("entM" + entH.getMitsumoriDtl());
出力結果
entHHeaderEntity(t_mitsumori_hdr_seq=1, mitsumori_hdr_no=1, mitsumori_ymd=2015-01-01 00:00:00.0, kokyaku_kaisha_mei=123, hakko_kaisha_mei=123, hakko_kaisha_yubin_no=123, hakko_kaisha_todofuken=123, hakko_kaisha_jusho=123, hakko_kaisha_tel=123, hakko_kaisha_fax=123, anken_mei=123, nounyu_basho=123, nounyu_kigen=123, torihiki_joken=123, mitsumori_kigen=123, mitsumori_shokei_zeinuki=123, mitsumori_nebiki_gaku=123, mitsumori_goukei_zeinuki=123, shouhizei_ritsu=123.0, shouhizei_gaku=123, mitsumori_goukei_zeikomi=123, kenshu_ymd=2015-01-01 00:00:00.0, shiharai_ymd=2015-01-01 00:00:00.0, tokki_jiko=123, sys_create_user=123, sys_create_ymdhm=2015-01-01 00:00:00.0, sys_create_class=123, sys_update_user=123, sys_update_ymdhm=2015-01-01 00:00:00.0, sys_update_class=123, sys_delete_user=123, sys_delete_ymdhm=2015-01-01 00:00:00.0, sys_delete_class=123, sys_delete_no=123, sys_version=1)
entM[MitsumoriDtl(t_mitsumori_dtl_seq=1, mitsumori_hdr_no=123, mitsumori_dtl_no=123, mitsumori_dtl_mei=123, mitsumori_dtl_suryo=123.0, mitsumori_dtl_suryo_tani=123, mitsumori_dtl_tanka=123, mitsumori_dtl_gaku=123, mitsumori_dtl_biko=123, t_mitsumori_hdr_seq=1, sys_create_user=123, sys_create_ymdhm=2015-01-01 00:00:00.0, sys_create_class=123, sys_update_user=123, sys_update_ymdhm=2015-01-01 00:00:00.0, sys_update_class=123, sys_delete_user=123, sys_delete_ymdhm=2015-01-01 00:00:00.0, sys_delete_class=123, sys_delete_no=123, sys_version=1, headerEntity=HeaderEntity(t_mitsumori_hdr_seq=1, mitsumori_hdr_no=1, mitsumori_ymd=2015-01-01 00:00:00.0, kokyaku_kaisha_mei=123, hakko_kaisha_mei=123, hakko_kaisha_yubin_no=123, hakko_kaisha_todofuken=123, hakko_kaisha_jusho=123, hakko_kaisha_tel=123, hakko_kaisha_fax=123, anken_mei=123, nounyu_basho=123, nounyu_kigen=123, torihiki_joken=123, mitsumori_kigen=123, mitsumori_shokei_zeinuki=123, mitsumori_nebiki_gaku=123, mitsumori_goukei_zeinuki=123, shouhizei_ritsu=123.0, shouhizei_gaku=123, mitsumori_goukei_zeikomi=123, kenshu_ymd=2015-01-01 00:00:00.0, shiharai_ymd=2015-01-01 00:00:00.0, tokki_jiko=123, sys_create_user=123, sys_create_ymdhm=2015-01-01 00:00:00.0, sys_create_class=123, sys_update_user=123, sys_update_ymdhm=2015-01-01 00:00:00.0, sys_update_class=123, sys_delete_user=123, sys_delete_ymdhm=2015-01-01 00:00:00.0, sys_delete_class=123, sys_delete_no=123, sys_version=1))]
HeaderEntityが取得されてくる際
MitsumoriDto(関連テーブルが、配列で取得されてきている)
永続化
元クラスへsaveを実行すると、関連テーブル含めて永続化が行われる