Top > MyPage
 

hibernateの双方向many-to-manyについて

many-to-many

Hibernateのmany-to-manyの使い方について、実際に簡単なテーブルを用意して確認したのでまとめる。

今回は、検証のためのアプリをmavenを用いて作ったが、mavenのプロジェクトの作り方については割愛する。

なお、検証にはこの時点のhibernateの最新バージョン(3.3.1.GA)をもちいた。

この検証を行うにあたって、HIBERNATE リファレンスガイドを参考にした。

テーブル構成

以下に、実際に使用したDBのテーブル構成のER図を示す。

ER図

図の様に、今回はUSERSとROLES、それらの管理テーブルとなるUSER_ROLEテーブルを用意した。

USERSとROLESのプライマリキーは、それぞれとである。そして、USER_ROLEは双方を外部キーとしてもっている。

ここで、 という点に注目してほしい。

USER_ROLEに保存されるUSERS_IDとROLES_IDは、その組み合わせがユニークでなければならない。複合プライマリキーとすることで、それが保障されるのである。

実のところ、複合プライマリキーにしなくても、重複がないように管理できるのであれば、問題なく動作する。しかしながら、複合プライマリキーにする方が安全であることは言うまでもない。

以下に、各テーブルのCREATE文を示す。USER_ROLEのCONSTRAINTがコメントアウトされているのは、検証をするうえで楽にするためであり、こうした方がよいというわけではない。

CREATE TABLE USERS (
       USERS_ID SERIAL NOT NULL
     , USERNAME VARCHAR(32)
     , PASSWORD VARCHAR(64)
     , PRIMARY KEY (USERS_ID)
);

CREATE TABLE ROLES (
       ROLES_ID SERIAL NOT NULL
     , ROLE_NAME VARCHAR(64)
     , PRIMARY KEY (ROLES_ID)
);

CREATE TABLE USER_ROLE (
       USERS_ID INTEGER NOT NULL
     , ROLES_ID INTEGER NOT NULL
     , PRIMARY KEY (USERS_ID, ROLES_ID)
--     , CONSTRAINT FK_USER_ROLE_1 FOREIGN KEY (USERS_ID)
--                  REFERENCES USERS (USERS_ID)
--     , CONSTRAINT FK_USER_ROLE_2 FOREIGN KEY (ROLES_ID)
--                  REFERENCES ROLES (ROLES_ID)
);

DTO構成

DTOは、USERSテーブルとROLESテーブルのそれぞれに対応するUsersとRolesを作成した。この二つが、多対多の関係となる。そして、USER_ROLEテーブルに対応するDTOはない。

それぞれのクラスの持つ変数は次のようにした。(メソッドは省略)お互いが、相手のクラスのSetをもつことがわかる。

public class Users implements Serializable {

    private Integer usersId;

    private String username;

    private String password;

    private Set<Roles> roles;

}


public class Roles implements Serializable {

    
    private Integer rolesId;
    
    private String roleName;
    
    private Set<Users> users;

}

このときのマッピング定義は、それぞれ次のようにした。

<hibernate-mapping>
  <class table="USERS" name="com.chikkun.common.login.db.Users">
    <id unsaved-value="null" name="usersId" type="java.lang.Integer" column="USERS_ID">
      <generator class="sequence">
        <param name="sequence">USERS_USERS_ID_SEQ</param>
      </generator>
    </id>
    <property name="username" column="USERNAME"/>
    <property name="password" column="PASSWORD"/>
    <set inverse="true" lazy="false" table="USER_ROLE" name="roles">
      <key column="USERS_ID"/>
      <many-to-many class="com.chikkun.common.login.db.Roles" column="ROLES_ID"/>
    </set>
  </class>
</hibernate-mapping>


<hibernate-mapping>
  <class table="ROLES" name="com.chikkun.common.login.db.Roles">
    <id unsaved-value="null" name="rolesId" type="java.lang.Integer" column="ROLES_ID">
      <generator class="sequence">
        <param name="sequence">ROLES_ROLES_ID_SEQ</param>
      </generator>
    </id>
    <property name="roleName" column="ROLE_NAME"/>
    <set lazy="false" table="USER_ROLE" name="users">
      <key column="ROLES_ID"/>
      <many-to-many class="com.chikkun.common.login.db.Users" column="USERS_ID"/>
    </set>
  </class>
</hibernate-mapping>

ここで、注目してほしいのはUsersクラスのマッピングにあるRolesのsetタグに設定されているである。

双方向の関連がある場合、これをどちらかに設定する必要があるので、今回はUsersのマッピングでRolesのセットにtrueでセットした。

ここで、Usersのsetマッピングを取り上げて、ちょっと解説する。

    <set inverse="true" lazy="false" table="USER_ROLE" name="roles">
      <key column="USERS_ID"/>
      <many-to-many class="com.chikkun.common.login.db.Roles" column="ROLES_ID"/>
    </set>
  • setに設定されているinverse属性は、UsersとRolesのリンクについて情報を探す必要があるとき、どちらから探すかを設定している。この設定だと、Roles側から探すということになる。
  • lazyは遅延処理の設定である。デフォルトではtrueなのだが、それでは困るという場合には明示的にfalseにする。この設定がtrueの場合は、UsersクラスからRolesのSetを取得しようとしたときに、sqlを発行してDBよりデータを取得する。一度にデータを持ってくるのではないので、メモリを圧迫しないのが利点である。しかし、セッションを閉じてからRolesを取得しようとしたりすると、エラーになる。falseの場合はUsersを取得したとき、すでにRolesもそのインスタンスに含まれている。
  • tableはお互いのキーを保存している管理テーブル名を設定する。この場合はご覧のようにUSER_ROLEである。nameはマッピングするUsersクラス内の変数である。
  • setの要素であるkeyにはcolumnとしてを設定している。これはUsersクラスにマッピングしているUSERSテーブルの主キーの値を保存するカラムである。
  • many-to-manyはこのsetの要素となるクラスをclass属性で設定する。many-to-manyのcolumn属性は、class属性で設定したクラスがマッピングされるテーブル(この例ではROLES)の主キーの値を保存する管理テーブルUSER_ROLEのカラムを設定する。

DTOのtips

今回の例のようなmany-to-manyを実装する場合、例えば、Usersのrolesに新しいRolesを追加したら、このRolesのusersにもUsersを追加する必要がある。逆にリムーブする場合も同様に双方からやる必要がある。

そこで、両側に正しく関連を設定するリンク管理メソッドをDTOに作成すると使いやすく、また、安全である。

以下に、Usersに作成したメソッドを示す。

    
    
    //通常のゲッターメソッド。 特筆すべきことはない。
    public Set<Roles> getRoles() {
        return roles;
    }

    //通常のセッターメソッド。 うかつに使用してしまうのを防ぐため protected にしている。
    protected void setRoles(Set<Roles> roles) {
        this.roles = roles;
    }
    
    //Usersクラスの private Set<Roles> roles に Roles r を追加するメソッド
    //同時に Roles r 側にもUsers自身を追加する
    public void addToRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            roles.add(r);
        }else{
            roles = new HashSet<Roles>();
            roles.add(r);
            this.setRoles(roles);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            users.add(this);
        }else{
            users = new HashSet<Users>();
            users.add(this);
            r.setUsers(users);
        }
    }
    
    //Usersクラスの private Set<Roles> roles から Roles r を削除するメソッド
    //同時に Roles r 側からもUsers自身を削除する
    public void removeFromRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            roles.remove(r);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            users.remove(this);
        }
    }
    

動作結果の検証 (cascade無し)

以下、insertやupdateなどをいくつか試してみて、結果を見たいと思う。

単純にdeleteする

まず、以下のようなデータをDBに挿入して、Users全て、Roles全てを取得してみて、想定どおりのデータが得られることを確認した。

USERS
USERS_ID USERNAME PASSWORD
1 boo pass1
2 hoo pass2
3 woo pass3
ROLES
ROLES_ID ROLE_NAME
1 hoge
2 haga
3 hugo
USER_ROLE
USERS_ID ROLES_ID
1 1
1 2
1 3
2 1
2 3
3 2

例えば、上に示したテーブルのようにデータが入っていたとすると、USERS_IDが1のUSERSを持ってくると、ROLES_IDが1と2と3のROLESも付いてくる。

反対側、ROLES_IDが1のROLESを持ってくると、USERS_IDが1と2のUSERSが付いてくる。

この状態で、単純に、Usersインスタンスをひとつdelete、Rolesインスタンスをひとつdeleteしたらどうなるか確認した。

  • Rolesをひとつdelete

    まず、roleIdが2であるRoles roleをDBから取得したとする。そして、そのインスタンスroleをそのままdeleteしたとする。

    すると、処理後のテーブルの内容は次のようになる

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 3
    2 1
    2 3

    ROLESテーブルのROLES_IDが2であったレコードが削除され、USER_ROLEの方もROLES_IDが2であったレコードが削除されている。

    sqlは次のようになっていた

        Hibernate: delete from USER_ROLE where ROLES_ID=?
        Hibernate: delete from ROLES where ROLES_ID=?
    

    まず、子テーブルであるUSER_ROLEの方の該当するレコードを削除して、それからROLESの方を削除しているのがわかる。

  • Usersをひとつdelete

    では、反対側のUsers userのusersIdが1のインスタンスをdeleteした場合はどうなるかというと、次に示すテーブルのようになる。

    USERS
    USERS_ID USERNAME PASSWORD
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 2
    1 3
    2 1
    2 3
    3 2

    こちらも、USERS_IDが1のレコードが全て削除されるかと思いきや、さにあらず。

    USERSテーブルの中のレコードのみ削除され、USER_ROLEテーブルの方は残っている。

    当然、USERSにUSERS_IDが1のレコードがないのに、USER_ROLEテーブルには在るというのはおかしなことなので、読み込んだときにエラーになる。

    SQLは次のようになる。

        Hibernate: delete from USERS where USERS_ID=?
    

Usersのマッピングで、inverse=trueをsetに設定しているため、Roles側を削除すると管理テーブルのデータも削除されるが、Users側を削除して管理テーブルのデータは削除されないのである。

新たな関連を増やすアップデート

まず、DBの状態を単純なデリートの例であげた初期状態のようにしたとする。

USERS
USERS_ID USERNAME PASSWORD
1 boo pass1
2 hoo pass2
3 woo pass3
ROLES
ROLES_ID ROLE_NAME
1 hoge
2 haga
3 hugo
USER_ROLE
USERS_ID ROLES_ID
1 1
1 2
1 3
2 1
2 3
3 2

すると、USERS_IDが3のUSERSのレコードと関連を持つROLESのレコードはROLES_IDが2のレコードである。

そこで、新たにUSERS_ID=3、ROLES_ID=3となるレコードをUSER_ROLEにをつかって作ることにする。

UsersとRolesのお互いをセットしあって、どちらかをアップデートする。

  • Usersをアップデート

        // generalDAOという汎用DAOクラスを使うということにする。generalDAOのソースは後述
        
        // usersIdが3のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(3));
        
        // rolesIdが3のRolesを取得する
        Roles ur = generalDAO.findRoleById(new Integer(3));
        
        // リンク管理メソッドで互いにセットしあう
        updateUser.addToRoles(ur);
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    この例の様に実行したところ、発行されたsqlは次のようなものだった

        Hibernate: update USERS set USERNAME=?, PASSWORD=? where USERS_ID=?
    

    deleteの例からも想像できたと思うが、USERSのみ更新されて、USER_ROLEテーブルには何も処理が行われていない。

  • Rolesをアップデート

        
        // usersIdが3のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(3));
        
        // rolesIdが3のRolesを取得する
        Roles ur = generalDAO.findRoleById(new Integer(3));
        
        // リンク管理メソッドで互いにセットしあう
        //updateUser.addToRoles(ur);  //Usersに実装したリンク管理メソッド
        ur.addToUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        // Rolesをアップデート
        generalDAO.updateRoles(ur);
    

    この例の様に実行したところ、発行されたsqlは次のようなものだった

         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
    

    予想通り、ROLESのアップデートのほかに、管理テーブルに新たな関連のレコードの挿入がおこなわれて、USER_ROLEテーブルにUSERS_ID=3、ROLES_ID=3である新たなレコードができていた。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 2
    1 3
    2 1
    2 3
    3 2
    3 3

    なお、例ではコメントアウトしてあるが、以下の様にRolesにつくられたリンク管理メソッドの代わりにUsersの方を使っても得られる結果は同じである。

    どちらも、互いをセットしあうメソッドであるからである。

        // リンク管理メソッドで互いにセットしあう
        updateUser.addToRoles(ur);    //Usersに実装したリンク管理メソッド
        //ur.addToUsers(updateUser);  //Rolesに実装したリンク管理メソッド
    

関連を減らすアップデート

まず、DBの状態を単純なデリートの例であげた初期状態のようにしたとする。

そして、USERS_IDが1のUSERSのレコードとROLES_IDが1のROLESのレコードの関連をなくすようにアップデートすることにする。

UsersとRolesのお互いをリムーブしあって、どちらかをアップデートする。

  • Usersをアップデート

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = generalDAO.findRoleById(new Integer(1));
        
        // リンク管理メソッドで互いにリムーブしあう
        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
        //ur.removeFromUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    実を言えば、Usersをアップデートして関連を変えようとすること自体が間違いなのであるが、まず、ここでこの例の間違いを説明する。

        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
    

    は内部では次のような処理を行っている。

        
        public void removeFromRoles(Roles r){
            Set<Roles>roles = this.getRoles();
            if(roles != null){
                //Usersインスタンス内のRolesのSetから
                //引数のRolesインスタンスと同一のものを除去
                roles.remove(r);
            }
            Set<Users>users = r.getUsers();
            if(users != null){
                //引数のRolesインスタンス内のUsersのSetから
                //このUsersインスタンスと同一のものを除去
                users.remove(this);
            }
        }
    

    先ほどの例の使い方を見ると、まず、UsersとRolesのインスタンスをそれぞれのプライマリキーを指定して取得している。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = generalDAO.findRoleById(new Integer(1));
    

    これらを、リンク管理メソッドremoveFromRolesにかけているが、それが間違いなのである。Rolesインスタンスurは、UsersインスタンスupdateUserと別に取得したものなので、updateUserのrolesに含まれるインスタンスにurと同一のレコードを表すインスタンスはあっても、urと同一のものはないのである。

    これを踏まえて、「USERS_IDが1のUSERSのレコードとROLES_IDが1のROLESのレコードの関連をなくすように」となると、次のように実行することになる。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        // リンク管理メソッドで互いにリムーブしあう
        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
        //ur.removeFromUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    これを実行すると、発行されるsqlは、次のようになる。

        Hibernate: update USERS set USERNAME=?, PASSWORD=? where USERS_ID=?
    

    予想通り、USERSのみ更新されて、USER_ROLEには何も処理が行われていない。

  • Rolesをアップデート

    インスタンス取得、リンク管理メソッド実行はおなじで、アップデートするところだけ、変更して実行してみる。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        // リンク管理メソッドで互いにリムーブしあう
        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
        //ur.removeFromUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        //Roles側からアップデート
        generalDAO.updateRoles(ur);
    

    この例の様に実行したところ、発行されたsqlは次のようなものだった

         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: delete from USER_ROLE where ROLES_ID=? and USERS_ID=?
         
    

    予想通り、ROLESのアップデートのほかに、管理テーブルから、リムーブしたインスタンスの関連のレコードが削除されて、USER_ROLEの1,1のレコードがなくなっているのがわかる。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 2
    1 3
    2 1
    2 3
    3 2

    このようなマッピングの場合、Roles側のusersからのリムーブだけで関連を消せるのではないかと思ったので、次のように実行してみた。

    インスタンス取得は同じであるが、リンク管理メソッドを使わず、Roles側のusersからだけリムーブして、Rolesインスタンスをアップデートする。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        //片側だけリムーブ
        ur.getUsers().remove(updateUser);
        
        //Roles側からアップデート
        generalDAO.updateRoles(ur);
    

    実行結果は予想通り、リンク管理メソッドを使った場合とまったく変わらなかった。主導権がRoles側にあるので、当たり前といえばあたりまえである。

    なお、updateUserのrolesからurをリムーブして、urをアップデートしても、まったく意味がないことは自明ではあるが、一応書いておく。

新たな関連を増やすインサート

次に、新しいレコードのインサートを試して見たいと思う。それぞれ単独でのインサートは試すまでもないと思われるので、省くことにする。

新しいUsersとRolesのインスタンスをつくり、その二つを関連づけて保存する。

  • Usersを保存

            //新しいUsersインスタンス
            Users insertUser = new Users();
            insertUser.setUsername("baa");
            insertUser.setPassword("pass4");
            
            //新しいRolesインスタンス
            Roles insertRole = new Roles();
            insertRole.setRoleName("mumumu");
            
            
            //リンク管理メソッドで関係付ける。どちらでも同じ
            //insertUser.addToRoles(insertRole);
            insertRole.addToUsers(insertUser);
            
            //Users保存
            generalDAO.saveUsers(insertUser);
    

    上の例の様に実行したところ、想像通り、Usersのみが保存されていた。SQLは以下のようになっていた

        Hibernate: select nextval ('USERS_USERS_ID_SEQ')
        Hibernate: insert into USERS (USERNAME, PASSWORD, USERS_ID) values (?, ?, ?)
    

    シーケンスからインサートするレコードのIDを取得して、それからインサートされている

  • Rolesを保存

            //新しいUsersインスタンス
            Users insertUser = new Users();
            insertUser.setUsername("baa");
            insertUser.setPassword("pass4");
            
            //新しいRolesインスタンス
            Roles insertRole = new Roles();
            insertRole.setRoleName("mumumu");
            
            
            //リンク管理メソッドで関係付ける。どちらでも同じ
            //insertUser.addToRoles(insertRole);
            insertRole.addToUsers(insertUser);
            
            //Roles保存
            generalDAO.saveRoles(insertRole);
    

    上の例の様に実行したところ、エラーが発生した。SQLは以下のようになっていた

         Hibernate: select nextval ('ROLES_ROLES_ID_SEQ')
         Hibernate: insert into ROLES (ROLE_NAME, ROLES_ID) values (?, ?)
         Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
    

    シーケンスからインサートするレコードのIDを取得して、それからROlESにインサートされている。

    その後、USER_ROLEにも新しいレコードを作ろうとしているが、このとき、USERS_IDがnullなのでエラーとなったである。

    このことから、もしどちらも新しいインスタンスを保存したい場合は、先にUsersを保存、それからRolesという順番でやらなくてはならないということがわかる(cascadeされないようにしている場合はこの点に気をつける必要がある)。

  • Usersを保存。その次にRolesを保存

            //新しいUsersインスタンス
            Users insertUser = new Users();
            insertUser.setUsername("baa");
            insertUser.setPassword("pass4");
            
            //新しいRolesインスタンス
            Roles insertRole = new Roles();
            insertRole.setRoleName("mumumu");
            
            
            //リンク管理メソッドで関係付ける。どちらでも同じ
            //insertUser.addToRoles(insertRole);
            insertRole.addToUsers(insertUser);
            
            //Users保存
            generalDAO.saveUsers(insertUser);
            
            //Roles保存
            generalDAO.saveRoles(insertRole);
    

    上の例の様に実行したところ、双方が保存され、管理テーブルにも新しいレコードができた。SQLは次のようになった。

        Hibernate: select nextval ('USERS_USERS_ID_SEQ')
        Hibernate: insert into USERS (USERNAME, PASSWORD, USERS_ID) values (?, ?, ?)
        Hibernate: select nextval ('ROLES_ROLES_ID_SEQ')
        Hibernate: insert into ROLES (ROLE_NAME, ROLES_ID) values (?, ?)
        Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
    

    以下のようなコードを実行しても、得られる結果は同じとなる。ただし、発行されるSQLはちがう点に注意。

            //新しいUsersインスタンス
            Users insertUser = new Users();
            insertUser.setUsername("baa");
            insertUser.setPassword("pass4");
            
            //新しいRolesインスタンス
            Roles insertRole = new Roles();
            insertRole.setRoleName("mumumu");
            
                    
            //Users保存
            generalDAO.saveUsers(insertUser);
            
            //Roles保存
            generalDAO.saveRoles(insertRole);
            
            
            //リンク管理メソッドで関係付ける。どちらでも同じ
            //insertUser.addToRoles(insertRole);
            insertRole.addToUsers(insertUser);
            
            
            //Rolesをアップデート (Usersでは管理テーブルに保存されない)
            generalDAO.updateRoles(insertRole);
    

    発行されたSQLは次のようになる

        Hibernate: select nextval ('USERS_USERS_ID_SEQ')
        Hibernate: insert into USERS (USERNAME, PASSWORD, USERS_ID) values (?, ?, ?)
        Hibernate: select nextval ('ROLES_ROLES_ID_SEQ')
        Hibernate: insert into ROLES (ROLE_NAME, ROLES_ID) values (?, ?)
        Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
        Hibernate: delete from USER_ROLE where ROLES_ID=?
        Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
    

    USERSとROLESにインサートされるところまでは、先ほどの例と同じだが、その後、ROLESをアップデートした後に注目してほしい。

    まず、管理テーブルにある、今回アップデートしたROLESのROLES_IDを持つレコード全てを削除して、その後、インサートを実行している。

    このSQLからわかるように、マッピングでinverse true指定された方をアップデートするときは、管理テーブルのデータが一度全て削除され、そのとき保持している関連を改めて保存しなおすのである。

動作結果の検証 (cascade all)

以上は、マッピングファイルのsetにcascade属性を設定していない状態での結果である。これはcascade="none"を設定したのと同じである。そこで、今度はcascade属性を設定した場合の挙動を見てみようと思う。

まずは、cascade="all"を設定して確認することにする。マッピングは次のようにした。

<hibernate-mapping>
  <class table="USERS" name="com.chikkun.common.login.db.Users">
    <id unsaved-value="null" name="usersId" type="java.lang.Integer" column="USERS_ID">
      <generator class="sequence">
        <param name="sequence">USERS_USERS_ID_SEQ</param>
      </generator>
    </id>
    <property name="username" column="USERNAME"/>
    <property name="password" column="PASSWORD"/>
    <set inverse="true" cascade="all" lazy="false" table="USER_ROLE" name="roles">
      <key column="USERS_ID"/>
      <many-to-many class="com.chikkun.common.login.db.Roles" column="ROLES_ID"/>
    </set>
  </class>
</hibernate-mapping>


<hibernate-mapping>
  <class table="ROLES" name="com.chikkun.common.login.db.Roles">
    <id unsaved-value="null" name="rolesId" type="java.lang.Integer" column="ROLES_ID">
      <generator class="sequence">
        <param name="sequence">ROLES_ROLES_ID_SEQ</param>
      </generator>
    </id>
    <property name="roleName" column="ROLE_NAME"/>
    <set lazy="false" table="USER_ROLE" name="users">
      <key column="ROLES_ID"/>
      <many-to-many class="com.chikkun.common.login.db.Users" column="USERS_ID"/>
    </set>
  </class>
</hibernate-mapping>

Usersのマッピングでrolesのsetにcascade="all"を設定して、逆側は何も設定しないことにした。

単純なdeleteを行う

前の例と同じように、次のようなデータを用意したとする。

USERS
USERS_ID USERNAME PASSWORD
1 boo pass1
2 hoo pass2
3 woo pass3
ROLES
ROLES_ID ROLE_NAME
1 hoge
2 haga
3 hugo
USER_ROLE
USERS_ID ROLES_ID
1 1
1 2
1 3
2 1
2 3
3 2
  • Rolesをひとつdelete

    先にやったdeleteとおなじように、roleIdが2であるRoles roleをDBから取得したとする。そして、そのインスタンスroleをそのままdeleteしたとする。

    すると、処理後のテーブルの内容は次のようになる

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 3
    2 1
    2 3

    先の例と、まったく同じ結果となった。

  • Usersをひとつdelete

    次に、DBのレコードを戻してから、また先の例と同じように、Users userのusersIdが1のインスタンスをdeleteしたところ、結果が大きく違ったものとなった。

    USERS
    USERS_ID USERNAME PASSWORD
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    USER_ROLE
    USERS_ID ROLES_ID

    ご覧のように、USERSテーブルのレコード二つを残して、他のレコードは全て削除された。

    USERSテーブルのUSERS_IDが1のレコードはROLESの3つのレコード全てと関連していた。そして、cascade="all"の設定がされていたので、USERSのレコードを削除しようとすると、関連するROLESのレコードも削除されるのである。

    そしてROLESがのレコードが削除されるならUSER_ROLEも削除されるので、このような結果になるのである。

    このとき、SQLは次のように発行された。

         Hibernate: delete from USER_ROLE where ROLES_ID=?
         Hibernate: delete from USER_ROLE where ROLES_ID=?
         Hibernate: delete from USER_ROLE where ROLES_ID=?
         Hibernate: delete from ROLES where ROLES_ID=?
         Hibernate: delete from ROLES where ROLES_ID=?
         Hibernate: delete from ROLES where ROLES_ID=?
         Hibernate: delete from USERS where USERS_ID=?
         
    

新たな関連を増やすアップデート

DBの初期状態を次のようにする。

USERS
USERS_ID USERNAME PASSWORD
1 boo pass1
2 hoo pass2
3 woo pass3
ROLES
ROLES_ID ROLE_NAME
1 hoge
2 haga
3 hugo
USER_ROLE
USERS_ID ROLES_ID
1 1
1 2
1 3
2 1
2 3
3 2
  • Usersをアップデート

    先の例と同じようにUsersをアップデートする。

        
        // usersIdが3のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(3));
        
        // rolesIdが3のRolesを取得する
        Roles ur = generalDAO.findRoleById(new Integer(3));
        
        // リンク管理メソッドで互いにセットしあう
        updateUser.addToRoles(ur);
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    実行した結果、次のようなsqlが発行されていた。

          Hibernate: update USERS set USERNAME=?, PASSWORD=? where USERS_ID=?
          Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
          Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
          Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
          
    

    cascadeが設定されていない場合と、大きく違うことがわかる。USERSのアップデートにともなって、ROLESもアップデートされ、USER_ROLEに新たな関連が挿入されている。その結果、次のようになっている。USER_ROLEに3,3のレコードが増えているのがわかる。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 2
    1 3
    2 1
    2 3
    3 2
    3 3

Rolesのアップデートは、先の例と変わらないので省略。

関連を減らすアップデート

まず、DBの初期状態のようにして、USERS_IDが1のUSERSのレコードとROLES_IDが1のROLESのレコードの関連をなくすようにアップデートすることにする。

UsersとRolesのお互いをリムーブしあって、どちらかをアップデートする。

  • Usersをアップデート

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        // リンク管理メソッドで互いにリムーブしあう
        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
        //ur.removeFromUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    これを実行すると、発行されるsqlは、次のようになる。

         Hibernate: update USERS set USERNAME=?, PASSWORD=? where USERS_ID=?
         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
    

    cascadeが設定されていないときと違い、ROLESもアップデートされているのがわかる。しかし、rolesからリムーブされたインスタンスについてはアップデートの対象にならないので、USER_ROLEからレコードが削除されていない。したがって、結果としては何も実行しなかったのと同じである。

  • Rolesをアップデート

    インスタンス取得、リンク管理メソッド実行はおなじで、アップデートするところだけ、変更して実行してみる。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        // リンク管理メソッドで互いにリムーブしあう
        updateUser.removeFromRoles(ur);  //Usersに実装したリンク管理メソッド
        //ur.removeFromUsers(updateUser);    //Rolesに実装したリンク管理メソッド
        
        //Roles側からアップデート
        generalDAO.updateRoles(ur);
    

    この例の様に実行したところ、発行されたsqlは次のようなものだった

         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: delete from USER_ROLE where ROLES_ID=? and USERS_ID=?
         
    

    予想通り、ROLESのアップデートのほかに、管理テーブルから、リムーブしたインスタンスの関連のレコードが削除されて、USER_ROLEの1,1のレコードがなくなっているのがわかる。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 2
    1 3
    2 1
    2 3
    3 2
  • Roles側からだけリムーブ、Users側からアップデート

    Rolesのアップデートの結果とUsersのアップデートの結果から、思いつくことがあり、次のように実行してみた。

    インスタンス取得は同じであるが、リンク管理メソッドを使わず、Roles側のusersからだけリムーブして、Usersインスタンスをアップデートする。

        
        // usersIdが1のUsersを取得する
        Users updateUser = generalDAO.findUserById(new Integer(1));
        
        // rolesIdが1のRolesを取得する
        Roles ur = null;
        Iterator<Roles>it = updateUser.getRoles().iterator();
        while(it.hasNext()){
            Roles tmp = it.next();
            if(tmp.getRolesId() == 1){
                ur = tmp;
                break;
            }
        }
        
        //片側だけリムーブ
        ur.getUsers().remove(updateUser);
        
        // Usersをアップデート
        generalDAO.updateUsers(updateUser);
    

    実行した結果、次のようなsqlが発行された。

         Hibernate: update USERS set USERNAME=?, PASSWORD=? where USERS_ID=?
         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: update ROLES set ROLE_NAME=? where ROLES_ID=?
         Hibernate: delete from USER_ROLE where ROLES_ID=? and USERS_ID=?
              
    

    USERSがアップデートされるとき、cascadeが設定されているので、UsersインスタンスupdateUserのrolesに含まれているRolesインスタンスもアップデートされる。そして、ROLES_ID=1のレコードに対応するRolesインスタンスのusersからはupdateUserがリムーブされているので、アップデートされたときに、管理テーブルから関連が削除されるのである。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    USER_ROLE
    USERS_ID ROLES_ID
    1 2
    1 3
    2 1
    2 3
    3 2

新たな関連を増やすインサート

DBを初期状態に戻してから、先の例と同じように、新しいUsersとRolesのインスタンスをつくり、その二つを関連づけて保存する。

  • Usersを保存

            //新しいUsersインスタンス
            Users insertUser = new Users();
            insertUser.setUsername("baa");
            insertUser.setPassword("pass4");
            
            //新しいRolesインスタンス
            Roles insertRole = new Roles();
            insertRole.setRoleName("mumumu");
            
            
            //リンク管理メソッドで関係付ける。どちらでも同じ
            //insertUser.addToRoles(insertRole);
            insertRole.addToUsers(insertUser);
            
            //Users保存
            generalDAO.saveUsers(insertUser);
    

    このように、先の例とまったく同じロジックなのだが、実行した結果、発行されたsqlは次のようになった。

         Hibernate: select nextval ('USERS_USERS_ID_SEQ')
         Hibernate: select nextval ('ROLES_ROLES_ID_SEQ')
         Hibernate: insert into USERS (USERNAME, PASSWORD, USERS_ID) values (?, ?, ?)
         Hibernate: insert into ROLES (ROLE_NAME, ROLES_ID) values (?, ?)
         Hibernate: insert into USER_ROLE (ROLES_ID, USERS_ID) values (?, ?)
         
    

    先の例では、USERS側しか保存されていなかったのだが、cascadeでROLES側も保存され、USER_ROLEにも関連が保存されている。結果、次のようになる。

    USERS
    USERS_ID USERNAME PASSWORD
    1 boo pass1
    2 hoo pass2
    3 woo pass3
    4 baa pass4
    ROLES
    ROLES_ID ROLE_NAME
    1 hoge
    2 haga
    3 hugo
    4 mumumu
    USER_ROLE
    USERS_ID ROLES_ID
    1 1
    1 2
    1 3
    2 1
    2 3
    3 2
    3 3
    4 4

many-to-manyの用途

many-to-manyの用途としては、今回のサンプルのように、ユーザー<−>ロールのように、別々に管理するレコードの関係付けをしたいといったものが考えられる。

今回、挙動を調べてみて、そのような用途でmany-to-manyを実装する際には、このように設定するべきだということがいくつか、おぼろげながらに見えたので、まとめて見ることにする。

inverseをどちらに設定するべきか

inverse="true"属性を与えられた変数の方が、関連テーブルの主導権を持つことになるので、端的にいえば、ということになる。

というのは、その管理テーブルのレコード管理の仕方に理由がある。先に示した例のアップデートに関するところを見るとわかるが、Rolesインスタンスをアップデートした際、一度、そのRolesに関連するUSER_ROLEのレコードは全て削除され、その後、そのRolesインスタンスが持っている情報に基づき、レコードが挿入されている。

これがもし、Usersの方に主導権があったらと想像しよう。ユーザー情報といえば、ログイン名や氏名、あるいは性別、年齢、その他いろんな情報を持つ可能性があるだろう。一方、ロールの方はシステムで使用する権限などを表すデータである。すると、より頻繁に更新する可能性があるのはUsersの方であると想像できる。もし、Usersの方に管理テーブルの主導権があるとしたら、ユーザー情報の変更のたびにまったく変更のない管理テーブルまで変更するSQLが発行されてしまう。それは、まったく無駄であり、パフォーマンス的によくない。

ゆえに、なのである。

ただし、「Usersの変更はRolesとの関連の変更を伴うことが多い」という場合はまったく逆になる。Users側に主導権があったほうがパフォーマンスがよくなる。間違えないでほしいのはあくまで「Rolesとの関連の変更を伴う」ことが多い場合はということであり、関連の変更だけが多いのであれば、やはり、変更頻度の少ない方にセットすべきである。

cascadeの設定

今回のUsersとRolesのような用途の場合、many-to-manyは基本的にcascadeは使わない方が良いように感じた。

UsersとRolesはどちらも、それそれに内容を管理して、双方同時に書き換えるといったことはほとんどないように思われる。それが、cascadeを設定すると、関連を通じて片方の処理がもう片方へ伝播してしまい、無駄な処理が増えることになる。cascade="all"を設定した場合の例を見てほしい。

しかしながら、cascadeを設定した方がよいという使い方もあるだろうと思われる(両方いっぺんに書き換えることの方が多いといった場合が該当する)。設定した方がパフォーマンスがよくなるというのであれば、適当な設定をすべきである。

ただ、基本的にはcascade無しの方がよいと思われる。cascadeあり、なし双方のdeleteの例をくらべて見てほしい。その理由がわかってもらえると思う。

プログラムソース

以下に、使用したDTOなどのソースを示す。

BaseDAO.java

package com.chikkun.common.login.db;

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

/**
 * DAOベースクラス
 * @author Chiku Kazuro
 * @version 1.1
 */
public class BaseDAO {

    /** セッションファクトリ */
    private static SessionFactory sessionFactory = null;

    /**
     * セッションファクトリを取得します。 最初に呼び出されたときに、初期化処理を行います。
     */
    static synchronized SessionFactory getSessionFactory()
            throws HibernateException {
        if (sessionFactory == null) {
            Configuration cfg = new Configuration();
            cfg.configure("hibernate.cfg.xml");
            sessionFactory = cfg.buildSessionFactory();
        }
        return sessionFactory;
    }

    /**
     * セッションを取得します。
     */
    public Session getSession() throws HibernateException {
        SessionFactory sf = getSessionFactory();
        return sf.openSession();
    }

}

GeneralDAO.java

package com.chikkun.common.login.db;

import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;


/**
 * @author meer
 * @version 1.1
 * baseDAOをextendsしたクラス
 */
public class GeneralDAO extends BaseDAO {
  

    
    public void saveUsers(Users u) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.save(u);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    public final void saveOrUpdateUsers(final Users _staff) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.saveOrUpdate(_staff);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
   
    public final void updateUsers(final Users _staff) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.update(_staff);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    public final void deleteUsers(final Users _staff) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.delete(_staff);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    public void saveRoles(Roles r) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.save(r);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    public final void saveOrUpdateRoles(final Roles r) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.saveOrUpdate(r);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
   
    public final void updateRoles(final Roles r) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.update(r);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    public final void deleteRoles(final Roles r) {
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            session.delete(r);
            transaction.commit();
        } catch (HibernateException ex) {
            if (transaction != null) {
                try {
                    transaction.rollback();
                } catch (HibernateException ignore) {
                }
            }
            throw new RuntimeException(ex.getMessage());
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
    }
    
    

    /**
     * @param id プライマリキー
     * @return List 実際はなかみは一つ
     * 
     */
    public final Users findUserById(final Integer id) {
        List<Users> list = null;
        Users user = null;
        Session session = null;
        try {
            session = getSession();
            Query query = session
            .createQuery("FROM Users as u where u.usersId = :id");
            query.setInteger("id", id.intValue());
            list = query.list();
            
            if(list != null && list.size() > 0){
                user = list.get(0);
            }

        } catch (HibernateException ex) {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
        return user;
    }

    /**
     * @return list 見つかった親のlist
     */
    public final List<Users> findAllUsers() {
        List<Users> list = null;
        Session session = null;
        try {
            session = getSession();
            Query query = session
            .createQuery("FROM Users AS u  ORDER BY u.usersId ASC");
            list = query.list();
        } catch (HibernateException ex) {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
        return list;
    }
    
    

    /**
     * @param id プライマリキー
     * @return List 実際はなかみは一つ
     * 
     */
    public final Roles findRoleById(final Integer id) {
        List<Roles> list = null;
        Roles role = null;
        Session session = null;
        try {
            session = getSession();
            Query query = session
            .createQuery("FROM Roles AS r WHERE r.rolesId = :id");
            query.setInteger("id", id.intValue());
            list = query.list();
            
            if(list != null && list.size() > 0){
                role = list.get(0);
            }

        } catch (HibernateException ex) {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
        return role;
    }

    /**
     * @return list 見つかった親のlist
     */
    public final List<Roles> findAllRoles() {
        List<Roles> list = null;
        Session session = null;
        try {
            session = getSession();
            Query query = session
            .createQuery("FROM Roles AS r ORDER BY r.rolesId ASC");
            list = query.list();
        } catch (HibernateException ex) {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (HibernateException ignore) {
                }
            }
        }
        return list;
    }


}

Users.java

package com.chikkun.common.login.db;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * hibernate テスト用 USERSのDTO
 * 
 * @author meer
 * @hibernate.class table="USERS"
 */
public class Users implements Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -6292804071602340401L;

    private Integer usersId;

    private String username;

    private String password;

    private Set<Roles> roles;

    /**
     * @return the usersId
     * @hibernate.id name="usersId" column="USERS_ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="USERS_USERS_ID_SEQ"
     */
    public Integer getUsersId() {
        return usersId;
    }

    /**
     * @param usersId
     *            the usersId to set
     */
    public void setUsersId(Integer usersId) {
        this.usersId = usersId;
    }

    /**
     * @return the username
     * @hibernate.property column="USERNAME"
     */
    public String getUsername() {
        return username;
    }

    /**
     * @param username
     *            the username to set
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * @return the password
     * @hibernate.property column="PASSWORD"
     */
    public String getPassword() {
        return password;
    }

    /**
     * @param password
     *            the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @return the roles
     * @hibernate.set table="USER_ROLE" inverse="true" lazy="false"
     * @hibernate.key column="USERS_ID"
     * @hibernate.many-to-many class="com.chikkun.common.login.db.Roles"
     *                         column="ROLES_ID"
     */
    public Set<Roles> getRoles() {
        return roles;
    }

    /**
     * @param roles
     *            the roles to set
     */
    protected void setRoles(Set<Roles> roles) {
        this.roles = roles;
    }
    
    
    public void addToRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            roles.add(r);
        }else{
            roles = new HashSet<Roles>();
            roles.add(r);
            this.setRoles(roles);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            users.add(this);
        }else{
            users = new HashSet<Users>();
            users.add(this);
            r.setUsers(users);
        }
    }
    
    public void removeFromRoles(Roles r){
        Set<Roles>roles = this.getRoles();
        if(roles != null){
            roles.remove(r);
        }
        Set<Users>users = r.getUsers();
        if(users != null){
            users.remove(this);
        }
    }
    
    

    /**
     * デフォルトコンストラクタ
     */
    public Users() {

    }

}

Roles.java

package com.chikkun.common.login.db;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * hibernate テスト用 ROLESのDTO
 * @author meer
 * @hibernate.class table="ROLES"
 *
 */
public class Roles implements Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -3784602532689050054L;
    
    
    
    private Integer rolesId;
    private String roleName;
    
    private Set<Users> users;
    
    
    /**
     * @return the rolesId
     * @hibernate.id name="rolesId" column="ROLES_ID" type="java.lang.Integer"
     *               unsaved-value="null" generator-class="sequence"
     * @hibernate.generator-param name="sequence" value="ROLES_ROLES_ID_SEQ"
     */
    public Integer getRolesId() {
        return rolesId;
    }
    /**
     * @param rolesId the rolesId to set
     */
    public void setRolesId(Integer rolesId) {
        this.rolesId = rolesId;
    }
    /**
     * @return the roleName
     * @hibernate.property column="ROLE_NAME"
     */
    public String getRoleName() {
        return roleName;
    }
    /**
     * @param roleName the roleName to set
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    
    
    
    /**
     * @return the users
     * @hibernate.set table="USER_ROLE"  lazy="false"
     * @hibernate.key column="ROLES_ID"
     * @hibernate.many-to-many class="com.chikkun.common.login.db.Users"
     *                         column="USERS_ID"
     */
    public Set<Users> getUsers() {
        return users;
    }
    /**
     * @param users the users to set
     */
    protected void setUsers(Set<Users> users) {
        this.users = users;
    }
    
    public void addToUsers(Users u){
        Set<Users>usersSet = this.getUsers();
        if(usersSet != null){
            usersSet.add(u);
        }else{
            usersSet = new HashSet<Users>();
            usersSet.add(u);
            this.setUsers(usersSet);
        }
        Set<Roles>roles = u.getRoles();
        if(roles != null){
            roles.add(this);
        }else{
            roles = new HashSet<Roles>();
            roles.add(this);
            u.setRoles(roles);
        }
    }
    
    public void removeFromUsers(Users u){
        Set<Users>usersSet = this.getUsers();
        if(usersSet != null){
            usersSet.remove(u);
        }
        Set<Roles>roles = u.getRoles();
        if(roles != null){
            roles.remove(this);
        }
    }
    
    
    
    /**
     * デフォルトコンストラクタ
     */
    public Roles() {
        
    }
    
    

}

番外 複合プライマリキーのあるテーブルのDTO

今回のmany-to-manyの例では、管理テーブルであるUSER_ROLEのDTOは必要ではなかったが、後に役立つかもしれないので、複合プライマリキーがあるテーブルのDTOの構築についてまとめておく。

この章をまとめるあたって、

を参考にした。

複合プライマリキーに対応するクラスを作成

普通のプライマリキーであれば、DTOにIntegerやStringなどの変数を用意するだけでよいが、複合プライマリキーの場合は、それに対応したクラスを作成する必要がある。

今回作成したクラスのソースを次に示す。なお、hbmファイルはXDoclet2で書き出しているため、ゲッターに書いてあるタグがきのさいとの例とは違う。

package com.chikkun.common.login.db;

import java.io.Serializable;

/**
 * @author meer
 * 
 */
public class UserRoleId implements Serializable {

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

    /**
     * デフォルトコンストラクタ
     */
    public UserRoleId() {
        
    }
    
    public UserRoleId(Integer usersId,Integer rolesId) {
        this.usersId = usersId;
        this.rolesId = rolesId;
    }

    private Integer usersId;

    private Integer rolesId;

    /**
     * @return the usersId
     * @hibernate.key-property column="USERS_ID"
     */
    public Integer getUsersId() {
        return usersId;
    }

    /**
     * @param usersId
     *            the usersId to set
     */
    public void setUsersId(Integer usersId) {
        this.usersId = usersId;
    }

    /**
     * @return the rolesId
     * @hibernate.key-property column="ROLES_ID"
     */
    public Integer getRolesId() {
        return rolesId;
    }

    /**
     * @param rolesId
     *            the rolesId to set
     */
    public void setRolesId(Integer rolesId) {
        this.rolesId = rolesId;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (this == o) {
            return true;
        }
        if (!(o instanceof UserRoleId)) {
            return false;
        }
        final UserRoleId userRoleId = (UserRoleId) o;
        if (!this.usersId.equals(userRoleId.getUsersId())) {
            return false;
        }
        if (!this.rolesId.equals(userRoleId.getRolesId())) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 31 + this.usersId.hashCode();
        hash = hash * 31 + this.rolesId.hashCode();
        return hash;
    }

}

ポイントは

  • Serializableをインプリメンツすること。
  • equals()とhashCode()をオーバーライドすること(toStringもやったほうがいい)

このクラスの変数をDTOに持たせる。

USER_ROLEテーブルに対応するクラスを作成

今回のUSER_ROLEテーブルはプライマリキーしかないテーブルなので、DTOのクラス変数は、先に作った複合プライマリキーのクラスの変数しかない。hibernateタグがcomposite-idになっているのがいつものDTOと違う点である。

package com.chikkun.common.login.db;

import java.io.Serializable;

/**
 * hibernate テスト用 USER_ROLEのDTO
 * 
 * @author meer
 * @hibernate.class table="USER_ROLE"
 */
public class UserRole implements Serializable {

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

    /**
     * デフォルトコンストラクタ
     */
    public UserRole() {

    }
    
    private UserRoleId userRoleId;

    /**
     * @return the userRoleId
     * @hibernate.composite-id 
     */
    public UserRoleId getUserRoleId() {
        return userRoleId;
    }

    /**
     * @param userRoleId the userRoleId to set
     */
    public void setUserRoleId(UserRoleId userRoleId) {
        this.userRoleId = userRoleId;
    }

}

このDTOをもちいて、他と同じようにHQLなどで、DBを操作することができる。たとえば、USER_ROLEのデータを全て持ってくるなら次のようにする。

    Session session = getSession();
    Query query = session
            .createQuery("FROM UserRole AS ur " +
                         "ORDER BY ur.userRoleId.usersId,ur.userRoleId.rolesId ");
    
    List<UserRole> list = query.list();
    
    session.close();