Hibernateの関連について
インデックスへ戻る データベーススキーマの作成サンプルとして以下のようなテーブルスキーマを作成します。Clayを利用してsqlも自動生成しちゃいます。 Categoryは1対多による自己関連で、CategoryとItemは1対多の関連です。 ![]() クラス図で表すと以下のようになります。ちなみに関連は双方向です。 ![]() MiddlegenIDEでマッピングファイルを自動生成するここでは、Cascadeをall-delete-orphanに設定しています。categoryとitemの関連も同じです。 ![]() 以下のようにPOJO、マッピングファイル、hibernate設定ファイルが生成されています。 ![]() 生成されたマッピング定義ファイル Category.hbm.xml のitemとの関連を定義する部分に条件(order-by、where)を追加してみます。
<set
name="items"
lazy="true"
inverse="true"
cascade="all-delete-orphan"
order-by="ITEM_ID asc" <-- ITEMテーブルをITEM_IDの昇順に並べる
where="DELETE_DATE IS NULL" <-- 論理的に削除されているレコードは対象外
>
<key>
<column name="CATEGORY_ID" />
</key>
<one-to-many
class="com.powerdee.model.Item"
/>
</set>
Categoryに足場コードを追加する生成されたPOJOのCategory.javaに1対多関連の足場コードを追加します。これは関係の管理を容易にします。
public void addItem(Item item) {
if (item == null)
throw new IllegalArgumentException("Can't add a null Category as item.");
if (item.getCategory() != null)
item.getCategory().getItems().remove(item);
item.setCategory(this);
this.getItems().add(item);
}
private void setItems(Set items) { <-- 可視性をpublicからprivateへ変更
this.items = items;
}
public void addChildCategory(Category category) {
if (category == null)
throw new IllegalArgumentException("Can't add a null Category.");
if (category.getCategory() != null)
category.getCategory().getCategories().remove(category);
category.setCategory(this);
this.getCategories().add(category);
}
private void setCategories(Set categories) {
this.categories = categories;
}
意外とはまりやすいのが、多側のプロパティを生成しておくこと。わすれると足場コードでNullPointerExceptionです。
/** persistent field */
private Set items = new LinkedHashSet();
/** persistent field */
private Set categories = new LinkedHashSet();
カスケードをテストしてみる。
まず、testInsertCategory()で親カテゴリを永続化しています。(★1)
package com.powerdee.model;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class Hibernate2test extends TestCase {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory
= new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
return sessionFactory.openSession();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(Hibernate2test.class);
}
public void testInsertCategory() throws Exception {
Session session = getSession();
Item item1 = new Item();
item1.setName("アイテムその1");
Item item2 = new Item();
item2.setName("アイテムその2");
Item item3 = new Item();
item3.setName("アイテムその3");
Category childCategory = new Category();
childCategory.setName("子カテゴリ");
childCategory.addItem(item1); <-- 子カテゴリにアイテムその1を追加
childCategory.addItem(item2); <-- 子カテゴリにアイテムその2を追加
childCategory.addItem(item3); <-- 子カテゴリにアイテムその3を追加
Category parentCategory = new Category();
parentCategory.setName("親カテゴリ");
parentCategory.addChildCategory(childCategory); <-- 親カテゴリに子カテゴリを追加
Transaction transaction = session.beginTransaction();
session.save(parentCategory); <-- ★1親カテゴリを永続化!
transaction.commit();
session.close();
}
public void testSelectDeleteCategory() throws Exception {
Session session = getSession();
Transaction transaction = session.beginTransaction();
String HQL
= "from Category cat where cat.category.categoryId is null";
Query query = session.createQuery(HQL);
List categoryList = query.list();
Category parentCategory = (Category) categoryList.get(0);
assertEquals("親カテゴリ", parentCategory.getName());
Iterator categories = parentCategory.getCategories().iterator();
Category childCategory = (Category) categories.next();
assertEquals("子カテゴリ", childCategory.getName());
Iterator items = childCategory.getItems().iterator();
assertEquals("アイテムその1", ((Item) items.next()).getName());
assertEquals("アイテムその2", ((Item) items.next()).getName());
assertEquals("アイテムその3", ((Item) items.next()).getName());
session.delete(parentCategory); <-- ★2これで全て削除される!
transaction.commit();
session.close();
}
}
論理削除済のアイテムその4を追加してみます。
package com.powerdee.model;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class Hibernate2test extends TestCase {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory
= new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
return sessionFactory.openSession();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(Hibernate2test.class);
}
public void testInsertCategory() throws Exception {
Session session = getSession();
Item item1 = new Item();
item1.setName("アイテムその1");
Item item2 = new Item();
item2.setName("アイテムその2");
Item item3 = new Item();
item3.setName("アイテムその3");
Item item4 = new Item();
item4.setName("アイテムその4");
item4.setDeleteDate(new Date()); <-- 削除日を入力
Category childCategory = new Category();
childCategory.setName("子カテゴリ");
childCategory.addItem(item1);
childCategory.addItem(item2);
childCategory.addItem(item3);
childCategory.addItem(item4);
Category parentCategory = new Category();
parentCategory.setName("親カテゴリ");
parentCategory.addChildCategory(childCategory);
Transaction transaction = session.beginTransaction();
session.save(parentCategory);
transaction.commit();
session.close();
}
public void testSelectDeleteCategory() throws Exception {
Session session = getSession();
Transaction transaction = session.beginTransaction();
String HQL
= "from Category cat where cat.category.categoryId is null";
Query query = session.createQuery(HQL);
List categoryList = query.list();
Category parentCategory = (Category) categoryList.get(0);
assertEquals("親カテゴリ", parentCategory.getName());
Iterator categories = parentCategory.getCategories().iterator();
Category childCategory = (Category) categories.next();
assertEquals("子カテゴリ", childCategory.getName());
Iterator items = childCategory.getItems().iterator();
assertEquals("アイテムその1", ((Item) items.next()).getName());
assertEquals("アイテムその2", ((Item) items.next()).getName());
assertEquals("アイテムその3", ((Item) items.next()).getName());
session.delete(parentCategory);
transaction.commit();
session.close();
}
}
すると、deleteで失敗してしまいます。ログを見ると・・・参照整合性違反ですね。
アイテムその4を取得せずに削除を行った為、子カテゴリが削除できないわけです。
71 logExceptions WARN org.hibernate.util.JDBCExceptionReporter
- SQL Error: 1217, SQLState: 23000
72 logExceptions ERROR org.hibernate.util.JDBCExceptionReporter
- Cannot delete or update a parent row: a foreign key constraint fails
遅延フェッチと外部結合(イーガーフェッチ)
先ほどのtestSelectDeleteCategory()のログを見ると以下のように個別にSELECT文が発行されているのがわかります。 select category0_.CATEGORY_ID as CATEGORY1_0_, category0_.NAME as NAME0_, category0_.INSERT_DATE as INSERT3_0_, category0_.UPDATE_DATE as UPDATE4_0_, category0_.DELETE_DATE as DELETE5_0_, category0_.PARENT_CATEGORY_ID as PARENT6_0_ from category category0_ where category0_.PARENT_CATEGORY_ID is null select categories0_.PARENT_CATEGORY_ID as PARENT6_1_, categories0_.CATEGORY_ID as CATEGORY1_1_, categories0_.CATEGORY_ID as CATEGORY1_0_0_, categories0_.NAME as NAME0_0_, categories0_.INSERT_DATE as INSERT3_0_0_, categories0_.UPDATE_DATE as UPDATE4_0_0_, categories0_.DELETE_DATE as DELETE5_0_0_, categories0_.PARENT_CATEGORY_ID as PARENT6_0_0_ from category categories0_ where categories0_.PARENT_CATEGORY_ID=? select items0_.CATEGORY_ID as CATEGORY8_1_, items0_.ITEM_ID as ITEM1_1_, items0_.ITEM_ID as ITEM1_1_0_, items0_.NAME as NAME1_0_, items0_.DESCRIPTION as DESCRIPT3_1_0_, items0_.PRICE as PRICE1_0_, items0_.INSERT_DATE as INSERT5_1_0_, items0_.UPDATE_DATE as UPDATE6_1_0_, items0_.DELETE_DATE as DELETE7_1_0_, items0_.CATEGORY_ID as CATEGORY8_1_0_ from item items0_ where items0_.DELETE_DATE IS NULL and items0_.CATEGORY_ID=? order by items0_.ITEM_ID asc select items0_.CATEGORY_ID as CATEGORY8_1_, items0_.ITEM_ID as ITEM1_1_, items0_.ITEM_ID as ITEM1_1_0_, items0_.NAME as NAME1_0_, items0_.DESCRIPTION as DESCRIPT3_1_0_, items0_.PRICE as PRICE1_0_, items0_.INSERT_DATE as INSERT5_1_0_, items0_.UPDATE_DATE as UPDATE6_1_0_, items0_.DELETE_DATE as DELETE7_1_0_, items0_.CATEGORY_ID as CATEGORY8_1_0_ from item items0_ where items0_.DELETE_DATE IS NULL and items0_.CATEGORY_ID=? order by items0_.ITEM_ID asc select categories0_.PARENT_CATEGORY_ID as PARENT6_1_, categories0_.CATEGORY_ID as CATEGORY1_1_, categories0_.CATEGORY_ID as CATEGORY1_0_0_, categories0_.NAME as NAME0_0_, categories0_.INSERT_DATE as INSERT3_0_0_, categories0_.UPDATE_DATE as UPDATE4_0_0_, categories0_.DELETE_DATE as DELETE5_0_0_, categories0_.PARENT_CATEGORY_ID as PARENT6_0_0_ from category categories0_ where categories0_.PARENT_CATEGORY_ID=? 以下のように外部結合フェッチを行うと1回のSELECTで親カテゴリ、子カテゴリ、アイテムのオブジェクト構造を取得することが可能です。
public void testEagerFetch() throws Exception {
Session session = getSession();
String HQL
= "from Category as c " +
"left join fetch c.categories " +
"left join fetch c.items " +
"where c.category.categoryId is null";
Query query = session.createQuery(HQL);
List categoryList = query.list();
assertEquals(1, categoryList.size());
Category parentCategory = (Category) categoryList.get(0);
assertEquals("親カテゴリ", parentCategory.getName());
Iterator categories = parentCategory.getCategories().iterator();
Category childCategory = (Category) categories.next();
assertEquals("子カテゴリ", childCategory.getName());
Iterator items = childCategory.getItems().iterator();
assertEquals("アイテムその1", ((Item) items.next()).getName());
assertEquals("アイテムその2", ((Item) items.next()).getName());
assertEquals("アイテムその3", ((Item) items.next()).getName());
session.close();
}
ログを見ると以下のようなSQLが発行されたようです。
select
category0_.CATEGORY_ID as CATEGORY1_0_0_,
categories1_.CATEGORY_ID as CATEGORY1_0_1_,
items2_.ITEM_ID as ITEM1_1_2_, category0_.NAME as NAME0_0_,
category0_.INSERT_DATE as INSERT3_0_0_, category0_.UPDATE_DATE as UPDATE4_0_0_,
category0_.DELETE_DATE as DELETE5_0_0_, category0_.PARENT_CATEGORY_ID as PARENT6_0_0_,
categories1_.NAME as NAME0_1_, categories1_.INSERT_DATE as INSERT3_0_1_,
categories1_.UPDATE_DATE as UPDATE4_0_1_, categories1_.DELETE_DATE as DELETE5_0_1_,
categories1_.PARENT_CATEGORY_ID as PARENT6_0_1_, categories1_.PARENT_CATEGORY_ID as PARENT6_0__,
categories1_.CATEGORY_ID as CATEGORY1_0__,
items2_.NAME as NAME1_2_, items2_.DESCRIPTION as DESCRIPT3_1_2_,
items2_.PRICE as PRICE1_2_, items2_.INSERT_DATE as INSERT5_1_2_,
items2_.UPDATE_DATE as UPDATE6_1_2_, items2_.DELETE_DATE as DELETE7_1_2_,
items2_.CATEGORY_ID as CATEGORY8_1_2_, items2_.CATEGORY_ID as CATEGORY8_1__,
items2_.ITEM_ID as ITEM1_1__
from
category category0_
left outer join category categories1_
on category0_.CATEGORY_ID=categories1_.PARENT_CATEGORY_ID
left outer join item items2_
on category0_.CATEGORY_ID=items2_.CATEGORY_ID and items2_.DELETE_DATE IS NULL
where
category0_.PARENT_CATEGORY_ID is null order by items2_.ITEM_ID asc
select
items0_.CATEGORY_ID as CATEGORY8_1_, items0_.ITEM_ID as ITEM1_1_,
items0_.ITEM_ID as ITEM1_1_0_, items0_.NAME as NAME1_0_,
items0_.DESCRIPTION as DESCRIPT3_1_0_, items0_.PRICE as PRICE1_0_,
items0_.INSERT_DATE as INSERT5_1_0_, items0_.UPDATE_DATE as UPDATE6_1_0_,
items0_.DELETE_DATE as DELETE7_1_0_, items0_.CATEGORY_ID as CATEGORY8_1_0_
from
item items0_
where
items0_.DELETE_DATE IS NULL and
items0_.CATEGORY_ID=?
order by
items0_.ITEM_ID asc
遅延ロードで複数回SELECTしてくるか、HQLで結合クエリを明示的に記述するかですかね
public void testEagerFetch() throws Exception {
Session session = getSession();
String HQL
= "from Category as c " +
"left join fetch c.categories " +
"left join fetch c.items"; <-- 条件を入れない
Query query = session.createQuery(HQL);
List categoryList = query.list();
assertEquals(4, categoryList.size()); <-- 親も関連する子の数だけ返されている。。
Category parentCategory = (Category) categoryList.get(0);
assertEquals("親カテゴリ", parentCategory.getName());
Iterator categories = parentCategory.getCategories().iterator();
Category childCategory = (Category) categories.next();
assertEquals("子カテゴリ", childCategory.getName());
Iterator items = childCategory.getItems().iterator();
assertEquals("アイテムその1", ((Item) items.next()).getName());
assertEquals("アイテムその2", ((Item) items.next()).getName());
assertEquals("アイテムその3", ((Item) items.next()).getName());
session.close();
}
select
category0_.CATEGORY_ID as CATEGORY1_0_0_,
categories1_.CATEGORY_ID as CATEGORY1_0_1_,
items2_.ITEM_ID as ITEM1_1_2_,
category0_.NAME as NAME0_0_,
category0_.INSERT_DATE as INSERT3_0_0_,
category0_.UPDATE_DATE as UPDATE4_0_0_,
category0_.DELETE_DATE as DELETE5_0_0_,
category0_.PARENT_CATEGORY_ID as PARENT6_0_0_,
categories1_.NAME as NAME0_1_,
categories1_.INSERT_DATE as INSERT3_0_1_,
categories1_.UPDATE_DATE as UPDATE4_0_1_,
categories1_.DELETE_DATE as DELETE5_0_1_,
categories1_.PARENT_CATEGORY_ID as PARENT6_0_1_,
categories1_.PARENT_CATEGORY_ID as PARENT6_0__,
categories1_.CATEGORY_ID as CATEGORY1_0__,
items2_.NAME as NAME1_2_,
items2_.DESCRIPTION as DESCRIPT3_1_2_,
items2_.PRICE as PRICE1_2_,
items2_.INSERT_DATE as INSERT5_1_2_,
items2_.UPDATE_DATE as UPDATE6_1_2_,
items2_.DELETE_DATE as DELETE7_1_2_,
items2_.CATEGORY_ID as CATEGORY8_1_2_,
items2_.CATEGORY_ID as CATEGORY8_1__,
items2_.ITEM_ID as ITEM1_1__
from
category category0_
left outer join category categories1_
on category0_.CATEGORY_ID=categories1_.PARENT_CATEGORY_ID
left outer join item items2_
on category0_.CATEGORY_ID=items2_.CATEGORY_ID
and items2_.DELETE_DATE IS NULL
order
by items2_.ITEM_ID asc
おすすめ書籍
|