Top > MyPage
 

hibernateのorg.hibernate.criterion.Exampleについて

Example

Hibernateのorg.hibernate.criterion.Exampleを少し使ってみた。

例に使用するテーブルとエンティティは「hibernateの双方向many-to-manyについて」と同じものを使うので、そちらを参照してほしい。

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

単純なクライテリオン構築

まずは、次に示すコードを見てほしい。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("boo");
    
    Example example = Example.create(u);
    
    criteria.add(example);
    
    List<Users> list = criteria.list();

このコードの最後の変数の型からわかるように、USERSテーブルから情報を読みだしてUsersクラスのリストとして取得するのが目的である。

ここでポイントになるのが、Exampleである。これを用いてクライテリオンを構築しているのである。この例で説明すると、Usersインスタンスを新たに用意して、usernameにbooという文字列をセットして、そのインスタンスをExampleのcreateメソッドに渡してExampleインスタンスを作っている。そして、それをcriteriaにaddしている。こうすることで、usernameがbooであるインスタンスのみが取得されることになる。

このコードを実行したときのsqlは次のようになる()。

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                from USERS this_ 
                where (this_.USERNAME=?)

このようにExampleを使うと簡単に検索条件を設定することが出来る。が、Exampleで作ることが出来るのは簡単なクライテリオンなので複雑なことは出来ない。

なお、exampleをaddしない場合はwhere句がなくなるだけである。

以降、いろいろなExampleを試してみて、発行されるsqlを比較する。

Exampleの使い方

Usersインスタンスに値を入れない場合

先ほどの例ではusernameにbooという文字列を入れてデータを取得したが、Usersインスタンスを初期状態でつかったらどうなるだろうか。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    //変数に何も入れない    
    Example example = Example.create(u);
    
    criteria.add(example);
    
    List<Users> list = criteria.list();

これは、なにも条件を入れないで情報を取得するということになり、発行されるsqlは若干変わるが、exampleをaddしなかったのと結果的に同じになる。

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                from USERS this_ 
                where (1=1)
                

複数の変数に値を入れる場合

最初の例ではusernameにだけ値を入れて検索した。次は、passwordにも値を入れて、実行してみる。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("boo");
    u.setPassword("pass");
    
    Example example = Example.create(u);
    
    criteria.add(example);
    
    List<Users> list = criteria.list();

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

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                       from USERS this_ 
                       where (this_.USERNAME=? and this_.PASSWORD=?)
                

Usersインスタンスの値二つがandでwhere句に現れているのがわかる。このwhere句の括弧を奇妙に感じるのは私だけではないと思うが、とりあえず気にしないことにしよう。

likeで検索したい場合

上の例では全て、文字列が一致していなければ、データが返ってこない。例えばusernameがbooのレコードがあっても、そのレコードのpasswordがpassでなければ、条件に合致しないため、そのレコードは返ってくるデータに含まれない。

文字列をlike条件で検索したい場合は、次のようにする。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("boo");
    u.setPassword("pass");
    
    Example example = Example.create(u);
    
    //文字列はlikeで
    example.enableLike();
    
    criteria.add(example);
    
    List<Users> list = criteria.list();

発行されるsqlは次のようになる。

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                       from USERS this_ 
                       where (this_.USERNAME like ? and this_.PASSWORD like ?)
                       

確かにlikeにはなっている。ではあるが、バインドされる文字列はUsersにセットしたものそのままなのである。したがって、このように、自分で前方後方一致をコントロールする必要がある。

    
    //likeなのでパーセントをつけてセットする
    u.setUsername("%boo%");
    u.setPassword("%pass%");

このようにすれば、前方後方どちらもあいまい検索になる。

追記

上で書いたように自分でパーセントをつけることで、変数一つ一つの条件をコントロールすることが出来るが、みな、同じようにするならば、enableLikeの引数をかえるのがよい。

    
    //前方、後方ともにあいまい   '%hoge%'
    example.enableLike(MatchMode.ANYWHERE);
    
    //前方あいまい '%hoge'
    example.enableLike(MatchMode.START);
    
    //後方あいまい 'hoge%'
    example.enableLike(MatchMode.END);
    
    //前方、後方 一致   'hoge' デフォルト?
    example.enableLike(MatchMode.EXACT);

片方の変数だけlikeで検索したい場合

上の例のようにすることで、likeが使えるようになったわけだが、このやりかただと、enableLikeメソッドを実行したExampleインスタンスに含まれるもののみではあるが文字列は全てlikeになる。

次のようにしてみたところ、usernameはlikeでpasswordはイコールとすることが出来た。 参考までに。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("%boo%");
    
    Example example = Example.create(u);
    
    //文字列はlikeで
    example.enableLike();
    criteria.add(example);
    
    //新たなUsersインスタンスu2を用意
    Users u2 = new Users();
    u2.setPassword("pass");
            
    Example example2 = Example.create(u2);
    criteria.add(example2);
    
    List<Users> list = criteria.list();

このように、Exampleインスタンス複数をcriteriaにaddして実行してみたところ、次のようなsqlが発行されていた。

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                       from USERS this_ 
                       where (this_.USERNAME like ?) and (this_.PASSWORD=?)
                       

ごらんのように、usernameはlikeでpasswordはイコールとすることが出来た。

ORで検索したい場合

Exampleは単純なクライテリオンを構築するだけなので、ORを使いたい場合はを使う。

次の例は、一つのExampleでusernameとpasswordをlikeで指定して、もうひとつのExampleでpasswordを指定して、両者をorでクライテリアにaddしたものである。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("%boo%");
    u.setPassword("%pass%");
    
    Example example = Example.create(u);
    
    //文字列はlikeで
    example.enableLike();
    
    
    //新たなUsersインスタンスu2を用意
    Users u2 = new Users();
    u2.setPassword("pass");
            
    Example example2 = Example.create(u2);
    
    
    //Exampleをorでadd
    criteria.add(
                 Restrictions.or(
                                  example,
                                  example2
                 )
    );
    
    
    List<Users> list = criteria.list();

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

     Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                       this_.USERNAME as USERNAME0_0_, 
                       this_.PASSWORD as PASSWORD0_0_ 
                       from USERS this_ 
                       where (
                               (this_.USERNAME like ? and this_.PASSWORD like ?)
                                or 
                               (this_.PASSWORD=?)
                             )

Example二つがorでwhere句に現れているのがわかる。

関連するエンティティを条件に指定したい場合

今度は、ROLESテーブルのROLE_NAMEにhogeを持つレコードと関連を持つUSERSテーブルのレコードを抽出する場合を考える。

先ほどの例を流用して、一つのExampleでusernameをlikeで指定して、別のExampleでRolesのroleNameを指定する。

    Session session = getSession();
    Criteria criteria = session.createCriteria(Users.class);
    
    Users u = new Users();
    u.setUsername("%boo%");
    u.setPassword("%pass%");
    
    Example example = Example.create(u);
    
    //文字列はlikeで
    example.enableLike();
    
    criteria.add(example);
    
    //ROLESテーブル側の条件指定
    Roles r = new Roles();
    r.setRoleName("hoge");
    
    Criteria criteria2 = criteria.createCriteria("roles", "roles");
    criteria2.add(Example.create(r));
    
    
    List<Users> list = criteria.list();

すると、次のようなsqlが発行される。

     Hibernate: select this_.USERS_ID as USERS1_0_1_, 
                       this_.USERNAME as USERNAME0_1_, 
                       this_.PASSWORD as PASSWORD0_1_, 
                       roles3_.USERS_ID as USERS1_, 
                       roles1_.ROLES_ID as ROLES2_, 
                       roles1_.ROLES_ID as ROLES1_2_0_, 
                       roles1_.ROLE_NAME as ROLE2_2_0_ 
                       from USERS this_ 
                            inner join USER_ROLE roles3_ 
                            on this_.USERS_ID=roles3_.USERS_ID 
                            inner join ROLES roles1_ 
                            on roles3_.ROLES_ID=roles1_.ROLES_ID 
                       where (this_.USERNAME like ? and this_.PASSWORD like ?) 
                       and (roles1_.ROLE_NAME=?)

ROLE_NAMEも条件に入っていることがわかる。

ROLE_NAME='hoge'のレコードともROLE_NAME='haga'のレコードとも関連を持つUSERSをCriteriaを使って取得したい

例えば、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

このとき、ROLESテーブルのROLE_NAMEにhogeとhagaの文字列を持つレコードと関連を持つUSERSのレコードはUSERS_IDが1のレコードである。

そこで、「ROLE_NAMEにhogeとhagaの文字列を持つレコードと関連を持つUSERSのレコードを取得する」というのをCriteriaでやるにはどうすればいいか模索してみた。

  • Rolesをandでやってみる

    次のようなコードを書いてみた。

        Session session = getSession();
        Criteria criteria = session.createCriteria(Users.class);
        
        //ROLESテーブル側の条件指定
        Roles r = new Roles();
        r.setRoleName("hoge");
        
        Roles r2 = new Roles();
        r2.setRoleName("haga");
        
        //二つのRolesから作ったExampleをandで指定
        Criteria criteria2 = criteria.createCriteria("roles", "roles");
        criteria2.add(Example.create(r));
        criteria2.add(Example.create(r2));
        
        List<Users> list = criteria.list();
    

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

         Hibernate: select this_.USERS_ID as USERS1_0_1_, 
                           this_.USERNAME as USERNAME0_1_, 
                           this_.PASSWORD as PASSWORD0_1_, 
                           roles3_.USERS_ID as USERS1_, 
                           roles1_.ROLES_ID as ROLES2_, 
                           roles1_.ROLES_ID as ROLES1_2_0_, 
                           roles1_.ROLE_NAME as ROLE2_2_0_ 
                           from USERS this_ 
                                inner join USER_ROLE roles3_ 
                                on this_.USERS_ID=roles3_.USERS_ID 
                                inner join ROLES roles1_ 
                                on roles3_.ROLES_ID=roles1_.ROLES_ID 
                           where (roles1_.ROLE_NAME=?) 
                           and (roles1_.ROLE_NAME=?)
                           
    

    where句からわかるように、まったく意味のないsqlである。「ROLE_NAME=hogeであり、かつ、ROLE_NAME=hagaであるレコード」は存在し得ないからである。

  • Rolesを別々のCriteriaでandにしたら・・・

    addするCriteriaを分けたらいいのでは? という発想から、次のようなコードを書いてみた。

        Session session = getSession();
        Criteria criteria = session.createCriteria(Users.class);
        
        //ROLESテーブル側の条件指定
        Roles r = new Roles();
        r.setRoleName("hoge");
        
        Roles r2 = new Roles();
        r2.setRoleName("haga");
        
        //別々のCriteriaにadd
        Criteria criteria2 = criteria.createCriteria("roles", "roles");
        Criteria criteria3 = criteria.createCriteria("roles", "roles2");
        criteria2.add(Example.create(r));
        criteria3.add(Example.create(r2));
        
        List<Users> list = criteria.list();
    

    こういう書き方は出来ないようで、エクセプションが返っていた。

        org.hibernate.QueryException: duplicate association path: roles
    

    HQLでも同じ変数に対する複数のjoinを書いたら、おなじエクセプションが返ってくる。

  • 参考 正しくデータが返ってくるHQL

    HQLで次のように書くと、望んだデータが返ってくる。

        Session session = getSession();
        Query query = session.createQuery(
                        " SELECT DISTINCT u" +
                        " FROM Roles AS r JOIN r.users AS u, " +
                        " Roles AS r2 JOIN r2.users AS u2" +
                        " WHERE u.usersId = u2.usersId" +
                        " AND r.roleName = 'hoge'" +
                        " AND r2.roleName = 'haga'"
                       );
        List<Users> list = query.list();
        
    

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

         Hibernate: select distinct users2_.USERS_ID as USERS1_0_, 
                           users2_.USERNAME as USERNAME0_, 
                           users2_.PASSWORD as PASSWORD0_ 
                           from ROLES roles0_ 
                                  inner join USER_ROLE users1_ 
                                  on roles0_.ROLES_ID=users1_.ROLES_ID 
                                  inner join USERS users2_ 
                                  on users1_.USERS_ID=users2_.USERS_ID, 
                                ROLES roles3_ 
                                  inner join USER_ROLE users4_ 
                                  on roles3_.ROLES_ID=users4_.ROLES_ID 
                                  inner join USERS users5_ 
                                  on users4_.USERS_ID=users5_.USERS_ID 
                           where users2_.USERS_ID=users5_.USERS_ID 
                           and roles0_.ROLE_NAME='hoge' 
                           and roles3_.ROLE_NAME='haga'
    

    なお、このsqlは実行された一つ目のsqlである。マッピングでlazy="false"を設定しているので、実際はこの後にも関連するテーブルのレコードを読み出すsqlが発行されている。

         Hibernate: select roles0_.USERS_ID as USERS1_1_, 
                           roles0_.ROLES_ID as ROLES2_1_, 
                           roles1_.ROLES_ID as ROLES1_2_0_, 
                           roles1_.ROLE_NAME as ROLE2_2_0_ 
                    from USER_ROLE roles0_ 
                         left outer join ROLES roles1_ 
                         on roles0_.ROLES_ID=roles1_.ROLES_ID 
                    where roles0_.USERS_ID=?
                    
         Hibernate: select users0_.ROLES_ID as ROLES2_1_, 
                           users0_.USERS_ID as USERS1_1_, 
                           users1_.USERS_ID as USERS1_0_0_, 
                           users1_.USERNAME as USERNAME0_0_, 
                           users1_.PASSWORD as PASSWORD0_0_ 
                    from USER_ROLE users0_ 
                         left outer join USERS users1_ 
                         on users0_.USERS_ID=users1_.USERS_ID 
                    where users0_.ROLES_ID=?
                    
         Hibernate: select roles0_.USERS_ID as USERS1_1_, 
                           roles0_.ROLES_ID as ROLES2_1_, 
                           roles1_.ROLES_ID as ROLES1_2_0_, 
                           roles1_.ROLE_NAME as ROLE2_2_0_ 
                    from USER_ROLE roles0_ 
                         left outer join ROLES roles1_ 
                         on roles0_.ROLES_ID=roles1_.ROLES_ID 
                    where roles0_.USERS_ID=?
                    
         Hibernate: select users0_.ROLES_ID as ROLES2_1_, 
                           users0_.USERS_ID as USERS1_1_, 
                           users1_.USERS_ID as USERS1_0_0_, 
                           users1_.USERNAME as USERNAME0_0_, 
                           users1_.PASSWORD as PASSWORD0_0_ 
                    from USER_ROLE users0_ 
                         left outer join USERS users1_ 
                         on users0_.USERS_ID=users1_.USERS_ID 
                    where users0_.ROLES_ID=?
                         
         Hibernate: select roles0_.USERS_ID as USERS1_1_, 
                           roles0_.ROLES_ID as ROLES2_1_, 
                           roles1_.ROLES_ID as ROLES1_2_0_, 
                           roles1_.ROLE_NAME as ROLE2_2_0_ 
                    from USER_ROLE roles0_ 
                         left outer join ROLES roles1_ 
                         on roles0_.ROLES_ID=roles1_.ROLES_ID 
                    where roles0_.USERS_ID=?
                    
         Hibernate: select users0_.ROLES_ID as ROLES2_1_, 
                           users0_.USERS_ID as USERS1_1_, 
                           users1_.USERS_ID as USERS1_0_0_, 
                           users1_.USERNAME as USERNAME0_0_, 
                           users1_.PASSWORD as PASSWORD0_0_ 
                    from USER_ROLE users0_ 
                         left outer join USERS users1_ 
                         on users0_.USERS_ID=users1_.USERS_ID 
                    where users0_.ROLES_ID=?
                    
                    
    
  • サブクエリで絞込み、そこからさらに絞り込む。

    上のHQLと同じようには出来ないようなので(本当か?)、ちょっとアプローチを変えてみる。

    サブクエリでROLE_NAME='hoge'のレコードと関連を持つUSERSを抽出して(実際に抽出してるのはUSERS_ID)、そのなかで、ROLE_NAME='haga'のレコードと関連を持つを探すと、得られる結果は同じになると思われる。

        Session session = getSession();
        Criteria criteria = session.createCriteria(Users.class);
        
        
        //サブクエリ用
        DetachedCriteria subQuery = DetachedCriteria.forClass(Users.class);
        //サブクエリでほしいのはUSERS_IDなので射影を使う
        subQuery.setProjection(Property.forName("usersId"));
        DetachedCriteria subCriteria = subQuery.createCriteria("roles");
        
        //ROLESテーブル側の条件指定
        Roles r = new Roles();
        r.setRoleName("hoge");
        
        //サブクエリ用
        subCriteria.add(Example.create(r));
        
        
        
        //ROLESテーブル側の条件指定
        Roles r2 = new Roles();
        r2.setRoleName("haga");
        
        Criteria criteria2 = criteria.createCriteria("roles", "roles");
        criteria2.add(Example.create(r2));
        
        
        //userIdを返すサブクエリをつかうクライテリオンをadd (IN 条件にサブクエリのデータを使う)
        criteria.add(Property.forName("usersId").in(subQuery));
        
        
        List<Users> list = criteria.list();
    

    これを実行した結果、発行されたsql(一つ目のみ)は次のようになった。

         Hibernate: select this_.USERS_ID as USERS1_0_1_, 
                           this_.USERNAME as USERNAME0_1_, 
                           this_.PASSWORD as PASSWORD0_1_, 
                           roles3_.USERS_ID as USERS1_, 
                           roles1_.ROLES_ID as ROLES2_, 
                           roles1_.ROLES_ID as ROLES1_2_0_, 
                           roles1_.ROLE_NAME as ROLE2_2_0_ 
                    from USERS this_ 
                         inner join USER_ROLE roles3_ 
                         on this_.USERS_ID=roles3_.USERS_ID 
                         inner join ROLES roles1_ 
                         on roles3_.ROLES_ID=roles1_.ROLES_ID 
                    where (roles1_.ROLE_NAME=?) 
                    and this_.USERS_ID 
                          in (
                                select this_.USERS_ID as y0_ 
                                from USERS this_ 
                                     inner join USER_ROLE roles3_ 
                                     on this_.USERS_ID=roles3_.USERS_ID 
                                     inner join ROLES roles1_ 
                                     on roles3_.ROLES_ID=roles1_.ROLES_ID 
                                where (roles1_.ROLE_NAME=?)
                             )
    

    この後に、関連するテーブルのレコードを取得するsqlが発行される。

    これにより得られるデータは、先のHQLの場合と同じとなる。

    また、この例を少し変えて、以下のようにしても、最終的に得られる結果は同じとなる。サブクエリでROLE_NAME="hoge"で絞り、別のサブクエリでROLE_NAME="haga"で絞る。両方に共通しているものを取り出せばよいという発想である。

        Session session = getSession();
        Criteria criteria = session.createCriteria(Users.class);
        
        
        //サブクエリ用1
        DetachedCriteria subQuery = DetachedCriteria.forClass(Users.class);
        //サブクエリでほしいのはUSERS_IDなので射影を使う
        subQuery.setProjection(Property.forName("usersId"));
        DetachedCriteria subCriteria = subQuery.createCriteria("roles","roles");
        
        //ROLESテーブル側の条件指定
        Roles r = new Roles();
        r.setRoleName("hoge");
        
        //サブクエリ用
        subCriteria.add(Example.create(r));
        
        
        //サブクエリ用2
        DetachedCriteria subQuery2 = DetachedCriteria.forClass(Users.class);
        //サブクエリでほしいのはUSERS_IDなので射影を使う
        subQuery2.setProjection(Property.forName("usersId"));
        DetachedCriteria subCriteria2 = subQuery2.createCriteria("roles","roles2");
        
        //ROLESテーブル側の条件指定
        Roles r2 = new Roles();
        r2.setRoleName("haga");
        
        //サブクエリ用2
        subCriteria2.add(Example.create(r2));
        
        
        //userIdを返すサブクエリをつかうクライテリオンをadd (IN 条件にサブクエリのデータを使う)
        criteria.add(Property.forName("usersId").in(subQuery));
        criteria.add(Property.forName("usersId").in(subQuery2));
        
        List<Users> list = criteria.list();
    

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

         Hibernate: select this_.USERS_ID as USERS1_0_0_, 
                           this_.USERNAME as USERNAME0_0_, 
                           this_.PASSWORD as PASSWORD0_0_ 
                    from USERS this_ 
                    where this_.USERS_ID 
                          in (
                                select this_.USERS_ID as y0_ 
                                from USERS this_ 
                                     inner join USER_ROLE roles3_ 
                                     on this_.USERS_ID=roles3_.USERS_ID 
                                     inner join ROLES roles1_ 
                                     on roles3_.ROLES_ID=roles1_.ROLES_ID 
                                where (roles1_.ROLE_NAME=?)
                              ) 
                    and this_.USERS_ID 
                          in (
                                select this_.USERS_ID as y0_ 
                                from USERS this_ 
                                     inner join USER_ROLE roles3_ 
                                     on this_.USERS_ID=roles3_.USERS_ID 
                                     inner join ROLES roles1_ 
                                     on roles3_.ROLES_ID=roles1_.ROLES_ID 
                                where (roles1_.ROLE_NAME=?)
                              )