Top > MyPage
 

hibernate 3.3で復習 一対多関係

hibernate3.3

hibernate3.3インストール

大型のJARが1つあるのではなく、現在では粒度の細かいJARが多数存在する。これにより、ユーザーの必要な構成に合わせて最小化できる。

ダウンロードはここ

hibernate3.3での変更

heibernate3.3からは、Sessionのconnectionメソッドを使ってJDBCのコネクションを取得することが非推奨になっていて

sess.connection().commit();

のようにしてトランザクションをコミットできない。

慣例的な書き方として、

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // 何らかの作業を行なう
    ...
    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    throw e;
}
finally {
    sess.close();
}

のような書き方がある。

テスト用データベースの用意

データベース

親のデータベースとしてstaffを、子のデータベースとしてstaffroleを用意した。

CREATE TABLE staff
(
  id serial NOT NULL,
  staff_name character varying(30),
  staff_password character varying(20),
  email character varying(30),
  CONSTRAINT staff_pkey PRIMARY KEY (id)
) 
WITHOUT OIDS;
ALTER TABLE staff OWNER TO chikkun;

CREATE TABLE staffrole
(
  id serial NOT NULL,
  staff_name character varying(30),
  staff_role character varying(20),
  staff_id integer,
  CONSTRAINT staffrole_pkey PRIMARY KEY (id)
) 
WITHOUT OIDS;
ALTER TABLE staffrole OWNER TO chikkun;

データマッピング

この2つのデータベースのhibernateのマッピングは次のようになる。

Staff

Staff.hbm.xml

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

<hibernate-mapping>
  <class lazy="true" table="staff" name="com.chikkun.tany.database.Staff">
    <id unsaved-value="null" name="id" type="java.lang.Integer" column="id">
      <generator class="sequence">
        <param name="sequence">staff_id_seq</param>
      </generator>
    </id>
    <property name="staffName" length="64" column="staff_name" unique="false" not-null="true"/>
    <property name="staffPassword" length="64" column="staff_password" unique="false" not-null="true"/>
    <property name="email" length="256" column="email" unique="false" not-null="false"/>
    <set inverse="false" cascade="all-delete-orphan" order-by="staff_id asc" lazy="false" name="staffrole">
      <key column="staff_id"/>
      <one-to-many class="com.chikkun.tany.database.Staffrole"/>
    </set>
  </class>
</hibernate-mapping>

Staffrole

Staffrole.hbm.xml

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

<hibernate-mapping>
  <class table="staffrole" name="com.chikkun.tany.database.Staffrole">
    <id unsaved-value="null" name="id" type="java.lang.Integer" column="id">
      <generator class="sequence">
        <param name="sequence">staffrole_id_seq</param>
      </generator>
    </id>
    <property name="staffName" length="64" column="staff_name" unique="false" not-null="true"/>
    <property name="staffRole" length="64" column="staff_role" unique="false" not-null="true"/>
<many-to-one insert="true" column="staff_id" cascade="none" update="true" outer-join="auto" class="com.chikkun.tany.database.Staff" \
    lazy="false" name="staff"/>
  </class>
</hibernate-mapping>

2つのクラスの、cascade、inverse、outer-joinなどは変更ながらテストを行う。

one-to-many

重複行の削除

複数のロールを持つスタッフなど、子を複数持つ親の検索の場合、親が複数Listに入って返ってきてしまう場合がある。

たとえば、

Query query = session.createQuery( "select staff from Staff as staff, Staffrole as role " +
                    " where role.staffName = staff.staffName AND role.staffName = 'tanaka'");
List list = query.list();
for(int i = 0; i < list.size(); i++){
    Staff st = (Staff)list.get(i);
    
    System.out.println("id:" + st.getId());
    System.out.println("name:" + st.getStaffName());
    System.out.println("\n");
}

で、Staffroleを2つ持っている場合は、

     [java] id:11
     [java] name:tanaka
     [java]
     [java]
     [java] id:11
     [java] name:tanaka
     [java]
     [java]

のように、同じStaffが返ってきてしまう。

これを防ぐには、重複行の削除を行うDISTINCTをつけて検索する。

session.createQuery( "select distinct staff from Staff as staff, Staffrole as role " +
                    " where role.staffName = staff.staffName AND role.staffName = 'tanaka'");

すると、

     [java] id:11
     [java] name:tanaka
     [java]
     [java]

のように、ひとつのStaffが返ってくる。

cascade、inverseのテスト

cascade

cascadeは、親側のset,bag,map等や、子のmany-to-oneにつける。

親側のinverseはfalse、cascadeは親がall-delete-orphan、子がnoneでは親側からの登録、削除、子の削除とも通常の動作だった。子のcascadeをallに変更して子の削除(子をSetなどからはずしてのアップデート)を行うと、親とその子のすべてが削除された。

親のcascadeをnoneにして、子の削除(子をデリート)の場合は、その子と親だけが削除されて、その他の子は残っていたが、staff_idはnullになっていた。(※1)

子のcascadeがallの場合、一つのStaffの子のStaffroleを全て変更して、Staffroleからのupdateをかけた場合でもすべてのStaffroleが更新された。

StaffDAO sdao = new StaffDAO();

Staff st = sdao.findById(1);

StaffRoleDAO dao = new StaffRoleDAO();
Staffrole sf = null;
for(Iterator ir = st.getStaffrole().iterator(); ir.hasNext();){
    Staffrole sf = (Staffrole)ir.next();

    sf.setStaffrole("update");
}

dao.saveOrUpdate(sf);

inverse

inverseは、setなど親側につける。

親側のinverseをtrueにしてテスト、cascadeは親がall-delete-orphan、子がnoneでは親側からの登録、削除、子の削除とも同じく通常の動作だった。

子のcascadeがallでもinverse=false時と同じく、登録削除は同じ動作で、子の削除を行うと、親、その他の子のすべてが削除された。

Staff st = new Staff();
Set set = new HashSet();
StaffRoleDAO sf = new StaffRoleDAO();
for(int i = 0; i < roles.length ; i++){
    id = new Integer(roles[i]);
    Staffrole role = new Staffrole(id);
    role.setStaffName(st.getStaffName());
    set.add(role);
}
st.setStaffrole(set);
dao.saveOrUpdate(st);

上記を実行したときに、inverse=falseのときは、StaffroleのstaffIdがデータベースに登録されたが、inverse=trueのときは、staffIdがnullであった。※2

Staff st = new Staff();
StaffRoleDAO sf = new StaffRoleDAO();
for(int i = 0; i < roles.length ; i++){
    id = new Integer(roles[i]);
    Staffrole role = new Staffrole(id);
    role.setStaff(st);
    
}
dao.saveOrUpdate(st);

上記の場合には、inverseに関係なくstaffだけしかDBに保存されなかった。

※1のときinverse=trueの場合は、staff_idが残っていた。

SQLの違い

Set deleteSet = new HashSet();

for(Iterator ir = st.getStaffrole().iterator(); ir.hasNext();){
    Staffrole sf = (Staffrole)ir.next();
    System.out.println("st:" + sf.getStaffRole() + " target:" + role.getStaffRole() + ":");
    if(sf.getStaffRole().equals(role.getStaffRole())){
        deleteSet.add(sf);
        System.out.println("delete:" + role.getStaffRole());
    }
}

st.getStaffrole().removeAll(deleteSet);
dao.update(st);

上記のようにして、親テーブルstaffから子テーブルstaffroleを一つ削除したときに発行されるSQLは、staffがStaffroleを3つ持っていて、inverse=falseのとき

     [java] Hibernate: update staff set staff_name=?, staff_password=?, email=?
where id=?
     [java] Hibernate: update staffrole set staff_name=?, staff_role=?, staff_id
=? where id=?
     [java] Hibernate: update staffrole set staff_name=?, staff_role=?, staff_id
=? where id=?
     [java] Hibernate: update staffrole set staff_id=null where staff_id=? and i
d=?
     [java] Hibernate: delete from staffrole where id=?

staffがinverse=trueのとき

     [java] Hibernate: update staff set staff_name=?, staff_password=?, email=?
where id=?
     [java] Hibernate: update staffrole set staff_name=?, staff_role=?, staff_id
=? where id=?
     [java] Hibernate: update staffrole set staff_name=?, staff_role=?, staff_id
=? where id=?
     [java] Hibernate: delete from staffrole where id=?

となった。結果としてstaffroleが一つ削除されたというところは同じだったが、発行されたSQLに違いがあり、inverse=trueのほうが動作としては軽くなりそうだった。

staff、staffroleの追加のときも、

StaffDAO dao = new StaffDAO();
if(roles != null){
    Set set = new HashSet();
    StaffRoleDAO sf = new StaffRoleDAO();
    for(int i = 0; i < roles.length ; i++){
        id = new Integer(roles[i]);
        Staffrole role = new Staffrole(id);
        role.setStaff(st);
        role.setStaffName(st.getStaffName());
        set.add(role);

    }
    st.setStaffrole(set);
}

dao.saveOrUpdate(st);

staffがinverse=falseのとき、

     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: update staffrole set staff_id=? where id=?
     [java] Hibernate: update staffrole set staff_id=? where id=?
     [java] Hibernate: update staffrole set staff_id=? where id=?

とinsertのあとにupdateが行われているが、inverse=trueのときは

     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)

insertだけであった。

これは、inverse=falseの場合は、主導権が親テーブルであるStaffにあるが、Staffがinsertされた時点では、Staffは自分の子供のstaffroleを知らないからである。

一般的には、無駄なSQL文を発行せず、パフォーマンスを向上するために、inverse="true"を設定することが薦められる。

ただし、※2の場合のときのようにinverse=trueの場合はstaffroleのアップデートが行われないために、staff_idが入らないというようなことも起きる。

子側であるStaffroleから登録を行った場合、(staffroleのcascadeをallにする)

Staffrole role = null;
Set set = new HashSet();
StaffRoleDAO dao = new StaffRoleDAO();
for(int i = 0; i < roles.length ; i++){
    id = new Integer(roles[i]);
    role = new Staffrole(id);
    role.setStaff(st);
    role.setStaffName(st.getStaffName());
    set.add(role);

}
st.setStaffrole(set);

dao.saveOrUpdate(role);

inverse=falseでStaffroleが2つのとき

     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: update staffrole set staff_id=? where id=?
     [java] Hibernate: update staffrole set staff_id=? where id=?

inverse=trueのとき

     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, staff_role, staff_id,
id) values (?, ?, ?, ?)

シーケンスの番号の検索の順番以外には、SQLに変わりは見られなかった。

outer-join

outer-joinプロパティーは、many-to-oneと、set、map、bagなどにつける。

設定できる値は、true、false、autoでautoを使う場合は、外部結合を使うかを自動的に判定するが、hibernate.cfg.xmlに、

<property name="use_outer_join">true</property>

のように指定が必要となる。

        Integer id = 1;
        StaffRoleDAO rdao = new StaffRoleDAO();
        Staffrole sf = rdao.findById(id);

        System.out.println("staff name:" + sf.getStaff().getStaffName());

上記のプログラムのとき、outer_join=falseのときに発行されるSQLは、

     [java] Hibernate: select staffrole0_.id as id0_, staffrole0_.staff_name as
staff2_0_, staffrole0_.staff_role as staff3_0_, staffrole0_.staff_id as staff4_0
_ from staffrole staffrole0_ where staffrole0_.id=1
     [java] Hibernate: select staff0_.id as id1_0_, staff0_.staff_name as staff2
_1_0_, staff0_.staff_password as staff3_1_0_, staff0_.email as email1_0_ from st
aff staff0_ where staff0_.id=?
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=? order by staffrole0_.staff_id asc

となり、3つのSQL文が発行されている。

しかし、outer_join=trueのときは、

     [java] Hibernate: select staffrole0_.id as id0_, staffrole0_.staff_name as
staff2_0_, staffrole0_.staff_role as staff3_0_, staffrole0_.staff_id as staff4_0
_ from staffrole staffrole0_ where staffrole0_.id=1
     [java] Hibernate: select staff0_.id as id1_1_, staff0_.staff_name as staff2
_1_1_, staff0_.staff_password as staff3_1_1_, staff0_.email as email1_1_, staffr
ole1_.staff_id as staff4_3_, staffrole1_.id as id3_, staffrole1_.id as id0_0_, s
taffrole1_.staff_name as staff2_0_0_, staffrole1_.staff_role as staff3_0_0_, sta
ffrole1_.staff_id as staff4_0_0_ from staff staff0_ left outer join staffrole st
affrole1_ on staff0_.id=staffrole1_.staff_id where staff0_.id=? order by staffro
le1_.staff_id asc

外部結合による問い合わせが実行され、SQL文を2つ発行する。

これによってパフォーマンスの改善がされることもあるかもしれない。

insert,update

insert,updateは、各プロパティーや、many-to-oneにつける。

これをfalseにすることで、そのカラムのinsert、updateを行われなくなる。

staffrole.hbm.xml

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

<hibernate-mapping>
  <class table="staffrole" name="com.chikkun.tany.database.Staffrole">
    <id unsaved-value="null" name="id" type="java.lang.Integer" column="id">
      <generator class="sequence">
        <param name="sequence">staffrole_id_seq</param>
      </generator>
    </id>
    <property name="staffName" length="64" column="staff_name" unique="false" not-null="true"/>
    <property name="staffRole" length="64" column="staff_role" update="false" unique="false" not-null="true" insert="false"/>
<many-to-one insert="false" column="staff_id" cascade="all" update="false" outer-join="auto" class="com.chikkun.tany.database.Staff" \
    lazy="false" name="staff"/>
  </class>
</hibernate-mapping>

Staffroleを上記のようにして、テストを行った。

ロールが2つでStaffの登録を行うとinverse=falseのとき、

     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, id) values (?, ?)
     [java] Hibernate: insert into staffrole (staff_name, id) values (?, ?)
     [java] Hibernate: update staffrole set staff_id=? where id=?
     [java] Hibernate: update staffrole set staff_id=? where id=?

staffroleテーブルには、

DB

のように入った。

inverse=trueのときは、

     [java] Hibernate: select nextval ('staff_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: select nextval ('staffrole_id_seq')
     [java] Hibernate: insert into staff (staff_name, staff_password, email, id)
 values (?, ?, ?, ?)
     [java] Hibernate: insert into staffrole (staff_name, id) values (?, ?)
     [java] Hibernate: insert into staffrole (staff_name, id) values (?, ?)

staffroleテーブルには、

DB2

のように入った。

inverse=trueの場合でないと、many-to-oneのinsert、updateは働いていないように思える。

order-byとsort

order-byは、親側のset、mapなどにつけるもので、この取得の順序を制御する。ここに書くのは、HQLではなくてSQLであることに注意が必要。

staff.hbm.xml

<hibernate-mapping>
  <class lazy="true" table="staff" name="com.chikkun.tany.database.Staff">
    <id unsaved-value="null" name="id" type="java.lang.Integer" column="id">
      <generator class="sequence">
        <param name="sequence">staff_id_seq</param>
      </generator>
    </id>
    <property name="staffName" length="64" column="staff_name" unique="false" not-null="true"/>
    <property name="staffPassword" length="64" column="staff_password" unique="false" not-null="true"/>
    <property name="email" length="256" column="email" unique="false" not-null="false"/>
    <set inverse="false" cascade="all-delete-orphan" order-by="id asc" outer-join="auto" lazy="false" name="staffrole">
      <key column="staff_id"/>
      <one-to-many class="com.chikkun.tany.database.Staffrole"/>
    </set>
  </class>
</hibernate-mapping>

上記のようにStaffのsetのorder-byをidの昇順にして、

List staffs = dao.findAll();

if(staffs != null){
    for(int i = 0; i < staffs.size(); i++){
        Staff st = (Staff)staffs.get(i);

        Set set = st.getStaffrole();
        if(set == null){
            continue;
        }
        System.out.println("set class:" + set.getClass().toString());
        for(Iterator is = set.iterator(); is.hasNext();){

            Staffrole role = (Staffrole)is.next();
            System.out.println("name:" + role.getStaffName());
            System.out.println("roleId:" + role.getId());
            System.out.println("role:" + role.getStaffRole());
            
        }
    }
}
System.out.println("\n");

上記のようなものを実行したとき、

     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem2
     [java] roleId:76
     [java] role:admin
     [java] name:sytem2
     [java] roleId:77
     [java] role:president
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java] name:sytem7
     [java] roleId:90
     [java] role:worker
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem11
     [java] roleId:95
     [java] role:user
     [java] name:sytem11
     [java] roleId:97
     [java] role:public
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem10
     [java] roleId:105
     [java] role:president
     [java] name:sytem10
     [java] roleId:106
     [java] role:admin
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem14
     [java] roleId:107
     [java] role:president
     [java] name:sytem14
     [java] roleId:108
     [java] role:admin

このように、出力された。

staffroleは、一応ID順に取得されているようで、その際にorg.hibernate.collection.PersistentSetというクラスが使われているようだった。

order-byを外した場合は、

     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem2
     [java] roleId:76
     [java] role:admin
     [java] name:sytem2
     [java] roleId:77
     [java] role:president
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java] name:sytem7
     [java] roleId:90
     [java] role:worker
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem11
     [java] roleId:97
     [java] role:public
     [java] name:sytem11
     [java] roleId:95
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem10
     [java] roleId:106
     [java] role:admin
     [java] name:sytem10
     [java] roleId:105
     [java] role:president
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem14
     [java] roleId:107
     [java] role:president
     [java] name:sytem14
     [java] roleId:108
     [java] role:admin

だった。

次にsortについて、sortはorder-byと同じくsetなどに記述するもので、"unsorted"、"natural"、"comparatorClass"を指定できる。

"unsorted"を指定した場合は、ソートが行われず上記の結果と同じだった。

"natural"は、子のクラスがComparableインターフェースを継承しているときに、そのソート順に行う。

hbmファイルが、

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

<hibernate-mapping>
  <class lazy="true" table="staff" name="com.chikkun.tany.database.Staff">
    <id unsaved-value="null" name="id" type="java.lang.Integer" column="id">
      <generator class="sequence">
        <param name="sequence">staff_id_seq</param>
      </generator>
    </id>
    <property name="staffName" length="64" column="staff_name" unique="false" not-null="true"/>
    <property name="staffPassword" length="64" column="staff_password" unique="false" not-null="true"/>
    <property name="email" length="256" column="email" unique="false" not-null="false"/>
    <set inverse="false" cascade="all-delete-orphan" outer-join="auto" lazy="false" sort="natural" name="staffrole">
      <key column="staff_id"/>
      <one-to-many class="com.chikkun.tany.database.Staffrole"/>
    </set>
  </class>
</hibernate-mapping>

のとき、StaffroleがComparableを継承していなければ、

     [java] java.lang.ClassCastException: com.chikkun.tany.database.Staffrole ca
nnot be cast to java.lang.Comparable
     [java]     at java.util.TreeMap.put(TreeMap.java:542)
     [java]     at java.util.TreeSet.add(TreeSet.java:238)
     ......

のようにExceptionが発生する。

Staffroleを

public class Staffrole implements Serializable, Comparable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 1583398470057024174L;

    /**
     * id
     */
    private Integer id;

    /**
     * staff_name
     */
    private String staffName;

    /**
     * staff_role
     */
    private String staffRole;

    /**
     * staff(親)
     */
    private Staff staff;

    /**
     * デフォルトコンストラクタ
     */
    public Staffrole() {
    }
    
    @Override
    public int compareTo(Object o) {
        //staffNameで比較
        Staffrole sf = (Staffrole)o;
        if(sf.getStaffName() == null){
            return -1;
        } else if(staffName == null){
            return 1;
        }
        return staffName.compareTo(sf.getStaffName());
        
    }

のようにComparableを継承して、テストしてみると、

     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem2
     [java] roleId:76
     [java] role:admin
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem11
     [java] roleId:95
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem10
     [java] roleId:106
     [java] role:admin
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem14
     [java] roleId:108
     [java] role:admin

なぜか、Staffroleを一つしかもっていないStaffが返ってきた。理由は不明。

"comparatorClass"は、ここの部分にComparatorインターフェースを継承したクラス名を記述する。(パッケージ名から書かないとエラーになるようだった)

package com.chikkun.tany.database;

import java.util.Comparator;

public class RoleComparator implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        // TODO Auto-generated method stub
        Staffrole sf1 = (Staffrole)o1;
        Staffrole sf2 = (Staffrole)o2;
        
        return sf2.getId().compareTo(sf1.getId());
    }

}

上記のようなIDの降順にソートするクラスを作成して、

<set inverse="false" cascade="all-delete-orphan" outer-join="auto" lazy="false" sort="com.chikkun.tany.database.RoleComparator" \
    name="staffrole">
      <key column="staff_id"/>
      <one-to-many class="com.chikkun.tany.database.Staffrole"/>
    </set>

hbmファイルを書き換えてテストを行った。

     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem2
     [java] roleId:77
     [java] role:president
     [java] name:sytem2
     [java] roleId:76
     [java] role:admin
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java] name:sytem7
     [java] roleId:90
     [java] role:worker
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem11
     [java] roleId:97
     [java] role:public
     [java] name:sytem11
     [java] roleId:95
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem10
     [java] roleId:106
     [java] role:admin
     [java] name:sytem10
     [java] roleId:105
     [java] role:president
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem14
     [java] roleId:108
     [java] role:admin
     [java] name:sytem14
     [java] roleId:107
     [java] role:president

するとこちらは、ちゃんとソートして取得できた。

<set inverse="false" cascade="all-delete-orphan" order-by="id asc" outer-join="auto" lazy="false" \
    sort="com.chikkun.tany.database.RoleComparator" name="staffrole">
      <key column="staff_id"/>
      <one-to-many class="com.chikkun.tany.database.Staffrole"/>
    </set>

上記のように、order-byとsortが両方あった場合は、

     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem2
     [java] roleId:77
     [java] role:president
     [java] name:sytem2
     [java] roleId:76
     [java] role:admin
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java] name:sytem7
     [java] roleId:90
     [java] role:worker
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem11
     [java] roleId:97
     [java] role:public
     [java] name:sytem11
     [java] roleId:95
     [java] role:user
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem10
     [java] roleId:106
     [java] role:admin
     [java] name:sytem10
     [java] roleId:105
     [java] role:president
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSortedSet
     [java] name:sytem14
     [java] roleId:108
     [java] role:admin
     [java] name:sytem14
     [java] roleId:107
     [java] role:president

上記のように、sortが優先されるよう。ただし、SQLでは

     [java] Hibernate: select staff0_.id as id1_, staff0_.staff_name as staff2_1
_, staff0_.staff_password as staff3_1_, staff0_.email as email1_ from staff staf
f0_
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=? order by staffrole0_.id asc
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=? order by staffrole0_.id asc
.......

のように、order byがついているがその分は無駄になる。片方だけの指定をするようにしたほうが良い。

FROM句の違い

Staffの検索を親側からFROMで行った場合、

Query query = session.createQuery("select staff from Staff as staff" +
                " where staff.staffName = 'tanaka'");

このとき発行されたSQLは、

     [java] Hibernate: select staff0_.id as id1_, staff0_.staff_name as staff2_1
_, staff0_.staff_password as staff3_1_, staff0_.email as email1_ from staff staf
f0_ where staff0_.staff_name='tanaka'
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=? order by staffrole0_.staff_id asc

となった。

子からの検索の場合、

Query query = session.createQuery("select role.staff from Staffrole as role" +
                " where role.staffName = 'tanaka'");

このとき発行されたSQLは、

     [java] Hibernate: select staff1_.id as id1_, staff1_.staff_name as staff2_1
_, staff1_.staff_password as staff3_1_, staff1_.email as email1_ from staffrole
staffrole0_ inner join staff staff1_ on staffrole0_.staff_id=staff1_.id where st
affrole0_.staff_name='tanaka'
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=? order by staffrole0_.staff_id asc

となり、親側からの検索の方が、joinがない分だけ早そうだった。

少し変えてみて、複数の結果が返るようにしてみる。

Query query = session.createQuery("select role.staff from Staffrole as role" +
                " where role.staffRole = 'public'");

これで、publicのロールを持った3つのStaffが返ってくる。このとき、発行されたSQLは、

     [java] Hibernate: select staff1_.id as id1_, staff1_.staff_name as staff2_1
_, staff1_.staff_password as staff3_1_, staff1_.email as email1_ from staffrole
staffrole0_ inner join staff staff1_ on staffrole0_.staff_id=staff1_.id where st
affrole0_.staff_role='public'
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?

となった。次に、Staffからの検索。

Query query = session.createQuery("select staff from Staff as staff join staff.staffrole as role" +
                " where role.staffRole = 'public'");

このとき、発行されたSQLは、

     [java] Hibernate: select staff0_.id as id1_, staff0_.staff_name as staff2_1
_, staff0_.staff_password as staff3_1_, staff0_.email as email1_ from staff staf
f0_ inner join staffrole staffrole1_ on staff0_.id=staffrole1_.staff_id where st
affrole1_.staff_role='public'
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?
     [java] Hibernate: select staffrole0_.staff_id as staff4_1_, staffrole0_.id
as id1_, staffrole0_.id as id0_0_, staffrole0_.staff_name as staff2_0_0_, staffr
ole0_.staff_role as staff3_0_0_, staffrole0_.staff_id as staff4_0_0_ from staffr
ole staffrole0_ where staffrole0_.staff_id=?

となり、あまり変わらない。

上記2つで返ってきた結果は、

     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem3
     [java] roleId:69
     [java] role:public
     [java] name:sytem3
     [java] roleId:70
     [java] role:worker
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem7
     [java] roleId:89
     [java] role:user
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java] name:sytem7
     [java] roleId:90
     [java] role:worker
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem11
     [java] roleId:97
     [java] role:public
     [java] name:sytem11
     [java] roleId:95
     [java] role:user

のように、ロールをもったStaffが返ってきた。ここで、

Query query = session.createQuery("select staff from Staff as staff join fetch staff.staffrole as role" +
                " where role.staffRole = 'public'");

のようにjoin fetchを使用すると、

     [java] Hibernate: select staff0_.id as id1_0_, staffrole1_.id as id0_1_, st
aff0_.staff_name as staff2_1_0_, staff0_.staff_password as staff3_1_0_, staff0_.
email as email1_0_, staffrole1_.staff_name as staff2_0_1_, staffrole1_.staff_rol
e as staff3_0_1_, staffrole1_.staff_id as staff4_0_1_, staffrole1_.staff_id as s
taff4_0__, staffrole1_.id as id0__ from staff staff0_ inner join staffrole staff
role1_ on staff0_.id=staffrole1_.staff_id where staffrole1_.staff_role='public'

SQLが一つになり、

     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem3
     [java] roleId:69
     [java] role:public
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem7
     [java] roleId:91
     [java] role:public
     [java]
     [java]
     [java] set class:class org.hibernate.collection.PersistentSet
     [java] name:sytem11
     [java] roleId:97
     [java] role:public

ロールは、publicしか持っていなかった。Staffのみをとるときは、早そうだが、その他の情報も欲しい場合は注意が必要のよう。