1. 序章

このクイックHibernateチュートリアルでは、XMLの代わりにJPAアノテーションを使用した1対多マッピングの例を紹介します。

また、双方向の関係とは何か、それらがどのように矛盾を生み出す可能性があるか、そして所有権の考え方がどのように役立つかについても学びます。

2. 説明

簡単に言えば、 1対多のマッピングとは、テーブル内の1つの行が別のテーブル内の複数の行にマップされることを意味します。

次の実体関連図を見て、1対多の関連付けを確認しましょう。

この例では、カートごとにテーブルがあり、アイテムごとに別のテーブルがあるカートシステムを実装します。 1つのカートに多くのアイテムを含めることができるため、ここでは1対多のマッピングを行います。

これがデータベースレベルで機能する方法は、cart_idcartテーブルの主キーとして使用し、cart_idの外部キーとして使用することです。 ]items

コードでそれを行う方法は、@OneToManyを使用することです。

データベース内の関係を反映する方法で、CartクラスをItemオブジェクトのコレクションにマップしてみましょう。

public class Cart {

    //...     
 
    @OneToMany(mappedBy="cart")
    private Set<Item> items;
	
    //...
}

@ManyToOne を使用して、各ItemCartへの参照を追加し、これを双方向の関係にすることもできます。 双方向とは、カートからアイテムにアクセスできること、およびアイテムからカートにアクセスできることを意味します。

mappedBy プロパティは、子クラスの親クラスを表すために使用している変数をHibernateに通知するために使用するものです。

1対多アソシエーションを実装するサンプルHibernateアプリケーションを開発するために、次のテクノロジーとライブラリが使用されます。

  • JDK1.8以降
  • Hibernate 5
  • Maven3以降
  • H2データベース

3. 設定

3.1. データベースの設定

Hibernateを使用して、ドメインモデルからスキーマを管理します。 つまり、エンティティ間のさまざまなテーブルと関係を作成するためにSQLステートメントを提供する必要はありません。 それでは、Hibernateサンプルプロジェクトの作成に移りましょう。

3.2. Mavenの依存関係

まず、HibernateおよびH2ドライバーの依存関係をpom.xmlファイルに追加します。 Hibernate依存関係はJBossロギングを使用し、一時的な依存関係として自動的に追加されます。

  • Hibernateバージョン5.6.7.Final
  • H2ドライバーバージョン2.1.212

Hibernateの最新バージョンとH2の依存関係については、Maven中央リポジトリにアクセスしてください。

3.3. Hibernate SessionFactory

次に、データベースの相互作用のためにHibernate SessionFactoryを作成しましょう。

public static SessionFactory getSessionFactory() {

    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
      .applySettings(dbSettings())
      .build();

    Metadata metadata = new MetadataSources(serviceRegistry)
      .addAnnotatedClass(Cart.class)
      // other domain classes
      .buildMetadata();

    return metadata.buildSessionFactory();
}

private static Map<String, String> dbSettings() {
    // return Hibernate settings
}

4. モデル

マッピング関連の構成は、モデルクラスのJPAアノテーションを使用して行われます。

@Entity
@Table(name="CART")
public class Cart {

    //...

    @OneToMany(mappedBy="cart")
    private Set<Item> items;
	
    // getters and setters
}

@OneToMany アノテーションは、mappedBy変数をマップするために使用されるItemクラスのプロパティを定義するために使用されることに注意してください。 そのため、Itemクラスに「cart」という名前のプロパティがあります。

@Entity
@Table(name="ITEMS")
public class Item {
    
    //...
    @ManyToOne
    @JoinColumn(name="cart_id", nullable=false)
    private Cart cart;

    public Item() {}
    
    // getters and setters
}

@ManyToOneアノテーションがCartクラス変数に関連付けられていることに注意することも重要です。 @JoinColumn アノテーションは、マップされた列を参照します。

5. 動作中

テストプログラムでは、Hibernateセッションを取得するための main ()メソッドを使用してクラスを作成し、1対多の関連付けを実装するデータベースにモデルオブジェクトを保存しています。 :

sessionFactory = HibernateAnnotationUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
System.out.println("Session created");
	    
tx = session.beginTransaction();

session.save(cart);
session.save(item1);
session.save(item2);
	    
tx.commit();
System.out.println("Cart ID=" + cart.getId());
System.out.println("item1 ID=" + item1.getId()
  + ", Foreign Key Cart ID=" + item.getCart().getId());
System.out.println("item2 ID=" + item2.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());

これは、テストプログラムの出力です。

Session created
Hibernate: insert into CART values ()
Hibernate: insert into ITEMS (cart_id)
  values (?)
Hibernate: insert into ITEMS (cart_id)
  values (?)
Cart ID=7
item1 ID=11, Foreign Key Cart ID=7
item2 ID=12, Foreign Key Cart ID=7
Closing SessionFactory

6. @ManyToOneアノテーション

セクション2で見たように、 @ManyToOne アノテーションを使用して、多対1の関係を指定できます。 多対1マッピングは、このエンティティの多くのインスタンスが別のエンティティの1つのインスタンスにマップされることを意味します–1つのカート内の多くのアイテム

@ManyToOneアノテーションを使用すると、双方向の関係も作成できます。これについては、次のいくつかのサブセクションで詳しく説明します。

6.1. 矛盾と所有権

ここで、CartItemを参照しているが、ItemCartを参照していない場合、の関係は一方向になりますオブジェクトにも自然な一貫性があります。

ただし、この場合、関係は双方向であり、不整合の可能性があります。

開発者がitem1cart1インスタンスに追加し、 item2cart2インスタンスに追加したいが、 cart2item2の間の参照に一貫性がなくなるという間違い:

Cart cart1 = new Cart();
Cart cart2 = new Cart();

Item item1 = new Item(cart1);
Item item2 = new Item(cart2); 
Set<Item> itemsSet = new HashSet<Item>();
itemsSet.add(item1);
itemsSet.add(item2); 
cart1.setItems(itemsSet); // wrong!

上記のように、 item2cart2、を参照しますが、 cart2item2、を参照しません。これは悪いです。

Hibernateはitem2をデータベースにどのように保存する必要がありますか? item2外部キーはcart1またはcart2を参照しますか?

関係の所有側のアイデアを使用して、このあいまいさを解決します。 所有側に属する参照が優先され、データベースに保存されます。

6.2. 所有側としてのアイテム

セクション2.9のJPA仕様に記載されているように、多対1側を所有側としてマークすることをお勧めします。

つまり、 I tem が所有側になり、 Cart が逆側になります。これは、以前に行ったこととまったく同じです。

では、どのようにしてこれを達成したのでしょうか。

マップされたBy属性をCartクラスに含めることにより、それを逆側としてマークします。

同時に、Item。cartフィールドに@ManyToOneの注釈を付けて、Itemを所有側にします。

「不整合」の例に戻ると、Hibernateは、 item2の参照がより重要であり、item2の参照をデータベースに保存することを認識しています。

結果を確認しましょう:

item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=2

cartはスニペットでitem2を参照していますが、item2cart2への参照はデータベースに保存されています。

6.3. 所有側としてのカート

1対多側を所有側としてマークし、多対1側を逆側としてマークすることもできます。

これは推奨される方法ではありませんが、先に進んで試してみましょう。

以下のコードスニペットは、所有側としての1対多側の実装を示しています。

public class ItemOIO {
    
    //  ...
    @ManyToOne
    @JoinColumn(name = "cart_id", insertable = false, updatable = false)
    private CartOIO cart;
    //..
}

public class CartOIO {
    
    //..  
    @OneToMany
    @JoinColumn(name = "cart_id") // we need to duplicate the physical information
    private Set<ItemOIO> items;
    //..
}

mappedBy 要素を削除し、 many-to-one@JoinColumninsertableおよびupdatableとしてfalseに設定したことに注目してください。

同じコードを実行すると、結果は逆になります。

item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=1

上記のように、item2カートに属します。

7. 結論

JPAアノテーションを使用して、HibernateORMおよびH2データベースとの1対多関係を実装するのがいかに簡単であるかを見てきました。

さらに、双方向の関係と、所有側の概念を実装する方法について学びました。

この記事のソースコードは、GitHubにあります。