Top / Hibernate / Exampleによるクエリ

Hibernatにはいろいろな検索機能がありますが「Exampleによるクエリ」というのがあります。これは与えられたモデル(エンティティ)のプロパティにマッチするレコードを返す機能です。具体的には

Item example = new Item();
example.setItemCode("C003");  <- アイテムコードのカラムが「C003」のモノ
List list = dao.findByExample(example); <-合致するモノをDBから検索してListで返却

こんな感じです。アイテムコードが「C003」となっているレコードを取得することができます。便利ですね。

準備

実際にやってみます。

テーブルと対応するJavaBeans?

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

  • 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;
      }
      .. その他の実装は省略
    }

やってみる

DAOの単体テストクラスを作って、その中で実際に検索を行います。DAOの単体テストクラスはSpring謹製のAbstractTransactionalDataSourceSpringContextTests?を継承したクラスとして作成します*1

データのセットアップ

tableには以下のようなデータが入っています。

ITEM_IDITEM_CODENAMEINITIAL_PRICEVERSION
21C003商品21505.50
23C003商品21_01202.50
24C004商品22_01302.80
25C003商品21_0200

単体テストクラス

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");
  }
  .....あとはいろいろ省略
}

検索結果

上の検索結果ですが、検索条件は「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]

となります。

いろいろな検索条件

0を除く

先の検索条件を

Item example = new Item();
example.setItemCode("C003");
// example.setInitialPrice(202.5);
List list = dao.findByExample(example);

と変更します。検索条件は「ITEM_CODE='C003'」ということで、

ITEM_IDITEM_CODENAMEINITIAL_PRICEVERSION
21C003商品21505.50
23C003商品21_01202.50
25C003商品21_0200

がヒットしそうですが、検索結果は

25C003商品21_0200

の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のレコードを検索することができないですね。

あるカラムだけ検索条件から除く

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は正しく小文字で始めないとダメみたいですね。

そもそも、識別子は除外される。

キーやバージョンのプロパティなどは検索条件から除外されるみたいです。

Item example = new Item();
example.setItemId(1);    <-適当な値
example.setVersion(100); <-適当な値
example.setItemCode("C003");
List list = dao.findByExample(example);

としても3件ヒットしました。また、nullやがセットされているカラムも除外されていますね。nameなどはそれにあたります。デフォルト値がありますががセットされるから、検索条件としては除外されています。

部分一致とか

次にNameカラムでいろいろ試してみます。DAOは

DetachedCriteria dc = DetachedCriteria.forClass(Item.class);
dc.add(Example.create(instance).excludeZeroes());
List results = getHibernateTemplate().findByCriteria(dc);

とします。再掲ですがデータは

ITEM_IDITEM_CODENAMEINITIAL_PRICEVERSION
21C003商品21505.50
23C003商品21_01202.50
24C004商品22_01302.80
25C003商品21_0200

でした。

まずは完全一致から。

Item example = new Item();
example.setName("商品21");
List list = dao.findByExample(example);

と実行すると完全一致のため

ITEM_IDITEM_CODENAMEINITIAL_PRICEVERSION
21C003商品21505.50

がヒットします。

結果は 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件になりました。

参考リンク


この記事は

選択肢 投票
おもしろかった 6  
そうでもない 3  

Top / Hibernate / Exampleによるクエリ

現在のアクセス:14176


*1 なんというか今回の議論の本質ではないので適当に流してください

添付ファイル: fileItem.java 654件 [詳細] fileItem.hbm.xml 698件 [詳細] fileitem.png 1126件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2008-10-27 (月) 17:36:13 (5653d)