// 下階層用テンプレート
#topicpath
----
//ここにコンテンツを記述します。

#contents

Hibernatにはいろいろな検索機能がありますが「Exampleによるクエリ」というのがあります。これは与えられたモデル(エンティティ)のプロパティにマッチするレコードを返す機能です。具体的には
 Item example = new Item();
 example.setItemCode("C003");  <- アイテムコードのカラムが「C003」のモノ
 List list = dao.findByExample(example); <-合致するモノをDBから検索してListで返却
こんな感じです。アイテムコードが「C003」となっているレコードを取得することができます。便利ですね。

**準備 [#u9da7deb]
実際にやってみます。

***テーブルと対応するJavaBeans [#ma3a1802]

#ref(item.png)

-DDL
 CREATE TABLE `ITEM` (
  `ITEM_ID` int(10) unsigned NOT NULL auto_increment,
  `ITEM_CODE` varchar(10) NOT NULL default '',
  `NAME` varchar(256) NOT NULL default '',
  `INITIAL_PRICE` double NOT NULL default '0',
  `VERSION` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`ITEM_ID`)
 );

-JavaBeans
 package nu.mine.kino.entity.webdb1;
 
 import org.apache.commons.lang.builder.ToStringBuilder;
 
 public class Item implements java.io.Serializable {
   private int itemId;
   private int version;
   private String itemCode;
   private String name;
   private double initialPrice;
  
   public Item() {
   }
  
   public Item(int itemId, String itemCode, String name, double initialPrice) {
     this.itemId = itemId;
     this.itemCode = itemCode;
     this.name = name;
     this.initialPrice = initialPrice;
   }
  
   public Item(String itemCode, String name, double initialPrice) {
     this.itemCode = itemCode;
     this.name = name;
     this.initialPrice = initialPrice;
   }
  
   public String toString() {
     return new ToStringBuilder(this).append("id", getItemId()).append(
         "code", getItemCode()).append("Name", getName()).append(
         "price", getInitialPrice()).toString();
   }
  
   getter/setterは省略
 }

-Item.hbm.xml
 <?xml version="1.0"?>
 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 <!-- Generated 2006/09/24 21:45:30 by Hibernate Tools 3.2.0.beta6a -->
 <hibernate-mapping>
   <class name="nu.mine.kino.entity.webdb1.Item" table="ITEM" catalog="webdb1">
     <id name="itemId" type="int">
       <column name="ITEM_ID" />
       <generator class="increment" />
     </id>
     <version name="version" type="int">
       <column name="VERSION" not-null="true" />
     </version>
     <property name="itemCode" type="string">
       <column name="ITEM_CODE" length="10" not-null="true" />
     </property>
     <property name="name" type="string">
       <column name="NAME" length="256" not-null="true" />
     </property>
     <property name="initialPrice" type="double">
       <column name="INITIAL_PRICE" precision="22" scale="0" not-null="true" />
     </property>
   </class>
 </hibernate-mapping>


***DAO [#fe878fed]
-DAO (interface)
 package nu.mine.kino.entity.webdb1;
 
 import java.util.List;
 
 public interface IItemDAO {
     Item save(Item item);
     void delete(Item item);
     Item update(Item item);
     Item findById(int id);
     List findByExample(Item instance);
 }


-DAOの実装(Powered by Spring)
 package nu.mine.kino.entity.webdb1;
 
 import org.apache.log4j.Logger;
 
 import java.util.List;
 
 import org.hibernate.criterion.DetachedCriteria;
 import org.hibernate.criterion.Example;
 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
 
 /**
  * @author Masatomi KINO
  * @version $Revision$
  * @spring.bean id = "itemDao"
  * @spring.property name = "sessionFactory" ref = "sessionFactory"
  */
 public class ItemDAO extends HibernateDaoSupport implements IItemDAO {
   private static final Logger logger = Logger.getLogger(ItemDAO.class);
 
   public List findByExample(Item instance) {
     DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
     List results = getHibernateTemplate().findByCriteria(dc);
     return results;
   }
   .. その他の実装は省略
 }


**やってみる [#e61da8fe]
DAOの単体テストクラスを作って、その中で実際に検索を行います。DAOの単体テストクラスはSpring謹製のAbstractTransactionalDataSourceSpringContextTestsを継承したクラスとして作成します((なんというか今回の議論の本質ではないので適当に流してください))。

***データのセットアップ [#s32caa72]
tableには以下のようなデータが入っています。
|LEFT:ITEM_ID|LEFT:ITEM_CODE|LEFT:NAME|LEFT:INITIAL_PRICE|LEFT:VERSION|
|RIGHT:21|LEFT:C003|LEFT:商品21|RIGHT:505.5|RIGHT:0|
|RIGHT:23|LEFT:C003|LEFT:商品21_01|RIGHT:202.5|RIGHT:0|
|RIGHT:24|LEFT:C004|LEFT:商品22_01|RIGHT:302.8|RIGHT:0|
|RIGHT:25|LEFT:C003|LEFT:商品21_02|RIGHT:0|RIGHT:0|


***単体テストクラス [#lf8dbd33]
 public class ItemDAOTestWithSpring extends
     AbstractTransactionalDataSourceSpringContextTests {
   public void testFindByExample() {
     logger.debug("testFindByExample() - start");
 
     Item example = new Item();
     example.setItemCode("C003");
     example.setInitialPrice(202.5);
 
     List list = dao.findByExample(example);
     logger.debug("結果は " + list.size() + " 件でした");
     Iterator iterator = list.iterator();
     while (iterator.hasNext()) {
       Item element = (Item) iterator.next();
       logger.debug(element);
     }
 
     logger.debug("testFindByExample() - end");
   }
   .....あとはいろいろ省略
 }

***検索結果 [#b4e8b83a]
上の検索結果ですが、検索条件は「ITEM_CODE='C003' and INITIAL_PRICE=202.5」ということで
 結果は 1 件でした
 nu.mine.kino.entity.webdb1.Item@d5c0f9[id=23,code=C003,Name=商品21_01,price=202.5]
となります。

**いろいろな検索条件 [#p51d792d]


***0を除く [#r103ab9f]
先の検索条件を
 Item example = new Item();
 example.setItemCode("C003");
 // example.setInitialPrice(202.5);
 List list = dao.findByExample(example);
と変更します。検索条件は「ITEM_CODE='C003'」ということで、
|LEFT:ITEM_ID|LEFT:ITEM_CODE|LEFT:NAME|LEFT:INITIAL_PRICE|LEFT:VERSION|
|RIGHT:21|LEFT:C003|LEFT:商品21|RIGHT:505.5|RIGHT:0|
|RIGHT:23|LEFT:C003|LEFT:商品21_01|RIGHT:202.5|RIGHT:0|
|RIGHT:25|LEFT:C003|LEFT:商品21_02|RIGHT:0|RIGHT:0|


がヒットしそうですが、検索結果は
|RIGHT:25|LEFT:C003|LEFT:商品21_02|RIGHT:0|RIGHT:0|
の1件のみになります。これはこのテーブルのINITIAL_PRICEのデフォルト値がdefault '0' であるため、自動的に example.setInitialPrice(0);がセットされて検索されるからです。

さて
 Item example = new Item();
 example.setItemCode("C003");
 List list = dao.findByExample(example);
としたら「ITEM_CODE='C003'」のレコードがすべて返却されるのが感覚に合ってる気がします。そのためには「値をセットしなかった結果デフォルト値になってしまったカラムは検索条件から除く」としたいわけですが、そのようなやり方はさがしても見つかりませんでした。ただ、今回のように「0とセットされたカラムは検索条件から除く」ということはできます。それにはDAO内の
 DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
 List results = getHibernateTemplate().findByCriteria(dc);
 return results;
を
 DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
 dc.add(Example.create(instance).excludeZeroes()); <-追加。
 List results = getHibernateTemplate().findByCriteria(dc);
 return results;
とします。この設定をしておくと、0とセットされたカラムを検索条件から除外するため結果的に「ITEM_CODE='C003'」だけが検索条件としてセットされます。結果は想定通り
 結果は 3 件でした
 nu.mine.kino.entity.webdb1.Item@d5c0f9[id=21,code=C003,Name=商品21,price=505.5]
 nu.mine.kino.entity.webdb1.Item@1701bdc[id=23,code=C003,Name=商品21_01,price=202.5]
 nu.mine.kino.entity.webdb1.Item@1353249[id=25,code=C003,Name=商品21_02,price=0.0]
となりました。ただこれだと、ホントにデータが0のレコードを検索することができないですね。

***あるカラムだけ検索条件から除く [#xdf118f4]
DAOで以下のように設定します。
 DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
 dc.add(Example.create(instance).excludeProperty("initialPrice"));
 List results = getHibernateTemplate().findByCriteria(dc);
 return results;
こうしておくと、initialPrice フィールドは検索条件から除外されます。従って、
 Item example = new Item();
 example.setItemCode("C003");
 example.setInitialPrice(100000.0); <-除外されたカラム
 List list = dao.findByExample(example);
などとしても検索結果は
 結果は 3 件でした
 nu.mine.kino.entity.webdb1.Item@d5c0f9[id=21,code=C003,Name=商品21,price=505.5]
 nu.mine.kino.entity.webdb1.Item@1701bdc[id=23,code=C003,Name=商品21_01,price=202.5]
 nu.mine.kino.entity.webdb1.Item@1353249[id=25,code=C003,Name=商品21_02,price=0.0]
となります。initialPriceは正しく小文字で始めないとダメみたいですね。


***そもそも、識別子は除外される。 [#a20c4447]
キーやバージョンのプロパティなどは検索条件から除外されるみたいです。
 Item example = new Item();
 example.setItemId(1);    <-適当な値
 example.setVersion(100); <-適当な値
 example.setItemCode("C003");
 List list = dao.findByExample(example);
としても3件ヒットしました。また、nullや''がセットされているカラムも除外されていますね。nameなどはそれにあたります。デフォルト値がありますが''がセットされるから、検索条件としては除外されています。



***部分一致とか [#e58f454b]
次にNameカラムでいろいろ試してみます。DAOは
 DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
 dc.add(Example.create(instance).excludeZeroes());
 List results = getHibernateTemplate().findByCriteria(dc);
とします。再掲ですがデータは

|LEFT:ITEM_ID|LEFT:ITEM_CODE|LEFT:NAME|LEFT:INITIAL_PRICE|LEFT:VERSION|
|RIGHT:21|LEFT:C003|LEFT:商品21|RIGHT:505.5|RIGHT:0|
|RIGHT:23|LEFT:C003|LEFT:商品21_01|RIGHT:202.5|RIGHT:0|
|RIGHT:24|LEFT:C004|LEFT:商品22_01|RIGHT:302.8|RIGHT:0|
|RIGHT:25|LEFT:C003|LEFT:商品21_02|RIGHT:0|RIGHT:0|

でした。

まずは完全一致から。
 Item example = new Item();
 example.setName("商品21");
 List list = dao.findByExample(example);
と実行すると完全一致のため
|LEFT:ITEM_ID|LEFT:ITEM_CODE|LEFT:NAME|LEFT:INITIAL_PRICE|LEFT:VERSION|
|RIGHT:21|LEFT:C003|LEFT:商品21|RIGHT:505.5|RIGHT:0|
がヒットします。
 結果は 1 件でした
 nu.mine.kino.entity.webdb1.Item@1701bdc[id=21,code=C003,Name=商品21,price=505.5]

部分一致させるためにはDAO内で部分一致を有効にする必要があります。以下のようにセットします。
 DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
 dc.add(Example.create(instance).excludeZeroes().enableLike()); <-enableLikeを追加
 List results = getHibernateTemplate().findByCriteria(dc);
検索側も
 example.setName("商品21%");
とすることで部分一致検索となります。
 結果は 3 件でした
 nu.mine.kino.entity.webdb1.Item@1579a30[id=21,code=C003,Name=商品21,price=505.5]
 nu.mine.kino.entity.webdb1.Item@4bfe6b[id=23,code=C003,Name=商品21_01,price=202.5]
 nu.mine.kino.entity.webdb1.Item@12c5431[id=25,code=C003,Name=商品21_02,price=0.0]
と検索結果は3件になりました。






**参考リンク [#c9cc61a2]
-[[Kodersのサンプル(QueryByExampleTest.java)>http://www.koders.com/java/fidD360DC5B7778E6DA7D6722922D7696753AF87ECF.aspx]]
-http://anonhibernate.labs.jboss.com/trunk/Hibernate3/doc/reference/ja/modules/query_criteria.xml

----
この記事は
#vote(おもしろかった[5],そうでもない[3])
#vote(おもしろかった[6],そうでもない[3])

#comment
#topicpath


SIZE(10){現在のアクセス:&counter;}

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS