63
Beginning Java EE 6 勉勉勉 (2) -JPA- 担担担@kjstylepp Kinji Akemine 2012/04/25

Beginning Java EE 6 勉強会(2) #bje_study

Embed Size (px)

Citation preview

Page 1: Beginning Java EE 6 勉強会(2) #bje_study

Beginning Java EE 6 勉強会 (2)-JPA-

担当者: @kjstyleppKinji Akemine

2012/04/25

Page 2: Beginning Java EE 6 勉強会(2) #bje_study

2

目次

04/12/2023 Beginning JavaEE6 勉強会 (2)

3. オブジェクト・リレーショナル・マッピング

4. 永続オブジェクトの管理

5. コールバックとリスナ

Page 3: Beginning Java EE 6 勉強会(2) #bje_study

3

本論に入る前に質問

• 前回の勉強会で、代表的な ORM ソリューションが列挙されていましたが、全部言えますか?

04/12/2023 Beginning JavaEE6 勉強会 (2)

ggrks

@making

Page 4: Beginning Java EE 6 勉強会(2) #bje_study

04/12/2023 4

第 3 章オブジェクト・リレーショナル・マッピング

Beginning JavaEE6 勉強会 (2)

Page 5: Beginning Java EE 6 勉強会(2) #bje_study

5

3. オブジェクト・リレーショナル・マッピング

• なぜ ORM ?– Object と RDB はパラダイムが違う

• リレーション– Object :参照、ポインタなどで表現– RDB :外部キーで表現

• 継承– RDB にはない概念

…etc

– このへんの差をうまく吸収してあげる仕組みをORM が提供する

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 6: Beginning Java EE 6 勉強会(2) #bje_study

6

3.1 エンティティのマッピング方法

• もっとも単純なマッピング

04/12/2023 Beginning JavaEE6 勉強会 (2)

import javax.persistence.*;

@Entitypublic class Book { @Id private Long id; private String title; private Float price; private String description; private String isbn; private Integer nbOfPage; private Boolean illustrations;}

JPA 関係をまるっとインポート@Entity アノテーション

で永続クラスとして宣言

@Id アノテーションで一意識別子(=主キー)

として id を宣言

Page 7: Beginning Java EE 6 勉強会(2) #bje_study

7

3.1.1 Configuration-by-Exception

• 設定より規約– クラス名→テーブル名

• Book クラス→ BOOK テーブル• 名前を変えたい場合は @Table アノテーションを利用

– 属性名→カラム名• id 属性→ ID カラム• 名前を変えたい場合は @Column アノテーションを利用

– 型マッピング規則は、基本規則は決まっているが、細かい部分は JDBC や RDBMS によって違う

– 基盤のデータベース情報は persistence.xml に記述

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 8: Beginning Java EE 6 勉強会(2) #bje_study

8

3.2.1 テーブル (1) @Table

• マッピング先のテーブルを規約から外したい場合に利用

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@Table(name = "t_book")public class Book { @Id private Long id; private String title; private Float price; private String description; private String isbn; private Integer nbOfPage; private Boolean illustrations;}

「 T_BOOK 」テーブルにマッピング

※ 規約では「 BOOK 」テーブル

Page 9: Beginning Java EE 6 勉強会(2) #bje_study

9

3.2.1 テーブル (2) @SecondaryTable• 1 つのエンティティの属性を 2 つ以上のテー

ブルにマッピング

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@SecondaryTables({ @SecondaryTable(name = "city"), @SecondaryTable(name = "country")})public class Address { @Id private Long id; private String street1; private String street2; @Column(table = "city") private String city; @Column(table = "city") private String state; @Column(table = "city") private String zipcode; @Column(table = "country") private String country;}

2 つ以上の二次テーブルを使う場合は@SecondaryTables アノテーション

二次テーブルとして「 CITY 」「 COUNTRY 」を利用

STATE カラムは CITY テーブルに作成

※ 規約では ADDRESS テーブル

Page 10: Beginning Java EE 6 勉強会(2) #bje_study

10

3.2.1 テーブル (2) @SecondaryTable• マッピングのされ方

04/12/2023 Beginning JavaEE6 勉強会 (2)

<< エンティティ >>Address

-id:Long-street1:String-street2:String-city:String-state:String-zipcode:String-country:String

COUNTRY

+#IDCOUNTRY

bigintvarchar(255)

ADDRESS

+#IDSTREET1STREET2

bigintvarchar(255)varchar(255)

CITY

+#IDCITYSTATEZIPCODE

bigintvarchar(255)varchar(255)varchar(255)

主キーが共通

Page 11: Beginning Java EE 6 勉強会(2) #bje_study

11

3.2.2 主キー (1) @Id, @GeneratedValue

• 主キーとしたい属性には @Id アノテーション

• 主キーの値の生成方法を @GeneratedValueアノテーションで指定

04/12/2023 Beginning JavaEE6 勉強会 (2)

@GeneratedValue (strategy = ***)

振る舞い

GenerationType.SEQUENCE

シーケンス番号を利用する

GenerationType.IDENTITY ID 列の値を利用する

GenerationType.TABLE 主キー用のテーブルを作成して利用する

GenerationType.AUTO RDBMS に最適の方法でよろしくする普通はこれで OK

Page 12: Beginning Java EE 6 勉強会(2) #bje_study

12

3.2.2 主キー (2) 複合主キー

• 2 つの実現方法

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Embeddablepublic class NewsId {

private String title;private String language;

}

@Entitypublic class News {

@EmbeddedIdprivate NewsId id;

private String content;}

public class NewsId {private String title;private String language;

}

@Entity@IdClass(NewsId.class)public class News {

@Idprivate String title;@Idprivate String language;private String content;

}

複合主キークラスに@Embeddable アノテーショ

複合主キークラスの属性に@EmbeddedId アノテーショ

複合主キークラスには特別な宣言はなし

複合主キークラスの属性を再度@Id アノテーションとともに宣

言特別な理由がなければこっち

Page 13: Beginning Java EE 6 勉強会(2) #bje_study

13

3.2.3 属性 (1) @Basic

• 属性とカラムのマッピング方法の基本的なオプション設定を行う– fetch = FetchType.LAZY

• サイズが大きい場合などに利用し、遅延取得する• 属性の getter メソッドが呼ばれたときに取得

– optional = false• persist 時などに、属性が null の場合にはじく

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 14: Beginning Java EE 6 勉強会(2) #bje_study

14

3.2.3 属性 (2) @Column

• 属性のマッピング先のカラムに関する様々な設定を行う

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Book16 { @Id @GeneratedValue private Long id; @Column(name = "book_title", nullable = false, updatable = false) private String title; private Float price; @Column(length = 2000) private String description; private String isbn; @Column(name = "nb_of_page", nullable = false) private Integer nbOfPage; private Boolean illustrations;}

本のタイトルは必須項目、かつ、更新されることはな

本のページ数は必須項目

@Basic(optional = false) と違い、@Column(nullable = false) はスキーマ作成時のみ有

本の説明文は 2000 文字※ 規約では 255 文字

Page 15: Beginning Java EE 6 勉強会(2) #bje_study

15

3.2.3 属性 (3) @Temporal, @Transient

• Date 型属性のマッピング

• 永続化したくない属性– @Transient アノテーションを付加することで永続

化対象から除外

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Temporal(***) 振る舞い

TemporalType.DATE 年月日のみ扱う

TemporalType.TIME 時分秒のみ扱う

TemporalType.TIMESTAMP 年月日と時分秒の両方を扱う

Page 16: Beginning Java EE 6 勉強会(2) #bje_study

16

3.2.4 アクセスタイプ

• 属性アノテーションは getter メソッドに対しても宣言可能

04/12/2023 Beginning JavaEE6 勉強会 (2)

属性に付加されたアノテーションをもとにラッピング@Entity

@Access(AccessType.FIELD)public class Customer {

@Column(length = 15)private String phoneNumber;private String email;

@Access(AccessType.PROPERTY)@Column(length = 555)public String getPhoneNumber() {

return phoneNumber;}

@Column(length = 555)public String getEmail() {

return email;}

}

@Access アノテーションが

上書きされているので555 文字で定義

上書きされていないので255 文字で定義

Page 17: Beginning Java EE 6 勉強会(2) #bje_study

17

3.2.5 基本型のコレクション

• リレーションを意識せずマッピング可能

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Book {

@Idprivate Long id;@ElementCollection(fetch = FetchType.LAZY)@CollectionTable(name = "Tag")@Column(name = "Value")private List<String> tags = new ArrayList<String>();

}

tags を「 TAG 」テーブルにマッピング

※ 規約では「 BOOK_TAGS 」

BOOK

+ID bigint

TAG

#BOOK_IDVALUE

bigintvarchar(255)

tags の要素を「 VALUE 」カラムにマッピング

※ 規約では「 TAGS 」

Page 18: Beginning Java EE 6 勉強会(2) #bje_study

18

3.2.6 基本型のマッピング

• Map 構造も用意にマッピング可能

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class CD {

@Idprivate Long id;@ElementCollection@MapKeyColumn(name = "position")private Map<Integer, String> tracks =

new HashMap<Integer, String>();}

key 要素を「 POSITION 」カラムにマッピング

※ 規約では「 CD_TRACKS_KEY 」

CD

+ID bigint

CD_TRACKS

#CD_IDPOSITIONTRACKS

bigintintegervarchar(255)

Page 19: Beginning Java EE 6 勉強会(2) #bje_study

19

3.3 XML によるマッピング

• アノテーションと同等の設定が可能– 普通はアノテーション– 解釈は XML のほうが優先される

04/12/2023 Beginning JavaEE6 勉強会 (2)

ggrks

@making

Page 20: Beginning Java EE 6 勉強会(2) #bje_study

20

3.4 組み込み可能オブジェクト

• コンポジション構造を 1 つのテーブルにマッピング

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Embeddablepublic class Address {

private String street1;private String street2;

}

@Entitypublic class Customer {

@Idprivate Long id;@Embeddedprivate Address address;

}

複合主キーで使ったやつ

CUSTOMER

+#IDSTREET1STREET2

bigintvarchar(255)varchar(255)

Page 21: Beginning Java EE 6 勉強会(2) #bje_study

21

3.5 リレーションシップ・マッピング

• 複雑なオブジェクト構造をデータベースのリレーションとしてマッピング

04/12/2023 Beginning JavaEE6 勉強会 (2)

しっかりやれよ

@making

Page 22: Beginning Java EE 6 勉強会(2) #bje_study

22

3.5.1 RDB におけるリレーションシップ

• カラム結合

• テーブル結合

04/12/2023 Beginning JavaEE6 勉強会 (2)

主キー

名前 外部キー

1 鈴木 11

2 佐藤 12

3 田中 13

主キー

都市

11 千葉!

12 滋賀!

13 佐賀!

主キー

名前

1 鈴木

2 佐藤

3 田中

主キー

都市

11 千葉!

12 滋賀!

13 佐賀!

顧客 住所

1 11

2 12

3 13

パフォーマンスを考えてなるべくこっち

Page 23: Beginning Java EE 6 勉強会(2) #bje_study

23

3.5.2 エンティティのリレーションシップ (1)

• カーディナリティと方向の組み合わせ

04/12/2023 Beginning JavaEE6 勉強会 (2)

カーディナリティ 方向

1 対 1 一方向

1 対 1 双方向

1 対 多 一方向

1 対 多 / 多 対 1 双方向

多 対 1 一方向

多 対 多 一方向

多 対 多 双方向

以下の 3種類1 対 11 対 多多 対 多

以下の 2種類一方向双方向

一般的に用いられる、赤枠の 3 つのパターンについて

マッピングの仕組みを詳細に説明

Page 24: Beginning Java EE 6 勉強会(2) #bje_study

24

3.5.2 エンティティのリレーションシップ (2)@OneToOne 、一方向

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Address {

@Idprivate String street1;private String street2;

}

@Entitypublic class Customer {

@Idprivate Long id;@OneToOne@JoinColumn(name = "add_fk", nullable = false)private Address address;

}

外部キーのカラム名は「 ADD_FK 」

※ 規約では「 ADDRESS_ID 」

ADDRESS

+IDSTREET1STREET2

bigintvarchar(255)varchar(255)

CUSTOMER

+ID+#ADD_FK

bigintbigint

Page 25: Beginning Java EE 6 勉強会(2) #bje_study

25

3.5.2 エンティティのリレーションシップ (3)@OneToMany 、一方向

04/12/2023 Beginning JavaEE6 勉強会 (2)

• デフォルトではテーブル結合

@Entitypublic class Order {

@Idprivate Long id;@OneToMany@JoinTable(name = "jnd_ord_line",

joinColumns = @JoinColumn(name = "order_fk"),inverseJoinColumns = @JoinColumn(name = "line_fk"))

private List<OrderLine> orderLines;}

@Entitypublic class OrderLine {

@Idprivate Long id;private String item;private Integer quantity;

}

ORDER

+ID bigint

ORDERLINE

+IDITEMQUANTITY

bigintvarchar(255)integer

JND_ORD_LINE

+#ORDER_FK+#LINE_FK

bigintbigint

結合テーブル名は「 JND_ORD_LINE 」※ 規約では「 ORDER_ORDERLINE 」

所有側の外部キーは「 ORDER_FK 」※ 規約では「 ORDER_ID 」

被所有側の外部キーは「 LINE_FK 」

※ 規約では「 ORDERLINE_ID 」

Page 26: Beginning Java EE 6 勉強会(2) #bje_study

26

3.5.2 エンティティのリレーションシップ (3)@OneToMany 、一方向

04/12/2023 Beginning JavaEE6 勉強会 (2)

• カラム結合に変更可能@Entitypublic class OrderLine {

@Idprivate Long id;private String item;private Integer quantity;

}

ORDER

+ID bigint

ORDERLINE

+IDITEMQUANTITY#ORDER_FK

bigintvarchar(255)integerbigint

@Entitypublic class Order {

@Idprivate Long id;@OneToMany@JoinColumn(name = "order_fk")private List<OrderLine> orderLines;

}

外部キーは所有側に宣言し、キー名は「 ORDER_FK 」※ 規約では「 ORDER_ID 」

こっちは変更なし

Page 27: Beginning Java EE 6 勉強会(2) #bje_study

27

3.5.2 エンティティのリレーションシップ (4)@ManyToMany 、双方向

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class CD {

@Idprivate Long id;@ManyToMany(mappedBy = "appearsOnCDs")private List<Artist> artists;

}

@Entitypublic class Artist {

@Idprivate Long id;@ManyToMany@JoinTable(name = "jnd_art_cd",

joinColumns = @JoinColumn(name = "artist_fk"),inverseJoinColumns = @JoinColumn(name = "cd_fk"))

private List<CD> = appearsOnCDs;}

結合テーブル名は「 JND_ART_CD 」※ 規約では「 ARTIST_CD 」

所有側の外部キーは「 ARTIST_FK 」

※ 規約では「 ARTIST_ID 」

被所有側の外部キーは「 CD_FK 」※ 規約では「 CD_ID 」

ARTIST

+ID bigint

CD

+ID bigintJND_ART_CD

+#ARTIST_FK+#CD_FK

bigintbigint

指定しないと「 CD_ARTIST 」もできてしまう

Page 28: Beginning Java EE 6 勉強会(2) #bje_study

28

3.5.3 リレーションシップのフェッチ

• オブジェクト、データベースへのアクセス頻度を考慮して適切に設定する必要あり

• デフォルトのフェッチ方式

04/12/2023 Beginning JavaEE6 勉強会 (2)

カーディナリティ

フェッチ方式

@OneToOne EAGER

@ManyToOne EAGER

@OneToMany LAZY

@ManyToMany LAZY

Page 29: Beginning Java EE 6 勉強会(2) #bje_study

29

3.5.4 リレーションシップの順序 (1) @OrderBy

• 属性名に対して ASC または DESC で指定

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class News {

@Idprivate Long id;private String content;@OneToMany(fetch = FetchType.EAGER)@OrderBy("postedDate DESC, content ASC")private List<Comment> comments;

}

@Entitypublic class Comment {

@Idprivate Long id;private String content;@Column(name = "posted_date")@Temporal(TemporalType.TIMESTAMP)private Date postedDate;

}

カラム名ではなく属性名で指定

Page 30: Beginning Java EE 6 勉強会(2) #bje_study

30

3.5.4 リレーションシップの順序 (2) @OrderColumn

• 索引カラムを追加することで順序を維持

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Comment {

@Idprivate Long id;private String content;

}

@Entitypublic class News {

@Idprivate Long id;private String content;@OneToMany(fetch = FetchType.EAGER)@OrderColumn(name = "posted_index")private List<Comment> comments;

}

結合テーブルに索引用カラム「 POSTED_INDEX 」を追加

※ 規約では「 COMMENTS_ORDER 」

Page 31: Beginning Java EE 6 勉強会(2) #bje_study

31

3.6 継承のマッピング

• 3 つのマッピング方式– クラス階層ごとに 1 つのテーブル

• 全部まとめて 1 つのテーブル• デフォルトはこの方式

– 結合サブクラス• クラス間の差分の属性のみを別テーブル

– 具象クラスごとのテーブル• 継承する属性も含め、クラスごとにテーブル• オプション機能なのでディストリビューションによって

は実装されていない可能性あり?

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 32: Beginning Java EE 6 勉強会(2) #bje_study

32

3.6.1 継承方式 (1) 例題

• 以下の例題をもとにマッピング方法を説明

04/12/2023 Beginning JavaEE6 勉強会 (2)

<< エンティティ>>

Item

-id : Long-title : String

<< エンティティ>>

Book

-isbn : String

<< エンティティ >>CD

-musicCompany : String

Page 33: Beginning Java EE 6 勉強会(2) #bje_study

33

3.6.1 継承方式 (2)クラス階層ごとに 1 つのテーブル

• 全部まとめて 1 つのテーブル

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@Inheritance(strategy = InheritanceType.SINGLE_TABLE)@DiscriminatorColumn(name = "ct",

discriminationType = DiscriminatorType.CHAR)@DiscriminatorValue("I")public class Item {

@Idprotected Long id;protected String title;

}

@Entity@DiscriminatorValue("B")public class Book extends Item {

private String isbn;}

@Entity@DiscriminatorValue("C")public class CD extends Item {

private String musicCompany;}

省略可能 どのクラスのインスタンスかを識別するためのカラム「 CT 」

※ 規約では「 DTYPE 」

Item クラスの識別は「 I 」※他のクラスも同様

※ 規約ではクラス名の「 Item 」

ID CT

TITLE ISBN MUSICCOMPANY

1 I 1_t

2 B 2_t 2_I

3 C 3_t 3_M

Page 34: Beginning Java EE 6 勉強会(2) #bje_study

34

3.6.1 継承方式 (3)結合サブクラス

• クラス間の差分の属性のみを別テーブル

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@Inheritance(strategy = InheritanceType.JOINED)public class Item {

@Idprotected Long id;protected String title;

}

@Entitypublic class Book extends Item {

private String isbn;}

@Entitypublic class CD extends Item {

private String musicCompany;}

結合サブクラス方式でマッピング

ITEM

+IDDTYPETITLE

bigintStringString

BOOK

+#IDISBN

bigintString

CD

+#IDMUSICCOMPANY

bigintString

Page 35: Beginning Java EE 6 勉強会(2) #bje_study

35

3.6.1 継承方式 (4)具象クラスごとのテーブル

• 継承する属性も含め、クラスごとにテーブル

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)public class Item {

@Idprotected Long id;protected String title;

}

@Entitypublic class Book extends Item {

private String isbn;}

@Entitypublic class CD extends Item {

private String musicCompany;}

具象クラスごとの方式でマッピング

ITEM

+IDTITLE

bigintString

BOOK

+IDTITLEISBN

bigintStringString

CD

+#IDTITLEMUSICCOMPANY

bigintStringString

3 クラス通して ID が一意となっている

Page 36: Beginning Java EE 6 勉強会(2) #bje_study

36

3.6.2 継承階層に含まれるクラスの種類 (1)

• 抽象エンティティ– 特に気にせず大丈夫

• 非エンティティ– 特に気にせず大丈夫– ただし(当たり前だが)非エンティティの属性は

マッピング対象にはならない

• マップドスーパークラス– 非エンティティではあるが、継承先のクラスにマッ

ピング情報を引き継ぎたい場合に利用

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 37: Beginning Java EE 6 勉強会(2) #bje_study

37

3.6.2 継承階層に含まれるクラスの種類 (2)マップドスーパークラス

• 継承先のクラスにマッピング情報を引き継ぎたい場合に利用

04/12/2023 Beginning JavaEE6 勉強会 (2)

@MappedSuperclass@Inheritance(strategy = InheritanceType.JOINED)public class Item {

@Idprotected Long id;protected String title;

}

@Entitypublic class Book extends Item {

private String isbn;}

BOOK

+IDTITLEISBN

bigintStringString

ID や TITLE カラムが作成される

ただし ITEM は作成されない

Page 38: Beginning Java EE 6 勉強会(2) #bje_study

04/12/2023 38Beginning JavaEE6 勉強会 (2)

第 4 章永続オブジェクトの管理

Page 39: Beginning Java EE 6 勉強会(2) #bje_study

39

4.1 エンティティをクエリする方法

• 全ての操作は EntityManager経由– Persistence から EntityManagerFactory を作成

• データーベースへの接続情報は persistence.xml に– Factory から EntityManager を作成– Manager に対して CRUD操作

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 40: Beginning Java EE 6 勉強会(2) #bje_study

40

4.2 エンティティ・マネージャ

• エンティティ・マネージャの取得– JavaSE環境

• 作成からクローズまで実装する必要あり– JavaEE環境

• アノテーション等を用いて簡単に取得可能

• 永続性コンテキスト– EntityManager で管理しているインスタンスの集

合• Manager内に同じ ID のものは存在しない

– Manager は同時に複数存在できるので、違うManager間で同じ ID のインスタンスは存在しえる

• データベースへ flush されるまでの一時キャッシュ

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 41: Beginning Java EE 6 勉強会(2) #bje_study

41

4.2.3 エンティティの操作 (1)

04/12/2023 Beginning JavaEE6 勉強会 (2)

メモリ内に存在

管理状態分離状態 削除済み状態

customer cust = new Customer() カーベジコレクション完了

em.persist(cust)

customer cust = em.find()

em.clear()em.detach(cust)

em.merge(cust)

em.remove(cust)

データベースから削除されてもメモリ上には存在

em.refresh(cust)セッターによる更新

データベース

Page 42: Beginning Java EE 6 勉強会(2) #bje_study

42

4.2.3 エンティティの操作 (2)

• 以下の例について考える

• エンティティの永続化

04/12/2023 Beginning JavaEE6 勉強会 (2)

ADDRESS

+IDSTREET1STREET2

bigintvarchar(255)varchar(255)

CUSTOMER

+ID+#ADD_FK

bigintbigint

Customer customer = new Customer();Address address = new Address("1", "2");customer.setAddress(address);

tx.begin();em.persist(customer);em.persist(address);tx.commit();

EntityManager はキャッシュとして動作するため、逆でも問題なく動作する

Page 43: Beginning Java EE 6 勉強会(2) #bje_study

43

4.2.3 エンティティの操作 (3)

• 2 つの方法による ID によるエンティティの検索– find()

– getReference()

04/12/2023 Beginning JavaEE6 勉強会 (2)

Customer customer = em.find(Customer.class, 1L);if (customer != null) {

// 処理}

try {Customer customer = em.getReference(Customer.class, 1L);// 処理

} catch(EntityNotFoundException e) {// 例外処理

}

なかった場合は NULL

なかった場合は例外

ID のみセットされた proxy オブジェクト

Page 44: Beginning Java EE 6 勉強会(2) #bje_study

44

4.2.3 エンティティの操作 (4)

• エンティティの削除

04/12/2023 Beginning JavaEE6 勉強会 (2)

Customer customer = new Customer();Address address = new Address("1", "2");customer.setAddress(address);

tx.begin();em.persist(customer);em.persist(address);tx.commit();

tx.begin();em.remove(customer);tx.commit();

assertNotNull(customer);

GC されるまで customer は存在

address は DB に残ったまま

Page 45: Beginning Java EE 6 勉強会(2) #bje_study

45

4.2.3 エンティティの操作 (5)

• 孤立したオブジェクトの削除– 先ほどの例では参照されない住所が取り残される

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Customer {

@Idprivate Long id;@OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)@JoinColumn(name = "address_fk", nullable = false)private Address address;

}

誰からも参照されない場合は一緒に削除

Page 46: Beginning Java EE 6 勉強会(2) #bje_study

46

4.2.3 エンティティの操作 (6)

• データベースとの同期– commit() 時は確実に同期される– 明示的に同期させたい場合は flush()

04/12/2023 Beginning JavaEE6 勉強会 (2)

tx.begin();

em.persist(customer1);em.persist(address1);em.flush();

em.persist(customer2);

em.flush();

em.persist(address2);tx.commit();

まだ address2 が DB にないため、外部キー違反で失敗

flush()済みのデータも削除される

Page 47: Beginning Java EE 6 勉強会(2) #bje_study

47

4.2.3 エンティティの操作 (7)

• イベントのカスケード

04/12/2023 Beginning JavaEE6 勉強会 (2)

Customer customer = new Customer();Address address = new Address("1", "2");customer.setAddress(address);

tx.begin();em.persist(customer);tx.commit(); めんどくさいのでこう書きたい

※address の persist を省略したい

@Entitypublic class Customer {

@Idprivate Long id;@OneToOne(fetch = FetchType.LAZY,

cascade = {CascadeType.PERSIST, CascadeType.REMOVE})@JoinColumn(name = "address_fk", nullable = false)private Address address;

}

persist(), remove() の際にカスケード

Page 48: Beginning Java EE 6 勉強会(2) #bje_study

48

4.2.3 エンティティの操作 (8)

• 二次キャッシュ機能– 全てのディストリビューションで実装済み

• 内部の振る舞いは微妙に違ってくる– 異なる EntityManager で発行されたクエリについ

てもマージしてキャッシュ– 振る舞いを persistence.xml で設定

04/12/2023 Beginning JavaEE6 勉強会 (2)

shared-cache-mode

振る舞い

ALL 全てのエンティティをキャッシュ

DISABLE_SELECTIVE @Cachable(false) 以外のエンティティをキャッシュ

ENABLE_SELECTIVE @Cachable(true) のエンティティのみキャッシュ

NONE 全てのエンティティをキャッシュしない

UNSPECIFIED ディストリビューションお任せ

Page 49: Beginning Java EE 6 勉強会(2) #bje_study

49

4.3 JPQL

• ほぼ SQL

04/12/2023 Beginning JavaEE6 勉強会 (2)

ggrks

@making

スライド作るの心折れました…

Page 50: Beginning Java EE 6 勉強会(2) #bje_study

50

4.4 クエリ

• 4種類の発行方式

• クエリの手順– クエリの生成

• EntityManager インタフェースのメソッドで生成– クエリの発行

• 当該結果を全て取得する getResultList()• 当該結果を 1 つだけ取得する getSingleResult()

04/12/2023 Beginning JavaEE6 勉強会 (2)

発行方式 説明

動的クエリ 必要なときに JPQL を都度投げる

名前付きクエリ 名前付きのクエリをあらかじめ作っておく

ネイティブクエリ

生 SQL を投げる

Criteria API オブジェクト指向っぽい特殊な記法

Page 51: Beginning Java EE 6 勉強会(2) #bje_study

51

4.4.1 動的クエリ

04/12/2023 Beginning JavaEE6 勉強会 (2)

String jpql = "SELECT c FROM Customer c WHERE c.name = :name";

TypedQuery<Customer> query = em.createQuery(jpql, Customer.class);

query.setParameter(“name", "making");

List<Customer> customers = query.getResultList();

動的なパラメータを指定可能

扱う型を指定可能

クエリの発行

@Entitypublic class Customer {

@Idprivate Long id;private String name;

}

Page 52: Beginning Java EE 6 勉強会(2) #bje_study

52

4.4.2 名前付きクエリ

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@NamedQueries({

@NamedQuery(name = "Customer.findByName",query = "SELECT c FROM Customer c WHERE c.name = :name")

})public class Customer {

public static final String FIND_BY_NAME = "Customer.findByName"

@Idprivate Long id;private String name;

}

Query query = em.createNamedQuery(Customer.FIND_BY_NAME);

query.setParameter("name", "making").setMaxResults(3);

List<?> customers = query.getResultList();

クエリ名を定数にしておくといい

取得件数を指定可能

メソッドチェーンで設定可能

Page 53: Beginning Java EE 6 勉強会(2) #bje_study

53

4.4.3 ネイティブクエリ

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entity@NamedQuery(name = "Customer.findAll",

query = "SELECT * FROM CUST")@Table(name = "CUST")public class Customer {

public static final String FIND_ALL = "Customer.findAll"

@Idprivate Long id;private String name;

}

Query query = em.createNativeQuery(Customer.FIND_ALL);

List<?> customers = query.getResultList();

JPQL では Customer

Page 54: Beginning Java EE 6 勉強会(2) #bje_study

54

4.4.4 Criteria API (オブジェクト指向クエリ)• オブジェクト指向っぽくクエリを生成– 文字列でゴリゴリ書くよりもバグを埋め込みにくい

• でもめんどくさい…

04/12/2023 Beginning JavaEE6 勉強会 (2)

SELECT c FROM Customer c WHERE c.name = 'making'

CriteriaBuilder builder = em.getCriteriaBuilder();

CriteriaQuery<Customer> query = builder.createQuery(Customer.class);

Root<Customer> c = query.from(Customer.class);

query.select(c).where(builder.equal(c.get("name"), "making"));

Page 55: Beginning Java EE 6 勉強会(2) #bje_study

55

4.5 同時実行

• 2種類のロック方式

04/12/2023 Beginning JavaEE6 勉強会 (2)

ロック方式 説明

楽観的ロック

基本的に競合は発生しないという前提。

JPA ではバージョニングの考え方を導入し、バージョン違反がなければ同時実行を許容する。

悲観的ロック

基本的に競合は発生するという前提。

データベースは通常悲観ロックを提供する。書き込みの発生が前提となる読み取りの際には、事前にロックをかける。 JPA ではバージョニング対象外は悲観ロックとなる。

Page 56: Beginning Java EE 6 勉強会(2) #bje_study

56

• バージョニング用の属性を導入

4.5 バージョニングと楽観的ロック

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Book {

@Idprivate Long id;@Versionprivate Integer version;

}

書き込み時に自動インクリメント更新の際、データベースでの値と

異なる場合は例外となる

tx1.begin();

Book book = em.find(Book.class, 10);// バージョン 1

book.raisePriceByTwoDollars();

tx1.commit();// バージョン 2

tx2.begin();

Book book = em.find(Book.class, 10);// バージョン 1

book.raisePriceByTwoDollars();

tx2.commit();// bookはバージョン 1、 DBはバージョン 2// よって OptimisticLockException

Page 57: Beginning Java EE 6 勉強会(2) #bje_study

04/12/2023 57Beginning JavaEE6 勉強会 (2)

第 5 章コールバックとリスナ

Page 58: Beginning Java EE 6 勉強会(2) #bje_study

58

5.2 コールバック (1)

• CRUD操作に対して事前/事後処理を指定

04/12/2023 Beginning JavaEE6 勉強会 (2)

メモリ内に存在

管理状態分離状態 削除済み状態

customer cust = new Customer() カーベジコレクション完了

em.persist(cust)

customer cust = em.find()

em.clear()em.detach(cust)

em.merge(cust)

em.remove(cust)

データベースから削除されてもメモリ上には存在

em.refresh(cust) セッターによる更新

@PrePersist

@PostPersist

@PreUpdate

@PostUpdate@PostLoad

@PreRemove

@PostRemove

@PostLoad

@PreUpdate

@PostUpdate, @PostLoad

Page 59: Beginning Java EE 6 勉強会(2) #bje_study

59

5.2 コールバック (2)

04/12/2023 Beginning JavaEE6 勉強会 (2)

@Entitypublic class Customer {

@Idprivate Long id;private String name;@Transientprivate Integer age;

@PrePersist@PreUpdateprivate void validate() {

if (c.getName() == null) {throw new IllegalNameException();

}}

@PostLoad@PostPersist@PostUpdatepublic void calcAge() {

// 年齢計算age = ...

}}

書き込み前に名前のバリデーション

読み込み/書き込み後に年齢計算

Page 60: Beginning Java EE 6 勉強会(2) #bje_study

60

5.2 コールバック (3)

• 注意点– 1 つのメソッドに複数のコールバックアノテーショ

ンは付加できる– 同じエンティティ内に同一のコールバックアノテー

ションは複数付加できない– 継承関係がある場合、親クラスのコールバックメ

ソッドが先に実行される

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 61: Beginning Java EE 6 勉強会(2) #bje_study

61

5.3 リスナ (1)

• エンティティ外にコールバックメソッドを定義する場合に利用

04/12/2023 Beginning JavaEE6 勉強会 (2)

@EntityListeners({DataValidationListener.class})@Entitypublic class Customer {

@Idprivate Long id;private String name;

}

public class DataValidationListener {@PrePersist@PreUpdatepublic void validate(Customer c) {

if (c.getName() == null) {throw new IllegalNameException();

}}

}

引数に呼び出しもとのエンティティ

関連づけるリスナを配列で指定この順番でコールバックメソッドを実施

Page 62: Beginning Java EE 6 勉強会(2) #bje_study

62

5.3 リスナ (2)

• 全てのエンティティから使用できるリスナ(デフォルトリスナ)– XML マッピングファイルを利用– 特定のエンティティだけ外したい場合は

@ExcludeDefaultListeners アノテーションを利用

04/12/2023 Beginning JavaEE6 勉強会 (2)

Page 63: Beginning Java EE 6 勉強会(2) #bje_study

6304/12/2023 Beginning JavaEE6 勉強会 (2)