Home日記コラム書評HintsLinks自己紹介
 

Hibernate (1)

※注意: このドキュメントは執筆途中のため、 意味の通じない箇所等が至るところにありますが、 あらかじめご了承ください。

Hibernate (1)

Hibernate というのはあちこちで話題になっているので細かいことは省略する。 というか、既知のものとする。

Note

要するに Java 系の O/R mapping。
O/R mapping について解説しておく。 O はオブジェクト、R は Relational hogehoge ということで、 Java の場合、オブジェクトに対応するものはクラス、 Relational model には RDB が対応する。
リレーショナルデータベースというのは、 縦横の表をイメージしてもらうといい。 横に項目一覧(カラム)が並んでいて、 縦に行が並ぶ。 1つの行がデータに対応している。
これを使ってどのようにデータを表現するかというのはデータベース系の一般的な解説書を読めば書いてあるので省略する。 ここで問題は、 データの性質によっては、RDBに格納するのが難しいことがある、ということだ。 実際は、難しいが不可能ではない、というケースが大半だと思うが、 かなりの工夫が必要になることがある。 Hibernate を使えば、 その工夫をある程度自動的にプログラムがやってくれるのだ。

collection

Hibernate のマニュアルには、 猫の例が出てくるのが有名だが、 Collection examples には Parent-Child の例が出ているから、 まず、それをそのまま素直に使ってみよう。

Hibernate は、 クラスとRDBとのマッピングファイルを使ってO/R mapping を実現している。 このマッピングファイルは XML で記述するのだが、 名前が .hbm.xml という文字列で終わるようにするのが慣例である。 ということで、今回のマッピングファイルは、 Family.hbm.xml という名前にしてみる。 内容は次のようになる。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
   <class name="com.phinloda.practice.hibernate.Parent">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="com.phinloda.practice.hibernate.Child"/>
        </set>
    </class>

    <class name="com.phinloda.practice.hibernate.Child">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <property name="name" type="string" />
    </class>
    
</hibernate-mapping>

Note

generator の class は、 サンプルでは sequence となっているが、 今回は increment にしている。 こちらの環境では MySQL を使っているのだが、 sequence だとうまく動作しなかったのだ。

これに対する Java のクラスは次のようになる。 コメントにも出てくるが、 このクラスは実は Hibernate の code generator を使って自動生成したものだ。

package com.phinloda.practice.hibernate;

import java.io.Serializable;
import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder;

/** @author Hibernate CodeGenerator */
public class Parent implements Serializable {

    /** identifier field */
    private int id;

    /** persistent field */
    private Set children;

    /** full constructor */
    public Parent(Set children) {
        this.children = children;
    }

    /** default constructor */
    public Parent() {
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Set getChildren() {
        return this.children;
    }

    public void setChildren(Set children) {
        this.children = children;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }
}
package com.phinloda.practice.hibernate;

import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;


/** @author Hibernate CodeGenerator */
public class Child implements Serializable {

    /** identifier field */
    private int id;

    /** nullable persistent field */
    private String name;

    /** full constructor */
    public Child(String name, String kind) {
        this.name = name;
    }

    /** default constructor */
    public Child() {
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }

}

このクラスをクラス図にしてみると、こんな感じ。

--------------
  Parent
--------------
- id : long
- children : set
--------------
+ getId() : long
+ setId(id : int) : void
+ getChildren() : Set
+ setChildren(children : Set) : void
-----------------------
--------------
  Child
--------------
- id : long
- name : string
--------------
+ getId() : long
+ setId(id : int) : void
+ getName() : String
+ setName(name : String) : void
-----------------------
---

Collection (2)

Child に parent を指定できるように拡張した例が出ているので、 動作を確認してみよう。 Mapping file を追加する。

Family2.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
   <class name="com.phinloda.practice.hibernate.Parent2">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="com.phinloda.practice.hibernate.Child2"/>
        </set>
    </class>

    <class name="com.phinloda.practice.hibernate.Child2">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <property name="name" type="string" />
        <many-to-one name="parent" class="com.phinloda.practice.hibernate.Parent2"
             column="parent_id" not-null="true"/>
    </class>
    
</hibernate-mapping>

Family.hbm.xml をちょっとだけ変更した。 Parent、Child という名前をそのまま使ってもいいのだが、 その場合は前の例が動かなくなるので、 別の名前でマッピングファイルを追加した。 この場合、 hibernate.cfg.xml にも変更が必要になる。

Buildfile: D:\java\eclipse\workspace\hibernate\build.xml
codegen:
 [hbm2java] Processing 1 mapping files.
 [hbm2java] 05:18:53,413  INFO Generator:96 - Generating 3 in D:\java\eclipse\workspace\hibernate\src
BUILD SUCCESSFUL
Total time: 3 seconds

ant schema を実行。

Buildfile: D:\java\eclipse\workspace\hibernate\build.xml
prepare:
compile:
schema:
[schemaexport] 21:08:28,968  INFO Environment:483 - Hibernate 2.1.8
[schemaexport] 21:08:28,988  INFO Environment:517 - loaded properties from resource hibernate.properties: {hibernate.connection.username=********, hibernate.connection.password=********, hibernate.cglib.use_reflection_optimizer=false, hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect, hibernate.connection.url=jdbc:mysql://localhost/practice, hibernate.connection.driver_class=com.mysql.jdbc.Driver}
[schemaexport] 21:08:28,998  INFO Environment:572 - using JDK 1.4 java.sql.Timestamp handling
[schemaexport] 21:08:29,048  INFO Configuration:170 - Mapping file: D:\java\eclipse\workspace\hibernate\classes\com\phinloda\practice\hibernate\Family.hbm.xml
[schemaexport] 21:08:31,010  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Parent -> Parent
[schemaexport] 21:08:31,481  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Child -> Child
[schemaexport] 21:08:31,481  INFO Configuration:170 - Mapping file: D:\java\eclipse\workspace\hibernate\classes\com\phinloda\practice\hibernate\Family2.hbm.xml
[schemaexport] 21:08:32,803  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Parent2 -> Parent2
[schemaexport] 21:08:32,813  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Child2 -> Child2
[schemaexport] 21:08:32,903  INFO Dialect:86 - Using dialect: net.sf.hibernate.dialect.MySQLDialect
[schemaexport] 21:08:32,903  INFO Configuration:641 - processing one-to-many association mappings
[schemaexport] 21:08:32,933  INFO Binder:1181 - Mapping collection: com.phinloda.practice.hibernate.Parent.children -> Child
[schemaexport] 21:08:32,933  INFO Binder:1181 - Mapping collection: com.phinloda.practice.hibernate.Parent2.children -> Child2
[schemaexport] 21:08:32,933  INFO Configuration:650 - processing one-to-one association property references
[schemaexport] 21:08:32,943  INFO Configuration:675 - processing foreign key constraints
[schemaexport] 21:08:33,073  INFO Configuration:641 - processing one-to-many association mappings
[schemaexport] 21:08:33,214  INFO Configuration:650 - processing one-to-one association property references
[schemaexport] 21:08:33,214  INFO Configuration:675 - processing foreign key constraints
[schemaexport] 21:08:33,234  INFO SchemaExport:98 - Running hbm2ddl schema export
[schemaexport] 21:08:33,234  INFO SchemaExport:117 - exporting generated schema to database
[schemaexport] 21:08:33,254  INFO DriverManagerConnectionProvider:42 - Using Hibernate built-in connection pool (not for production use!)
[schemaexport] 21:08:33,264  INFO DriverManagerConnectionProvider:43 - Hibernate connection pool size: 20
[schemaexport] 21:08:33,284  INFO DriverManagerConnectionProvider:77 - using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost/practice
[schemaexport] 21:08:33,284  INFO DriverManagerConnectionProvider:78 - connection properties: {user=********, password=********}
[schemaexport] alter table Child drop foreign key FK3E104FC7B66B0D0
[schemaexport] 21:08:34,385 DEBUG SchemaExport:132 - alter table Child drop foreign key FK3E104FC7B66B0D0
[schemaexport] alter table Child2 drop foreign key FK783F9AB67B66B0D0
[schemaexport] 21:08:34,596 DEBUG SchemaExport:132 - alter table Child2 drop foreign key FK783F9AB67B66B0D0
[schemaexport] drop table if exists Parent2
[schemaexport] 21:08:34,776 DEBUG SchemaExport:132 - drop table if exists Parent2
[schemaexport] drop table if exists Parent
[schemaexport] 21:08:34,786 DEBUG SchemaExport:132 - drop table if exists Parent
[schemaexport] drop table if exists Child
[schemaexport] 21:08:34,816 DEBUG SchemaExport:132 - drop table if exists Child
[schemaexport] drop table if exists Child2
[schemaexport] 21:08:34,816 DEBUG SchemaExport:132 - drop table if exists Child2
[schemaexport] create table Parent2 (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 21:08:34,826 DEBUG SchemaExport:149 - create table Parent2 (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] create table Parent (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 21:08:34,926 DEBUG SchemaExport:149 - create table Parent (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] create table Child (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] parent_id bigint,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 21:08:35,006 DEBUG SchemaExport:149 - create table Child (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] parent_id bigint,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] create table Child2 (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] parent_id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 21:08:35,126 DEBUG SchemaExport:149 - create table Child2 (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] parent_id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] alter table Child add index FK3E104FC7B66B0D0 (parent_id), add constraint FK3E104FC7B66B0D0 foreign key (parent_id) references Parent (id)
[schemaexport] 21:08:35,186 DEBUG SchemaExport:149 - alter table Child add index FK3E104FC7B66B0D0 (parent_id), add constraint FK3E104FC7B66B0D0 foreign key (parent_id) references Parent (id)
[schemaexport] alter table Child2 add index FK783F9AB67B66B0D0 (parent_id), add constraint FK783F9AB67B66B0D0 foreign key (parent_id) references Parent2 (id)
[schemaexport] 21:08:35,287 DEBUG SchemaExport:149 - alter table Child2 add index FK783F9AB67B66B0D0 (parent_id), add constraint FK783F9AB67B66B0D0 foreign key (parent_id) references Parent2 (id)
[schemaexport] 21:08:35,377  INFO SchemaExport:160 - schema export complete
[schemaexport] 21:08:35,387  INFO DriverManagerConnectionProvider:143 - cleaning up connection pool: jdbc:mysql://localhost/practice
BUILD SUCCESSFUL
Total time: 9 seconds

これに対するテストケースを書くのだが、 まず、コピペしてちゃちゃっと修正してみる。

package com.phinloda.practice.hibernate;

import java.util.HashSet;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import junit.framework.TestCase;

public class FamilyTest2 extends TestCase {

    public static void main(String[] args) {
        junit.textui.TestRunner.run(FamilyTest2.class);
    }

    protected void setUp() throws Exception {
        super.setUp();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public final void testAll() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        Transaction transaction = session.beginTransaction();

        try {
            
            Parent2 parent2 = new Parent2();
            parent2.setChildren(new HashSet());
            Child2 child2 = new Child2();
            child2.setName("John");
            child2.setParent(parent2);            
            parent2.getChildren().add(child2);

            session.save(parent2);
            session.save(child2);

            transaction.commit();
            
        } catch (HibernateException he) {
            he.printStackTrace();
            transaction.rollback();
            fail();
        }

        session.close();

    }
    
    public final void testSearch() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        List result = session.createQuery("from Child").list();
        showList(result);

        List result2 = session.createQuery(
                "from Child2 as child" +
                " where child.name='John'").list();
        showList(result2);
        
        session.close();

    }
    
        
    private void showList(List list) {
        if (list == null) {
            System.out.println("list is null");
            return;
        }
        
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).toString());
        }
    }
    
    private void showList2(List list) {
        if (list == null) {
            System.out.println("list is null");
            return;
        }
        
        for (int i = 0; i < list.size(); i++) {
            System.out.print(i);
            System.out.print(": ");
            Object [] rows = (Object[]) list.get(i);
            for (int j = 0; j < rows.length; j++) {
                System.out.print(rows[j].toString());                
            }
            System.out.println("");
        }
    }

}

Note

殆ど同じ処理となるクラスを複製して作っているわけで、 何か規定クラスを作ってサブクラスを変更した方が効率的なのだが (generation gap pattern)、 後でリファクタリングすることにして、 とりあえず動作するものを用意するのだ。

実行すると、こんな感じ。

log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select max(id) from Parent2
Hibernate: select max(id) from Child2
Hibernate: insert into Parent2 (id) values (?)
Hibernate: insert into Child2 (name, parent_id, id) values (?, ?, ?)
Hibernate: update Child2 set parent_id=? where id=?
Hibernate: select child0_.id as id9_, child0_.name as name9_ from Child child0_
Hibernate: select child2x0_.id as id11_, child2x0_.name as name11_, child2x0_.parent_id as parent3_11_ from Child2 child2x0_ where child2x0_.name='John'
com.phinloda.practice.hibernate.Child2@90832e[id=1]

これはこれでいいのだが、 1箇所変更して試してみよう。 session.save を呼び出す順序を、 次のように変更する。

            session.save(child2);
            session.save(parent2);

実行すると、 次のように Exception が発生する。

    ~(略)~
org.hibernate.PropertyValueException: not-null property references a null or transient value: com.phinloda.practice.hibernate.Child2.parent
    at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
    ~(略)~
    at com.phinloda.practice.hibernate.FamilyTest2.testAll(FamilyTest2.java:46)

発生しているのは、 次の箇所。

            session.save(child2);

前後の状況を確認してみたいので、 一時的に、 Child2.java の toString() メソッドを変更する。 このように Child2.java を直接変更してしまうと、 その後、 ant codegen で上書きされて、内容が消滅するかもしれない、 という意味で「一時的」と表現した。

    public String toString() {

        return new ToStringBuilder(this)
            .append("id", getId())
            .append("name", getName())
            .append("parent", getParent())
            .toString();
    }

FamilyTest2.java の、session.save の前にコードを追加する。

            System.err.println(child2.toString());

            session.save(child2);
            session.save(parent2);

Note

System.err を使うのは、 Hibernate のエラーメッセージが err を使って出力されるので、 順序を確定するため。 もちろん logging を使った方がいい。
com.phinloda.practice.hibernate.Child2@1884174[id=0,name=John,parent=com.phinloda.practice.hibernate.Parent2@ee6681[id=0]]

なるほど、 id=0 というのがおかしいようだ。 ということは、試しに parent2 のid を1にしてみる。

            parent2.setId(1);
            session.save(child2);
            session.save(parent2);

これでエラーは解消した。

エラーは解消したが、 id をこのように明示的に指定してもいいのだろうか? mysql をコマンドラインから起動してテーブルを見ると、 ちゃんと id は修正されて保存されている。

さて、これでテストはうまく行ったが、 このテストケースはあんまりだから、 リファクタリングをしておこう。 まず、 src の下に、 Util.java を作成する。

package com.phinloda.practice.hibernate;

import java.util.List;

public class Util {

    static public void showList(List list) {
        if (list == null) {
            System.out.println("list is null");
            return;
        }

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).toString());
        }
    }

}

FamilyTest2.java は、次のように showList の呼び出しを、 今作ったユーティリティクラスを使うように変更する。

    public final void testSearch() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        List result = session.createQuery("from Child").list();
        Util.showList(result);

        List result2 = session.createQuery(
                "from Child2 as child" +
                " where child.name='John'").list();
        Util.showList(result2);
        
        session.close();

    }

showList2 も Util 側に移動しておく。

package com.phinloda.practice.hibernate;

import java.util.List;

public class Util {

    static public void showList(List list) {
        if (list == null) {
            System.out.println("list is null");
            return;
        }

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).toString());
        }
    }

    static public void showList2(List list) {
        if (list == null) {
            System.out.println("list is null");
            return;
        }

        for (int i = 0; i < list.size(); i++) {
            System.out.print(i);
            System.out.print(": ");
            Object[] rows = (Object[]) list.get(i);
            for (int j = 0; j < rows.length; j++) {
                System.out.print(rows[j].toString());
            }
            System.out.println("");
        }
    }

}

Collection (3)

次は、HIBERNATE のマニュアルには

if you absolutely insist that this association should be unidirectional

と書いてある例。 マニュアルでは、次のようになっている。

<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

実際に使ったマッピングファイルは次のものである。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
   <class name="com.phinloda.practice.hibernate.Parent3">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="com.phinloda.practice.hibernate.Child3"/>
        </set>
    </class>

    <class name="com.phinloda.practice.hibernate.Child3">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <property name="name" type="string" />
    </class>
    
</hibernate-mapping>

ant codegen と、 ant schema を実行しておく。 特に問題は発生しないはず。

これに対するテストケース。 今回は既定クラスを作ることにする。 TestCaseBase.java を test ディレクトリの下に作成する。

package com.phinloda.practice.hibernate;

import org.hibernate.cfg.Configuration;

import junit.framework.TestCase;

public class TestCaseBase extends TestCase {

    public static void main(String[] args) {
        junit.textui.TestRunner.run(TestCaseBase.class);
    }

    protected void setUp() throws Exception {
        super.setUp();
        setCfg(new Configuration());
        getCfg().configure();

    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    private Configuration _cfg;

    public Configuration getCfg() {
        return _cfg;
    }

    public void setCfg(Configuration cfg) {
        this._cfg = cfg;
    }
}

FamilyTest3.java は、これを extends するように作成する。 とはいっても、結局 FamilyTest.java の殆どパクリという状態。 まだ工夫の余地がたくさんあるのだが、 とりあえず。 余談だが、 最近この「とりあえず」というのがアジャイルの極意ではないか、 と思ったりすることがある。

package com.phinloda.practice.hibernate;

import java.util.HashSet;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class FamilyTest3 extends TestCaseBase {

    public final void testAll() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        Transaction transaction = session.beginTransaction();

        try {

            Parent3 parent3 = new Parent3();
            
            parent3.setChildren(new HashSet());
            Child3 child3 = new Child3();
            child3.setName("taro");
            parent3.getChildren().add(child3);

            session.save(parent3);
            session.save(child3);

            child3 = new Child3();
            child3.setName("Hoge");
            parent3.getChildren().add(child3);

            session.save(parent3);
            session.save(child3);

            transaction.commit();

        } catch (HibernateException he) {
            he.printStackTrace();
            transaction.rollback();
            fail();
        }

        session.close();

    }

    public final void testSearch() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        List result = session.createQuery("from Child").list();
        Util.showList(result);

        List result2 = session.createQuery(
                "from Parent as parent"
                        + " inner join parent.children as child"
                        + " where child.name='Hoge'").list();
        Util.showList2(result2);

        session.close();

    }
}

これはこれで普通に動作するはず。

Collection (4)

次にマニュアルに出てくるのは many-to-many association の例だ。 実際に使ったマッピングファイルは次のようになった。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
   <class name="com.phinloda.practice.hibernate.Parent4">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <set name="children" table="childset4">
            <key column="parent_id"/>
            <many-to-many class="com.phinloda.practice.hibernate.Child4" column="child_id" />
        </set>
    </class>

    <class name="com.phinloda.practice.hibernate.Child4">
        <id name="id" type="long">
            <generator class="increment"/>
        </id>
        <property name="name" type="string" />
    </class>
    
</hibernate-mapping>

生成されたソースは次のようになっている。

package com.phinloda.practice.hibernate;

import java.io.Serializable;
import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder;


/** @author Hibernate CodeGenerator */
public class Parent4 implements Serializable {

    /** identifier field */
    private long id;

    /** persistent field */
    private Set children;

    /** full constructor */
    public Parent4(Set children) {
        this.children = children;
    }

    /** default constructor */
    public Parent4() {
    }

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Set getChildren() {
        return this.children;
    }

    public void setChildren(Set children) {
        this.children = children;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }

}
package com.phinloda.practice.hibernate;

import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;


/** @author Hibernate CodeGenerator */
public class Child4 implements Serializable {

    /** identifier field */
    private long id;

    /** nullable persistent field */
    private String name;

    /** full constructor */
    public Child4(String name) {
        this.name = name;
    }

    /** default constructor */
    public Child4() {
    }

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }

}

最初の例と変わらないようだが、 とりあえず次に進もう。

ant schema を実行すると、 次のように表示された。

Buildfile: D:\java\eclipse\workspace\hibernate\build.xml
prepare:
     [copy] Copying 1 file to D:\java\eclipse\workspace\hibernate\classes
compile:
    [javac] Compiling 8 source files to D:\java\eclipse\workspace\hibernate\classes
schema:
[schemaexport] 02:55:26,727  INFO Environment:483 - Hibernate 2.1.8
[schemaexport] 02:55:26,737  INFO Environment:517 - loaded properties from resource hibernate.properties: {hibernate.connection.username=********, hibernate.connection.password=********, hibernate.cglib.use_reflection_optimizer=false, hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect, hibernate.connection.url=jdbc:mysql://localhost/practice, hibernate.connection.driver_class=com.mysql.jdbc.Driver}
[schemaexport] 02:55:26,737  INFO Environment:572 - using JDK 1.4 java.sql.Timestamp handling
    ~(略)~
[schemaexport] 02:55:30,562  INFO Configuration:170 - Mapping file: D:\java\eclipse\workspace\hibernate\classes\com\phinloda\practice\hibernate\family4.hbm.xml
[schemaexport] 02:55:31,634  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Parent4 -> Parent4
[schemaexport] 02:55:31,634  INFO Binder:571 - Mapping collection: com.phinloda.practice.hibernate.Parent4.children -> childset4
[schemaexport] 02:55:31,644  INFO Binder:229 - Mapping class: com.phinloda.practice.hibernate.Child4 -> Child4
[schemaexport] 02:55:31,724  INFO Dialect:86 - Using dialect: net.sf.hibernate.dialect.MySQLDialect
[schemaexport] 02:55:31,734  INFO Configuration:641 - processing one-to-many association mappings
    ~(略)~
[schemaexport] 02:55:31,744  INFO Configuration:650 - processing one-to-one association property references
[schemaexport] 02:55:31,744  INFO Configuration:675 - processing foreign key constraints
[schemaexport] 02:55:31,864  INFO Configuration:641 - processing one-to-many association mappings
[schemaexport] 02:55:31,864  INFO Configuration:650 - processing one-to-one association property references
[schemaexport] 02:55:31,864  INFO Configuration:675 - processing foreign key constraints
[schemaexport] 02:55:31,884  INFO SchemaExport:98 - Running hbm2ddl schema export
[schemaexport] 02:55:31,884  INFO SchemaExport:117 - exporting generated schema to database
[schemaexport] 02:55:31,904  INFO DriverManagerConnectionProvider:42 - Using Hibernate built-in connection pool (not for production use!)
[schemaexport] 02:55:31,904  INFO DriverManagerConnectionProvider:43 - Hibernate connection pool size: 20
[schemaexport] 02:55:31,914  INFO DriverManagerConnectionProvider:77 - using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost/practice
[schemaexport] 02:55:31,914  INFO DriverManagerConnectionProvider:78 - connection properties: {user=********, password=********}
[schemaexport] alter table childset4 drop foreign key FKFA61D72E62EA171E
[schemaexport] 02:55:32,395 DEBUG SchemaExport:132 - alter table childset4 drop foreign key FKFA61D72E62EA171E
[schemaexport] 02:55:32,435 DEBUG SchemaExport:137 - Unsuccessful: alter table childset4 drop foreign key FKFA61D72E62EA171E
[schemaexport] 02:55:32,435 DEBUG SchemaExport:138 - Table 'practice.childset4' doesn't exist
[schemaexport] alter table childset4 drop foreign key FKFA61D72E7B66B0D0
[schemaexport] 02:55:32,435 DEBUG SchemaExport:132 - alter table childset4 drop foreign key FKFA61D72E7B66B0D0
[schemaexport] 02:55:32,445 DEBUG SchemaExport:137 - Unsuccessful: alter table childset4 drop foreign key FKFA61D72E7B66B0D0
[schemaexport] 02:55:32,445 DEBUG SchemaExport:138 - Table 'practice.childset4' doesn't exist
    ~(略)~
[schemaexport] drop table if exists childset4
[schemaexport] 02:55:32,736 DEBUG SchemaExport:132 - drop table if exists childset4
[schemaexport] drop table if exists Parent4
[schemaexport] 02:55:32,746 DEBUG SchemaExport:132 - drop table if exists Parent4
    ~(略)~
[schemaexport] drop table if exists Child4
[schemaexport] 02:55:32,756 DEBUG SchemaExport:132 - drop table if exists Child4
    ~(略)~
[schemaexport] create table childset4 (
[schemaexport] parent_id bigint not null,
[schemaexport] child_id bigint not null,
[schemaexport] primary key (parent_id, child_id)
[schemaexport] )
[schemaexport] 02:55:32,766 DEBUG SchemaExport:149 - create table childset4 (
[schemaexport] parent_id bigint not null,
[schemaexport] child_id bigint not null,
[schemaexport] primary key (parent_id, child_id)
[schemaexport] )
    ~(略)~
[schemaexport] create table Parent4 (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 02:55:33,116 DEBUG SchemaExport:149 - create table Parent4 (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] )
    ~(略)~
[schemaexport] create table Child4 (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] primary key (id)
[schemaexport] )
[schemaexport] 02:55:33,366 DEBUG SchemaExport:149 - create table Child4 (
[schemaexport] id bigint not null,
[schemaexport] name varchar(255),
[schemaexport] primary key (id)
[schemaexport] )
    ~(略)~
[schemaexport] alter table childset4 add index FKFA61D72E62EA171E (child_id), add constraint FKFA61D72E62EA171E foreign key (child_id) references Child4 (id)
[schemaexport] 02:55:33,677 DEBUG SchemaExport:149 - alter table childset4 add index FKFA61D72E62EA171E (child_id), add constraint FKFA61D72E62EA171E foreign key (child_id) references Child4 (id)
[schemaexport] alter table childset4 add index FKFA61D72E7B66B0D0 (parent_id), add constraint FKFA61D72E7B66B0D0 foreign key (parent_id) references Parent4 (id)
[schemaexport] 02:55:33,807 DEBUG SchemaExport:149 - alter table childset4 add index FKFA61D72E7B66B0D0 (parent_id), add constraint FKFA61D72E7B66B0D0 foreign key (parent_id) references Parent4 (id)
    ~(略)~
[schemaexport] 02:55:34,138  INFO SchemaExport:160 - schema export complete
[schemaexport] 02:55:34,148  INFO DriverManagerConnectionProvider:143 - cleaning up connection pool: jdbc:mysql://localhost/practice
BUILD SUCCESSFUL
Total time: 9 seconds

ポイントは childset4 というテーブルで、 これを使って many-to-many の関係が表現されているのである。

hibernate プロジェクトを refresh して、 テストケースを作成する。

package com.phinloda.practice.hibernate;

import java.util.HashSet;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class FamilyTest4 extends TestCaseBase {
    
    public final void testAll() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        Transaction transaction = session.beginTransaction();

        try {

            Parent4 parent4_1 = new Parent4();
            
            parent4_1.setChildren(new HashSet());
            Child4 child4 = new Child4();
            child4.setName("taro");
            parent4_1.getChildren().add(child4);

            session.save(parent4_1);
            session.save(child4);

            Parent4 parent4_2 = new Parent4();
            parent4_2.setChildren(new HashSet());
            parent4_2.getChildren().add(child4);
            
            session.save(parent4_2);
            session.save(child4);

            transaction.commit();

        } catch (HibernateException he) {
            he.printStackTrace();
            transaction.rollback();
            fail();
        }

        session.close();

    }

    public final void testSearch() {
        Configuration cfg = new Configuration();
        cfg.configure();

        SessionFactory factory = cfg.buildSessionFactory();
        Session session = factory.openSession();

        List result = session.createQuery("from Child4").list();
        Util.showList(result);

        List result2 = session.createQuery(
                "from Parent4 as parent"
                        + " inner join parent.children as child"
                        + " where child.name='taro'").list();
        Util.showList2(result2);

        session.close();

    }

}

Collection (5)

※UNDER CONSTRUCTION -- この箇所、執筆中です