Hibernate 入門記 コレクションその10 idbag で many-to-many
今回はバッグのもう一つ,
<idbag>
要素
について学習します.
前回の <bag>
要素と何が違うのかというと... うーみゅ...
Hibernate では,複合キーはレガシー扱いで業務的な意味を持たない単なる ID (代理キーというらしい?)を主キーとすることを推奨しています.
しかしながら,関連テーブルについても代理キーを使うべきかは議論の余地がある,とのことです.実際,関連テーブルには代理キーは不要(二つの外部キーからなる複合キーを主キーにすればよい)です.
しかし,必要なら関連テーブルに代理キーを持たせることも出来て,そのマッピングに使うのが <idbag>
ということらしいです...
なんてこった... やってもうた.(ToT)
確かにコレクションの始めの頃(<set>
要素の頃とか)は ID
カラムを持たない関連テーブルを使っていましたが,いつの頃からかこの入門記では,関連テーブルにも ID
カラムが当然のようについていました... 心より恥じる.
安易にコピペしたからだよなぁ.しかも何も疑問に思っていなかったかも.
あれ? そうでもないぞ.前回の <bag>
要素では,ID
カラムがないと外部キーだけではユニークにならないんですけど? インデックスだけ作って主キーを付けなければよかったのか?
ともあれ,Hibernate に関連テーブルの主キーを扱ってもらうには,<idbag>
要素を使わなくてはならないようです.前回の場合,IDENTITY
なカラムだったので HSQLDB が勝手に値を設定してくれていたというだけで,Hibernate はそんなカラムがあることさえしらなかったのですね.やっぱり心より恥じる.
次行きますよっ!次,次!(エビちゃん風)
ということで <idbag>
要素なんですが,属性は <bag>
要素と同じです.
内容としては,<key>
要素の前に
<collection-id>
要素
で関連テーブルの主キーに関するマッピング情報を記述します.
こいつの属性は次の通り.
column
- 主キーのカラム名を指定します.
type
- 主キーの型を指定します.
いずれも必須の属性です.
そして <collection-id>
要素の内容には,
<generator>
要素
を記述します.遠い昔(いつだ?)に学習した,主キーの値を生成するアレです.
なお,このドキュメントによると identity
ジェネレータはサポートされていないとのことです.あらら.
ということでお試しします.
前回のテーブルにも ID
のカラムはあったのですが,今回は IDENTITY
の属性を外して Hibernate に値を設定してもらいましょう.
ということでテーブル定義はこんな感じ.
CREATE TABLE HAYAMIMI ( ID INTEGER IDENTITY, WEEK VARCHAR ) CREATE TABLE MODEL ( ID INTEGER IDENTITY, NAME VARCHAR ) CREATE TABLE APPEARANCE ( ID INTEGER, WEEK INTEGER, MODEL INTEGER, PRIMARY KEY (ID) )
そして早耳の永続クラスは前回と同じで,マッピングファイルを次のように修正.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" > <hibernate-mapping auto-import="false" package="study"> <class name="Hayamimi"> <id name="id" access="field" unsaved-value="-1"> <generator class="identity"/> </id> <property name="week" access="field"/> <idbag name="model" access="field" table="appearance"> <collection-id column="id" type="integer"> <generator class="increment"/> </collection-id> <key column="week"/> <many-to-many column="model" class="Model"/> </idbag> </class> </hibernate-mapping>
ジェネレータは面倒なので increment
を使いました.
モデルの永続クラスとマッピングファイルは前回と同じ.
実行用のクラスも前回と同じでもいいのですが,週が変わってしまった(「今週」はもう「先週」)のが気になるので次のように修正.
package study; import java.util.Iterator; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.cfg.Configuration; public class Main { public static void main(String[] args) { try { Configuration config = new Configuration(); SessionFactory factory = config.configure().buildSessionFactory(); Session session = factory.openSession(); Model asami = new Model("あさ美"); session.save(asami); Model anne = new Model("杏"); session.save(anne); Model kana = new Model("香奈"); session.save(kana); Model sayo = new Model("紗世"); session.save(sayo); Model jun = new Model("潤"); session.save(jun); Model rosa = new Model("ローサ"); session.save(rosa); Hayamimi week1 = new Hayamimi("先々週"); week1.model.add(asami); week1.model.add(asami); week1.model.add(kana); week1.model.add(sayo); week1.model.add(asami); session.save(week1); Hayamimi week2 = new Hayamimi("先週"); week2.model.add(rosa); week2.model.add(jun); week2.model.add(anne); week2.model.add(kana); week2.model.add(asami); session.save(week2); session.flush(); session = factory.openSession(); Iterator it = session.find("from study.Hayamimi").iterator(); while (it.hasNext()) { System.out.println(it.next()); } } catch (Throwable e) { e.printStackTrace(); } } }
これを実行!!
onSave() : あさ美 Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 杏 Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 香奈 Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 紗世 Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 潤 Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : ローサ Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 早耳トレンドNo1 先々週 [あさ美, あさ美, 香奈, 紗世, あさ美] Hibernate: insert into Hayamimi (week, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 早耳トレンドNo1 先週 [ローサ, 潤, 杏, 香奈, あさ美] Hibernate: insert into Hayamimi (week, id) values (?, null) Hibernate: CALL IDENTITY() Hibernate: insert into appearance (week, id, model) values (?, ?, ?) Hibernate: select hayamimi0_.id as id, hayamimi0_.week as week from Hayamimi hayamimi0_ onLoad() : 早耳トレンドNo1 先々週 net.sf.hibernate.collection.IdentifierBag@14e4e31 onLoad() : 早耳トレンドNo1 先週 net.sf.hibernate.collection.IdentifierBag@1ef7de4 Hibernate: select model0_.model as model__, model0_.week as week__, model0_.id as id__ from appearance model0_ where model0_.week=? Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : ローサ Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : 潤 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : 杏 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : 香奈 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : あさ美 Hibernate: select model0_.model as model__, model0_.week as week__, model0_.id as id__ from appearance model0_ where model0_.week=? Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=? onLoad() : 紗世 早耳トレンドNo1 先々週 net.sf.hibernate.collection.IdentifierBag@14e4e31 早耳トレンドNo1 先週 net.sf.hibernate.collection.IdentifierBag@1ef7de4
ぐはぁっ,なんやねん IdentifierBag
って?
別にいいけどさぁ,toString()
ちゃんと実装してくれよぉ.
しょうがないので Hayamimi
クラスの toString()
で頑張ることにして再度実行!!(最後の所だけ)
早耳トレンドNo1 先々週 [あさ美, あさ美, 香奈, 紗世, あさ美] 早耳トレンドNo1 先週 [ローサ, 潤, 杏, 香奈, あさ美]
前回の実行結果と比べてみると,関連テーブルへの INSERT で前回は ID
に値が設定されていなかったものが,今回は設定されるようになりました.めでたし,めでたし.
ということで,コレクションは一通り完了!! \(^o^)/
と思ったのもつかの間,いくつかやり損ねているものがあるようです.遅延ロードとか.心より恥じる.
ということで,次回は6章をきっちり締めくくれるように,これまでで取りこぼしたものをまとめて学習したいと思います.