Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
JPA 説明会その1 O/R マッピングの基本概念 および静的マッピング
業務システムにおける データアクセス層の重要性 大部分の業務システムの開発では、関係データベース( RDB )へのアクセスを行う必要がある データアクセス層の設計方針は以下のような要素に影響を与え、プロジェクトの成功・失敗を左右する最も重要な要因の一つである 性能 開発生産性 保守性 Java から RDB にアクセスするには、さまざまな手段があるので、業務要件に応じて適切な方法を選択することが重要である
さまざまなデータアクセス手法 SQL 中心アプローチ JDBC API (のラッパー)を使い、アプリケーションロジックから手続き的に検索、更新、挿入などの DML 文、あるいはストアドプロシージャを呼び出す 通常は上記ロジックを DAO クラス内部にカプセル化する テーブルの行データを受け渡すために「オブジェクト」を利用することはあるが、通常はロジックを持たないデータの入れ物としてのみ利用 O/R マッピングアプローチ 業務要件に応じたドメインモデルを設計 XML やアノテーションなどのメタ情報を使うことで、オブジェクト構造とテーブル構造とを自動的にマッピング FW が SQL を自動生成するため、プログラム中では直接 SQL を意識しない
O/R マッピングアプローチが 適するケースと適さないケース O/R マッピングが適するケース 検索処理と更新処理が分離されており、大量の行を検索することがあっても、更新は数行単位で行うような処理が中心の場合 継承関係や粒度の細かい値オブジェクトなど複雑なドメインモデルを扱う場合( 静的インピーダンスミスマッチ ) エンティティ自体を複雑な状態と振る舞いをカプセル化した OO 本来のアプローチで設計したい場合( 動的インピーダンスミスマッチ ) O/R マッピングが適さないケース 大量データの一括削除や一括更新などバッチ的なデータ処理が中心の場合 本質的にデータの処理が中心であり、わざわざオブジェクトにマップする意味がない場合 そもそも開発チームに OO のスキルがない場合 単純なアプリなら RAD 的に自動生成した方がベター?
O/R マッピング手法自体の分類 一口に「 O/R マッピング  FW 」といっても、実はいろいろな手法を使ったものが存在する 永続化ロジックそのものの配置場所による違い Active Record パターン Data Mapper パターン 動的マッピング機能に対するサポート有無の違い Identity Mapping パターン Unit Of Work パターン 以上のパターンは、すべてマーチンファウラーのエンタープライズアーキテクチャパターンで説明されている http://capsctrl.que.jp/kdmsnr/wiki/PofEAA/?CatalogOfPofEAAInJapanese
Active Record パターン 永続化処理をエンティティに持たせることで、エンティティが自分自身で「アクティブに」 DB にアクセスする 古い CMP エンティティ EJB における O/R マップの手法 一見 OO 的でわかりやすいが、ドメインロジックを DB と切り離して単体試験できないなど最近の Java の世界では、むしろ、アンチパターンとされている Ruby の世界では動的言語の特性を活かすことで Active Record のアプローチが見直されている エンティティが自分で DB に対して永続ロジックを実行する
Data Mapper パターン エンティティと DB とのデータの変換を担当する専用の「マッパーオブジェクト」を利用する エンティティ自身にはドメインロジックのみが記述され、 DB からは完全に切り離される XML ファイルやアノテーションなどにマッピングのためのメタデータを記述することで、マッパーオブジェクト自身は汎用の FW を再利用することができる JPA をはじめ、最近の Java の O/R マッピングでは、流行のアプローチ
動的マッピングと透過的永続性 高度な O/R マッピング FW では、読み込んだエンティティの状態をメモリ中で自動的に管理している 業務ロジックの途中でエンティティの状態が変更されると、適切なタイミングで自動的に UPDATE や DELETE などの DML を発行することでエンティティの状態と DB との同期処理を実行する このように、エンティティとテーブルとの構造的なマッピングのみでなく、動的な更新タイミングのマッピングを自動的に行うことで、業務ロジックからは DB アクセス処理が完全に切り離される形になる(透過的永続性、 transparent persistence) このレベルを実現した FW を「完全 O/R マッピング」と呼ぶ
さまざまな永続化手法のまとめ 今回説明の対象とする JPA は「完全 O/R マッピング」に対する標準 API である 最も複雑なカテゴリのため、敷居が高いが、非常に強力であり、適切に使いこなせば、開発生産性を飛躍的に高めることができる SQL 中心 O/R マッピング 静的構造マッピングのみ 完全 O/R マッピング 永続化手法 Hibernate 、 Toplink 、 Open JPA など JPA 対応の FW 、 JDO EJB CMP も思想は異なるが一応このカテゴリ S2DAO 、 iBatis 、 Ruby on Rails の Active Record など JDBC
JPA とは Java Persistence API の略 もともとは、 Java EE における EJB3 のエンティティ Bean の仕様の一部だったが、仕様として独立し、 Java SE 環境でもサポートされる 完全 O/R マッピング FW が実装すべき標準 API 静的マッピングにおけるメタ情報を定義するための標準アノテーション群 エンティティの動的な状態管理、クエリーのためのエンティティマネージャ API JPA 自体は仕様なので、実際は JPA を実装した製品(= JPA プロバイダ)を利用する Open Source の JPA プロバイダの例 Hibernate Open JPA Toplink Essentials JavaEE 5 対応のアプリケーションサーバ製品には最初から JPA プロバイダが組み込まれている
Hibernate とその他の プロバイダの比較(参考) Hibernate の長所 Hibernate は JPA よりかなり前からオープンソースのライブラリとして使われてきた実績がある 現在は JBoss AP サーバのコア部分で使われている 状態管理に cglib を使った実行時コード生成を利用しているため、コンパイル時やクラスロード時コード変換(インツルメンテーション処理)が不要 Hibernate 本体は JDK1.4 でも動作する Hibernate の短所 JPA 部分を後付けで実装しているため、アーキテクチャや API が複雑(最初から JavaEE5 をターゲットにするなら、他のプロバイダの方がベターかも)
JPA プロジェクトの ディレクトリ構造と設定ファイル JPA では大部分の設定をアノテーションを使ってコード中に記述する ただし、データソースなど一部の設定は xml ファイルに記述する必要がある JPA プロバイダの起動時に、 persistence.xml を含む jar のツリー内に含まれるエンティティクラスを自動的に探して登録してくれる DB 接続やトランザクションなど永続ユニットに対する設定を記述(必須) エンティティとテーブルとのマッピングメタデータを記述 (すべてアノテーション利用なら不要)
エンティティとテーブルとの 基本的なマッピング エンティティクラスの必須要件は以下の通り(それ以外は任意の POJO ) クラス宣言には @Entity をつける 主キーに対応するフィールド(あるいはゲッター)に @Id をつける public か protected のデフォルトコンストラクタを持つ クラス自体やマッピング対象のフィールドやメソッドや final にしてはならない エンティティ自身を値渡しする場合は Serializable を実装する
永続フィールド・プロパティ @Id をフィールドに対して付けた場合、それ以外のすべてのフィールド(関連を除く)はデフォルトで永続対象フィールドとなり、テーブルのカラムにマップされる @Id を getter に付けた場合、すべての getter 、 setter メソッドは永続プロパティとなる (JPA エンジンとのやり取りはすべてアクセサを経由する) フィールド・プロパティを明示的に永続化対処からはずす場合は @Transient をつける 以降は永続フィールドの使用を前提に説明
単純なエンティティ定義の例 @Entity   public class Employee {  @Id   private Integer id; private String name; private int age; … }   POJO にアノテーションをつけるだけで自動的にテーブルにマッピングされる! Emp loyee .Java EMPLOYEE テーブル 33 Ryo Asai 1 27 Daisaku Kato 2 AGE NAME ID
単純にマップ可能な型について プリミティブ型、プリミティブラッパー型 String 型 Enum 型 その他の任意のシリアライザブルな型(特に以下の型を含む) 日付型 BigDecimal 、 BigInteger byte[] 、 Byte[] 、 char[] 、 Character[]
Enum 型のマッピング Enum 型はデフォルトでは定数の定義されている順番( ordinal 番号)が永続化される @ Enumerated アノテーションを使うことで、定数名を文字列として永続化できる @ Enumerated( EnumType.ORDINAL ) 省略した場合と同様順番を永続化 @ Enumerated( EnumType.STRING ) 定数名を文字列のとして永続化 それ以外のマッピングを行うにはプロバイダ固有の機能を使う必要がある
日付型のマッピング JPA 仕様的には以下の日付型を使う場合は @Temporal をつけるのが必須 util パッケージの日付型 java.util.Date, java.util.Calendar sql パッケージの日付型 java.sql.Date java.sql.Time java.sql.Timestamp 以下の何れかを指定 @Temporal(TemporalType.DATE) @Temporal(TemporalType.TIME) @Temporal(TemporalType.TIMESTAMP)
デフォルト名以外での テーブル名、カラム名の指定方法 JPA 標準では、デフォルトで、クラス名がそのままテーブル名に、フィールド名がそのままカラム名にマップされる Hibernate では独自の NamingStrategy 機能により、デフォルト変換ルールをオーバーライドできる ( テーブル名の先頭に自動的に T をつけるなど) それ以外の場合は以下のアノテーションで明示的に指定する @Table(name=“ テーブル名” ) @Column(name=“ カラム名” )
テーブル名、カラム名の指定例 @Entity  @Table(name=“EMP”) public class Employee {  @Id  @Column(name=“EMP_ID”) private Integer id; private String name; private int age; … }   @Table 、 @Column などのアノテーションはエンティティの論理構造ではなく、物理的なテーブル上のマッピングをカスタマイズするために使用する。 後で出てくる @ JoinTable,  @ JoinColumn などを含めて 最後に Table か Column が付くアノテーションはたいてい物理マッピングに使用する と覚えておくと良い。 Emp loyee .Java EMP テーブル 33 Ryo Asai 1 27 Daisaku Kato 2 AGE NAME EMP_ID
エンティティ同士の関連のマッピング エンティティ間の関連をマップする場合、まずは次の 2 つの点の組み合わせを考慮する 多重度(カージナリティ) 一対一 一対多、多対一 多対多 方向性 単方向 双方向 純粋な OO モデルからは以上のすべての組み合わせが可能だが、 DB とのマッピングの相性が異なる
POJO による関連の表現 エンティティは POJO クラスなので、オブジェクトモデル(クラス図)は機械的なルールで簡単に記述することができる 関連先が 1 なら関連先のエンティティ型をフィールドを定義 関連先が多ならコレクション(ただし IF )型のフィールドを定義 関連が相方向であれば、関連するお互いのクラスに関連フィールドを持たせる 関連が単方向であれば、たどれる方向に相当する関連フィールドのみを持たせる
最も基本的な関連マッピング: 「一方向多対1」 CUSTOMER テーブル BRANCH テーブル @Entity public class Customer {  @Id private  Long  id; @ManyToOne private Branch branch; … }   @Entity public class Branch {  @Id private Long id; … }   外部キー列を使って関連先テーブルの主キー列と関連付ける 外部キー列の名前はデフォルトで「関連先エンティティ名 _ 主キー名」となる 1 2 2 1 2 3 BRANCH_ID ID 2 1 ID
外部キー列の名前を明示的に指定したい場合 CUSTOMER テーブル BRANCH テーブル @Entity public class Customer {  @Id private  Long  id; @ManyToOne @JoinColumn(name=“BRANCH_CODE”) private Branch branch; … }   @Entity public class Branch {  @Id private Long id; … }   @JoinColumn を使って外部キー列名を指定する 1 2 2 1 2 3 BRANCH_CODE ID 2 1 ID
関連を双方向にするには @Entity public class Customer {  @Id private  Long  id; @ManyToOne private Branch branch; … }   @Entity public class Branch {  @Id private Long id; @OneToMany(mappedBy=“branch”) Set<Customer> customers … }   CUSTOMER テーブル BRANCH テーブル テーブル上は単方向の場合と区別がない!! 関連の方向性というのは OO モデルにのみ存在する概念で、もともとテーブルモデル上は存在しない。 1 2 2 1 2 3 BRANCH_ID ID 2 1 ID
@ OneToMeny における mappedBy の意味について OO モデル( Java クラスの世界)では双方向の関連は 2 つの独立した関連で表現される。 顧客⇒支店 支店⇒顧客リスト テーブルモデル上は「1つ」の外部キーによってデータレコードが紐付けられる mappedBy は反対側の属性でマップされている外部キーによって自動的にマップされるということを意味している
関連の所有側と反対側 双方向の関連をテーブルの関連にマップする際には、必ず、一方が外部キーの所有側( owning side) 、他方が反対側( inverse side )となる @ JoinColumn を記述する側が所有側、 mappedBy を記述する側が反対側になる覚えておく 一対多関連では、通常多側が外部キーの所有側 O/R マッパーが更新処理を行う際には、所有側の情報のみが使われる ⇒ そうしないと、無駄に 2 回の UPDATE が実行されてしまうことになる!!! 逆に言うと、双方向の関連をプログラムする際には、プログラマの責任でお互いの関連の整合性を保つ必要があるということ
双方向関連のベストプラクティス ⇒追加、削除ヘルパーメソッド 双方向関連をマップする時は、関連の整合性を保証するため、追加、削除のための専用メソッドを持たせること @Entity public class Branch {  … @OneToMany(mappedBy=“branch”) private  Set<Customer> customers public void addCustomer(Customer customer)  { customers.add(customer); customer.setBranch(this); } public void  remove Customer(Customer customer)  { customers.remove(customer); customer.setBranch(null); } }   所有側の関連を忘れずに更新すること
その他の関連のマッピングの概要 一対一関連 @OneToOne アノテーションを使う 外部キーを使ってマップする場合は 1 対多と同じだが、外部キー列に一意制約をつける 主キー同士をそろえることでマップする場合は所有側に @PrimaryKeyJoinColumn をつける 一対一の場合所有側(外部キーを持つ側)はどちらのテーブルでもよい 多対多関連 @ ManyToMany アノテーションを使う 結合表(関連表、 JOIN テーブル)を使ってマッピングする(@ JoinTable をつかって結合表を指定) 所有側はどちら側でもよい
OO モデルと関連モデルとの 静的インピーダンスミスマッチ より複雑なドメインモデルを扱いたい場合には、以下の点で機械的にテーブルとクラス間の関連を対応付けることが不適切になる 粒度の問題⇒ OO 的には細かいクラスに分割したいが、テーブル的には JOIN の性能を考えるとあまり細かく分割すべきでない 継承の問題⇒テーブルは直接継承を表現できない CMP エンティティ Bean の仕様は、以上の問題を完全に無視されていた JPA 仕様では、継承な値オブジェクトなど、より適切な OO ドメインモデルの構築が可能である

More Related Content

JPA説明会

  • 1. JPA 説明会その1 O/R マッピングの基本概念 および静的マッピング
  • 2. 業務システムにおける データアクセス層の重要性 大部分の業務システムの開発では、関係データベース( RDB )へのアクセスを行う必要がある データアクセス層の設計方針は以下のような要素に影響を与え、プロジェクトの成功・失敗を左右する最も重要な要因の一つである 性能 開発生産性 保守性 Java から RDB にアクセスするには、さまざまな手段があるので、業務要件に応じて適切な方法を選択することが重要である
  • 3. さまざまなデータアクセス手法 SQL 中心アプローチ JDBC API (のラッパー)を使い、アプリケーションロジックから手続き的に検索、更新、挿入などの DML 文、あるいはストアドプロシージャを呼び出す 通常は上記ロジックを DAO クラス内部にカプセル化する テーブルの行データを受け渡すために「オブジェクト」を利用することはあるが、通常はロジックを持たないデータの入れ物としてのみ利用 O/R マッピングアプローチ 業務要件に応じたドメインモデルを設計 XML やアノテーションなどのメタ情報を使うことで、オブジェクト構造とテーブル構造とを自動的にマッピング FW が SQL を自動生成するため、プログラム中では直接 SQL を意識しない
  • 4. O/R マッピングアプローチが 適するケースと適さないケース O/R マッピングが適するケース 検索処理と更新処理が分離されており、大量の行を検索することがあっても、更新は数行単位で行うような処理が中心の場合 継承関係や粒度の細かい値オブジェクトなど複雑なドメインモデルを扱う場合( 静的インピーダンスミスマッチ ) エンティティ自体を複雑な状態と振る舞いをカプセル化した OO 本来のアプローチで設計したい場合( 動的インピーダンスミスマッチ ) O/R マッピングが適さないケース 大量データの一括削除や一括更新などバッチ的なデータ処理が中心の場合 本質的にデータの処理が中心であり、わざわざオブジェクトにマップする意味がない場合 そもそも開発チームに OO のスキルがない場合 単純なアプリなら RAD 的に自動生成した方がベター?
  • 5. O/R マッピング手法自体の分類 一口に「 O/R マッピング FW 」といっても、実はいろいろな手法を使ったものが存在する 永続化ロジックそのものの配置場所による違い Active Record パターン Data Mapper パターン 動的マッピング機能に対するサポート有無の違い Identity Mapping パターン Unit Of Work パターン 以上のパターンは、すべてマーチンファウラーのエンタープライズアーキテクチャパターンで説明されている http://capsctrl.que.jp/kdmsnr/wiki/PofEAA/?CatalogOfPofEAAInJapanese
  • 6. Active Record パターン 永続化処理をエンティティに持たせることで、エンティティが自分自身で「アクティブに」 DB にアクセスする 古い CMP エンティティ EJB における O/R マップの手法 一見 OO 的でわかりやすいが、ドメインロジックを DB と切り離して単体試験できないなど最近の Java の世界では、むしろ、アンチパターンとされている Ruby の世界では動的言語の特性を活かすことで Active Record のアプローチが見直されている エンティティが自分で DB に対して永続ロジックを実行する
  • 7. Data Mapper パターン エンティティと DB とのデータの変換を担当する専用の「マッパーオブジェクト」を利用する エンティティ自身にはドメインロジックのみが記述され、 DB からは完全に切り離される XML ファイルやアノテーションなどにマッピングのためのメタデータを記述することで、マッパーオブジェクト自身は汎用の FW を再利用することができる JPA をはじめ、最近の Java の O/R マッピングでは、流行のアプローチ
  • 8. 動的マッピングと透過的永続性 高度な O/R マッピング FW では、読み込んだエンティティの状態をメモリ中で自動的に管理している 業務ロジックの途中でエンティティの状態が変更されると、適切なタイミングで自動的に UPDATE や DELETE などの DML を発行することでエンティティの状態と DB との同期処理を実行する このように、エンティティとテーブルとの構造的なマッピングのみでなく、動的な更新タイミングのマッピングを自動的に行うことで、業務ロジックからは DB アクセス処理が完全に切り離される形になる(透過的永続性、 transparent persistence) このレベルを実現した FW を「完全 O/R マッピング」と呼ぶ
  • 9. さまざまな永続化手法のまとめ 今回説明の対象とする JPA は「完全 O/R マッピング」に対する標準 API である 最も複雑なカテゴリのため、敷居が高いが、非常に強力であり、適切に使いこなせば、開発生産性を飛躍的に高めることができる SQL 中心 O/R マッピング 静的構造マッピングのみ 完全 O/R マッピング 永続化手法 Hibernate 、 Toplink 、 Open JPA など JPA 対応の FW 、 JDO EJB CMP も思想は異なるが一応このカテゴリ S2DAO 、 iBatis 、 Ruby on Rails の Active Record など JDBC
  • 10. JPA とは Java Persistence API の略 もともとは、 Java EE における EJB3 のエンティティ Bean の仕様の一部だったが、仕様として独立し、 Java SE 環境でもサポートされる 完全 O/R マッピング FW が実装すべき標準 API 静的マッピングにおけるメタ情報を定義するための標準アノテーション群 エンティティの動的な状態管理、クエリーのためのエンティティマネージャ API JPA 自体は仕様なので、実際は JPA を実装した製品(= JPA プロバイダ)を利用する Open Source の JPA プロバイダの例 Hibernate Open JPA Toplink Essentials JavaEE 5 対応のアプリケーションサーバ製品には最初から JPA プロバイダが組み込まれている
  • 11. Hibernate とその他の プロバイダの比較(参考) Hibernate の長所 Hibernate は JPA よりかなり前からオープンソースのライブラリとして使われてきた実績がある 現在は JBoss AP サーバのコア部分で使われている 状態管理に cglib を使った実行時コード生成を利用しているため、コンパイル時やクラスロード時コード変換(インツルメンテーション処理)が不要 Hibernate 本体は JDK1.4 でも動作する Hibernate の短所 JPA 部分を後付けで実装しているため、アーキテクチャや API が複雑(最初から JavaEE5 をターゲットにするなら、他のプロバイダの方がベターかも)
  • 12. JPA プロジェクトの ディレクトリ構造と設定ファイル JPA では大部分の設定をアノテーションを使ってコード中に記述する ただし、データソースなど一部の設定は xml ファイルに記述する必要がある JPA プロバイダの起動時に、 persistence.xml を含む jar のツリー内に含まれるエンティティクラスを自動的に探して登録してくれる DB 接続やトランザクションなど永続ユニットに対する設定を記述(必須) エンティティとテーブルとのマッピングメタデータを記述 (すべてアノテーション利用なら不要)
  • 13. エンティティとテーブルとの 基本的なマッピング エンティティクラスの必須要件は以下の通り(それ以外は任意の POJO ) クラス宣言には @Entity をつける 主キーに対応するフィールド(あるいはゲッター)に @Id をつける public か protected のデフォルトコンストラクタを持つ クラス自体やマッピング対象のフィールドやメソッドや final にしてはならない エンティティ自身を値渡しする場合は Serializable を実装する
  • 14. 永続フィールド・プロパティ @Id をフィールドに対して付けた場合、それ以外のすべてのフィールド(関連を除く)はデフォルトで永続対象フィールドとなり、テーブルのカラムにマップされる @Id を getter に付けた場合、すべての getter 、 setter メソッドは永続プロパティとなる (JPA エンジンとのやり取りはすべてアクセサを経由する) フィールド・プロパティを明示的に永続化対処からはずす場合は @Transient をつける 以降は永続フィールドの使用を前提に説明
  • 15. 単純なエンティティ定義の例 @Entity public class Employee { @Id private Integer id; private String name; private int age; … } POJO にアノテーションをつけるだけで自動的にテーブルにマッピングされる! Emp loyee .Java EMPLOYEE テーブル 33 Ryo Asai 1 27 Daisaku Kato 2 AGE NAME ID
  • 16. 単純にマップ可能な型について プリミティブ型、プリミティブラッパー型 String 型 Enum 型 その他の任意のシリアライザブルな型(特に以下の型を含む) 日付型 BigDecimal 、 BigInteger byte[] 、 Byte[] 、 char[] 、 Character[]
  • 17. Enum 型のマッピング Enum 型はデフォルトでは定数の定義されている順番( ordinal 番号)が永続化される @ Enumerated アノテーションを使うことで、定数名を文字列として永続化できる @ Enumerated( EnumType.ORDINAL ) 省略した場合と同様順番を永続化 @ Enumerated( EnumType.STRING ) 定数名を文字列のとして永続化 それ以外のマッピングを行うにはプロバイダ固有の機能を使う必要がある
  • 18. 日付型のマッピング JPA 仕様的には以下の日付型を使う場合は @Temporal をつけるのが必須 util パッケージの日付型 java.util.Date, java.util.Calendar sql パッケージの日付型 java.sql.Date java.sql.Time java.sql.Timestamp 以下の何れかを指定 @Temporal(TemporalType.DATE) @Temporal(TemporalType.TIME) @Temporal(TemporalType.TIMESTAMP)
  • 19. デフォルト名以外での テーブル名、カラム名の指定方法 JPA 標準では、デフォルトで、クラス名がそのままテーブル名に、フィールド名がそのままカラム名にマップされる Hibernate では独自の NamingStrategy 機能により、デフォルト変換ルールをオーバーライドできる ( テーブル名の先頭に自動的に T をつけるなど) それ以外の場合は以下のアノテーションで明示的に指定する @Table(name=“ テーブル名” ) @Column(name=“ カラム名” )
  • 20. テーブル名、カラム名の指定例 @Entity @Table(name=“EMP”) public class Employee { @Id @Column(name=“EMP_ID”) private Integer id; private String name; private int age; … } @Table 、 @Column などのアノテーションはエンティティの論理構造ではなく、物理的なテーブル上のマッピングをカスタマイズするために使用する。 後で出てくる @ JoinTable, @ JoinColumn などを含めて 最後に Table か Column が付くアノテーションはたいてい物理マッピングに使用する と覚えておくと良い。 Emp loyee .Java EMP テーブル 33 Ryo Asai 1 27 Daisaku Kato 2 AGE NAME EMP_ID
  • 21. エンティティ同士の関連のマッピング エンティティ間の関連をマップする場合、まずは次の 2 つの点の組み合わせを考慮する 多重度(カージナリティ) 一対一 一対多、多対一 多対多 方向性 単方向 双方向 純粋な OO モデルからは以上のすべての組み合わせが可能だが、 DB とのマッピングの相性が異なる
  • 22. POJO による関連の表現 エンティティは POJO クラスなので、オブジェクトモデル(クラス図)は機械的なルールで簡単に記述することができる 関連先が 1 なら関連先のエンティティ型をフィールドを定義 関連先が多ならコレクション(ただし IF )型のフィールドを定義 関連が相方向であれば、関連するお互いのクラスに関連フィールドを持たせる 関連が単方向であれば、たどれる方向に相当する関連フィールドのみを持たせる
  • 23. 最も基本的な関連マッピング: 「一方向多対1」 CUSTOMER テーブル BRANCH テーブル @Entity public class Customer { @Id private Long id; @ManyToOne private Branch branch; … } @Entity public class Branch { @Id private Long id; … } 外部キー列を使って関連先テーブルの主キー列と関連付ける 外部キー列の名前はデフォルトで「関連先エンティティ名 _ 主キー名」となる 1 2 2 1 2 3 BRANCH_ID ID 2 1 ID
  • 24. 外部キー列の名前を明示的に指定したい場合 CUSTOMER テーブル BRANCH テーブル @Entity public class Customer { @Id private Long id; @ManyToOne @JoinColumn(name=“BRANCH_CODE”) private Branch branch; … } @Entity public class Branch { @Id private Long id; … } @JoinColumn を使って外部キー列名を指定する 1 2 2 1 2 3 BRANCH_CODE ID 2 1 ID
  • 25. 関連を双方向にするには @Entity public class Customer { @Id private Long id; @ManyToOne private Branch branch; … } @Entity public class Branch { @Id private Long id; @OneToMany(mappedBy=“branch”) Set<Customer> customers … } CUSTOMER テーブル BRANCH テーブル テーブル上は単方向の場合と区別がない!! 関連の方向性というのは OO モデルにのみ存在する概念で、もともとテーブルモデル上は存在しない。 1 2 2 1 2 3 BRANCH_ID ID 2 1 ID
  • 26. @ OneToMeny における mappedBy の意味について OO モデル( Java クラスの世界)では双方向の関連は 2 つの独立した関連で表現される。 顧客⇒支店 支店⇒顧客リスト テーブルモデル上は「1つ」の外部キーによってデータレコードが紐付けられる mappedBy は反対側の属性でマップされている外部キーによって自動的にマップされるということを意味している
  • 27. 関連の所有側と反対側 双方向の関連をテーブルの関連にマップする際には、必ず、一方が外部キーの所有側( owning side) 、他方が反対側( inverse side )となる @ JoinColumn を記述する側が所有側、 mappedBy を記述する側が反対側になる覚えておく 一対多関連では、通常多側が外部キーの所有側 O/R マッパーが更新処理を行う際には、所有側の情報のみが使われる ⇒ そうしないと、無駄に 2 回の UPDATE が実行されてしまうことになる!!! 逆に言うと、双方向の関連をプログラムする際には、プログラマの責任でお互いの関連の整合性を保つ必要があるということ
  • 28. 双方向関連のベストプラクティス ⇒追加、削除ヘルパーメソッド 双方向関連をマップする時は、関連の整合性を保証するため、追加、削除のための専用メソッドを持たせること @Entity public class Branch { … @OneToMany(mappedBy=“branch”) private Set<Customer> customers public void addCustomer(Customer customer) { customers.add(customer); customer.setBranch(this); } public void remove Customer(Customer customer) { customers.remove(customer); customer.setBranch(null); } } 所有側の関連を忘れずに更新すること
  • 29. その他の関連のマッピングの概要 一対一関連 @OneToOne アノテーションを使う 外部キーを使ってマップする場合は 1 対多と同じだが、外部キー列に一意制約をつける 主キー同士をそろえることでマップする場合は所有側に @PrimaryKeyJoinColumn をつける 一対一の場合所有側(外部キーを持つ側)はどちらのテーブルでもよい 多対多関連 @ ManyToMany アノテーションを使う 結合表(関連表、 JOIN テーブル)を使ってマッピングする(@ JoinTable をつかって結合表を指定) 所有側はどちら側でもよい
  • 30. OO モデルと関連モデルとの 静的インピーダンスミスマッチ より複雑なドメインモデルを扱いたい場合には、以下の点で機械的にテーブルとクラス間の関連を対応付けることが不適切になる 粒度の問題⇒ OO 的には細かいクラスに分割したいが、テーブル的には JOIN の性能を考えるとあまり細かく分割すべきでない 継承の問題⇒テーブルは直接継承を表現できない CMP エンティティ Bean の仕様は、以上の問題を完全に無視されていた JPA 仕様では、継承な値オブジェクトなど、より適切な OO ドメインモデルの構築が可能である