Hibernate入門記 コレクションその2 set で many-to-many しかも双方向
前回に引き続いて <set>
を扱いますが,今回は多対多に挑戦です.ついでに双方向も.
ということで,「6.3. Collections of Values and Many-To-Many Associations」とか「6.8. Bidirectional Associations」あたりの学習です.
Hibernateは,多対多関連も1対多関連と同じように,コレクションとして扱います.そのための指定をするのが
<set>
要素
なのですが,これは前回学習済み.問題ありません.たぶん.
その内容としては,やはり one-to-many
と同様に
<key>
要素
を記述します.これも前回学習済み.
それに続けて,<one-to-many>
要素の代わりに
<meny-to-many>
要素
を使って関連のマッピングを指定します.
この要素は,次の属性を持っています.
class
- コレクションの要素となるエンティティの型を指定します.
column
- 外部キーのカラム名です.リファレンスでは必須となっていますが,DTDでは任意です.どっち?
outer-join
- 外部結合を使用する場合に指定します.
DTDには他にも forign-key
という属性もあるようです.column
のレガシーな名前?
うーん,<many-to-many>
要素の column
属性と,<key>
要素の column
属性の関係は?
多対多の関連を表現するには,関連テーブルを使用します.関連テーブルは,二つのテーブルに対する外部キーを持ちます.そこで,
- こっち(自分)への外部キーを
<key>
要素のcolumn
属性で指定する. - あっち(相手)への外部キーを
<many-to-many>
要素のcolumn
属性で指定する.
ということのようです.
ふむふむ.1対多の場合もやはり,こっちへの外部キーを含むテーブル(関連先のエンティティそのもの)を <key>
要素の column
属性で指定しました.なるほど,一貫していますね.
おっと,関連エンティティのテーブルはどこで指定するのでしょうか? どうやら,
<set>
要素のtable
属性で指定する.
みたいですね.省略した場合はコレクションのプロパティ名が使われます.らじゃ.
多対多関連のマッピングについては,これくらいでどうにかなりそうです.
では,続いて双方向の関連について学習します.
まず,双方向の関連は1対多・多対多で使うことができるようです.ただし,多の側を参照するために使うことができるコレクションは,
- セット
- バッグ
の二つだけで,マップやリスト,配列はサポートされないとのこと.今はセットの学習中なので問題ありません.
そして,実際に双方向関連を使う場合に必要なのは,1対多の場合は多の側,多対多の場合は任意の一方の <set>
要素で inverse
属性に true
を指定するだけみたいです.
注意が必要なのは,多対多で inverse
属性に true
を指定するのは,関連づけられる二つの永続クラスのどちらか一方だけ,というところですね.
どちらの永続クラスの inverse
属性に true
を指定したかによって,関連テーブルの更新に影響があるようです.
仮にAとBという二つの永続クラスが多対多の関連を持ち,Aの inverse
属性に true
を指定した場合,Bを save()
しても関連テーブルはINSERT/UPDATEされないみたい.Aを save()
した場合のみ,関連テーブルが INSERT/UPDATE されるらしいのです.うーみゅ,これは注意が必要かも.
1対多の場合は,1の側にしか inverse
属性を指定できませんから,1の側の永続クラスを save()
した場合にのみ外部キーが更新されるということでしょう.この場合,外部キーは多の側のテーブルにあるわけですから,1の側のテーブルをINSERT/UPDATEするときに多の側のテーブルをUPDATEするという動きになると思われます.うーん,効率的といえるのか疑問...
ともあれ,必要な情報はそろった気がするのでお試ししましょう.
モデルと多対多の関係にあるものってなんでしょうか? とりあえず,掲載されている雑誌でしょうか.新鮮味に欠けるネタですが,それでやってみますか.
ということで,テーブルを用意します.
CREATE TABLE MODEL ( ID INTEGER IDENTITY, NAME VARCHAR ) CREATE TABLE MAGAZINE ( ID INTEGER IDENTITY, NAME VARCHAR ) CREATE TABLE MODEL_MAGAZINE ( MODEL INTEGER, MAGAZINE )
え? 外部キー制約を付けないのかって? いやその,ずっと忘れていたのです,あうあう.だからこれからもつけません! 心より恥じる.
次行きますよっ! 次,次!!(エビちゃん風)
モデルのクラスです.
package study; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import net.sf.hibernate.CallbackException; import net.sf.hibernate.Lifecycle; import net.sf.hibernate.Session; public class Model implements Lifecycle { int id = -1; String name; Set magazine; public Model() { } public Model(String name) { this.name = name; this.magazine = new HashSet(); } public String toString() { StringBuffer buf = new StringBuffer(); buf.append(name).append(" ["); Iterator it = magazine.iterator(); while (it.hasNext()) { Magazine magazine = (Magazine) it.next(); buf.append(magazine.name).append(", "); } buf.setLength(buf.length() - 2); buf.append("]"); return new String(buf); } public void onLoad(Session s, Serializable id) { System.out.println("onLoad() : " + toString()); } public boolean onSave(Session s) throws CallbackException { System.out.println("onSave() : " + toString()); return false; } public boolean onUpdate(Session s) throws CallbackException { System.out.println("onUpdate() : " + toString()); return false; } public boolean onDelete(Session s) throws CallbackException { System.out.println("onDelete() : " + toString()); return false; } }
モデルのマッピングファイル.
<?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="Model"> <id name="id" access="field" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name" access="field"/> <set name="magazine" access="field" inverse="true" table="MODEL_MAGAZINE"> <key column="model"/> <many-to-many class="Magazine" column="magazine"/> </set> </class> </hibernate-mapping>
そして雑誌のクラス.
package study; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import net.sf.hibernate.CallbackException; import net.sf.hibernate.Lifecycle; import net.sf.hibernate.Session; public class Magazine implements Lifecycle { int id = -1; String name; Set model; public Magazine() { } public Magazine(String name) { this.name = name; this.model = new HashSet(); } public String toString() { StringBuffer buf = new StringBuffer(); buf.append(name).append(" ["); Iterator it = model.iterator(); while (it.hasNext()) { Model model = (Model) it.next(); buf.append(model.name).append(", "); } buf.setLength(buf.length() - 2); buf.append("]"); return new String(buf); } public void onLoad(Session s, Serializable id) { System.out.println("onLoad() : " + toString()); } public boolean onSave(Session s) throws CallbackException { System.out.println("onSave() : " + toString()); return false; } public boolean onUpdate(Session s) throws CallbackException { System.out.println("onUpdate() : " + toString()); return false; } public boolean onDelete(Session s) throws CallbackException { System.out.println("onDelete() : " + toString()); return false; } }
雑誌のマッピングファイル.
<?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="Magazine"> <id name="id" access="field" unsaved-value="-1"> <generator class="identity"/> </id> <property name="name" access="field"/> <set name="model" access="field" table="MODEL_MAGAZINE"> <key column="magazine"/> <many-to-many class="Model" column="model"/> </set> </class> </hibernate-mapping>
あと,hibernate.cfg.xml
で読み込むマッピングファイルについて,前回の study/Cm.hbm.xml を study/Magazine.hbm.xml に変更しておきます.
そして実行用のクラス.
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(); Magazine cancam = new Magazine("CanCam"); Magazine vivi = new Magazine("ViVi"); Magazine classy = new Magazine("CLASSY"); Model yuri = new Model("蛯原友里"); yuri.magazine.add(cancam); cancam.model.add(yuri); Model sayo = new Model("相沢紗世"); sayo.magazine.add(vivi); vivi.model.add(sayo); sayo.magazine.add(classy); classy.model.add(sayo); Model yu = new Model("山田優"); yu.magazine.add(cancam); cancam.model.add(yu); session.save(cancam); session.save(vivi); session.save(classy); session.save(yuri); session.save(sayo); session.save(yu); session.flush(); session = factory.openSession(); Iterator it = session.find("from study.Model").iterator(); while (it.hasNext()) { System.out.println(it.next()); } it = session.find("from study.Magazine").iterator(); while (it.hasNext()) { System.out.println(it.next()); } } catch (Throwable e) { e.printStackTrace(); } } }
CLASSYみたいな上流階級な雑誌は読んでいないので,紗世ちゃんが今も登場しているのか知らないのですが,とりあえずということで.
こいつを実行!!
onSave() : CanCam [山田優, 蛯原友里] Hibernate: insert into Magazine (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : ViVi [相沢紗世] Hibernate: insert into Magazine (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : CLASSY [相沢紗世] Hibernate: insert into Magazine (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 蛯原友里 [CanCam] Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 相沢紗世 [CLASSY, ViVi] Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() onSave() : 山田優 [CanCam] Hibernate: insert into Model (name, id) values (?, null) Hibernate: CALL IDENTITY() Hibernate: insert into MODEL_MAGAZINE (magazine, model) values (?, ?) Hibernate: select model0_.id as id, model0_.name as name from Model model0_ Hibernate: select magazine0_.magazine as magazine__, magazine0_.model as model__ from MODEL_MAGAZINE magazine0_ where magazine0_.model=? Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ from Magazine magazine0_ where magazine0_.id=? Hibernate: select model0_.model as model__, model0_.magazine as magazine__ from MODEL_MAGAZINE model0_ where model0_.magazine=? onLoad() : CanCam [null, 蛯原友里] onLoad() : 蛯原友里 [CanCam] Hibernate: select magazine0_.magazine as magazine__, magazine0_.model as model__ from MODEL_MAGAZINE magazine0_ where magazine0_.model=? Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ from Magazine magazine0_ where magazine0_.id=? Hibernate: select model0_.model as model__, model0_.magazine as magazine__ from MODEL_MAGAZINE model0_ where model0_.magazine=? onLoad() : ViVi [相沢紗世] Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ from Magazine magazine0_ where magazine0_.id=? Hibernate: select model0_.model as model__, model0_.magazine as magazine__ from MODEL_MAGAZINE model0_ where model0_.magazine=? onLoad() : CLASSY [相沢紗世] onLoad() : 相沢紗世 [CLASSY, ViVi] Hibernate: select magazine0_.magazine as magazine__, magazine0_.model as model__ from MODEL_MAGAZINE magazine0_ where magazine0_.model=? onLoad() : 山田優 [CanCam] 蛯原友里 [CanCam] 相沢紗世 [CLASSY, ViVi] 山田優 [CanCam] Hibernate: select magazine0_.id as id, magazine0_.name as name from Magazine magazine0_ CanCam [山田優, 蛯原友里] ViVi [相沢紗世] CLASSY [相沢紗世]
\(^o^)/
動きを見ていると,Session#flush()
を呼び出すと関連テーブル(MODEL_MAGAZINE)へのINSERTが行われるようです.
SELECTがずいぶんたくさん発行されてるのが気になりますね... こういうもの? このあたりはいずれ「14. Improving performance」で解説されると期待して,今は忘れることにしますか.
なんか吐血しないと入門した気がしないわけですが,今日はこれくらいでお終いにします.
心より恥じる. ←吐血していないのになぜ恥じる?>自分