Hibernatにはいろいろな検索機能がありますが「Exampleによるクエリ」というのがあります。これは与えられたモデル(エンティティ)のプロパティにマッチするレコードを返す機能です。具体的には
Item example = new Item(); example.setItemCode("C003"); <- アイテムコードのカラムが「C003」のモノ List list = dao.findByExample(example); <-合致するモノをDBから検索してListで返却
こんな感じです。アイテムコードが「C003」となっているレコードを取得することができます。便利ですね。
実際にやってみます。
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`) );
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は省略 }
<?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>
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); }
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_ID | ITEM_CODE | NAME | INITIAL_PRICE | VERSION |
21 | C003 | 商品21 | 505.5 | 0 |
23 | C003 | 商品21_01 | 202.5 | 0 |
24 | C004 | 商品22_01 | 302.8 | 0 |
25 | C003 | 商品21_02 | 0 | 0 |
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]
となります。
先の検索条件を
Item example = new Item(); example.setItemCode("C003"); // example.setInitialPrice(202.5); List list = dao.findByExample(example);
と変更します。検索条件は「ITEM_CODE='C003'」ということで、
ITEM_ID | ITEM_CODE | NAME | INITIAL_PRICE | VERSION |
21 | C003 | 商品21 | 505.5 | 0 |
23 | C003 | 商品21_01 | 202.5 | 0 |
25 | C003 | 商品21_02 | 0 | 0 |
がヒットしそうですが、検索結果は
25 | C003 | 商品21_02 | 0 | 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のレコードを検索することができないですね。
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_ID | ITEM_CODE | NAME | INITIAL_PRICE | VERSION |
21 | C003 | 商品21 | 505.5 | 0 |
23 | C003 | 商品21_01 | 202.5 | 0 |
24 | C004 | 商品22_01 | 302.8 | 0 |
25 | C003 | 商品21_02 | 0 | 0 |
でした。
まずは完全一致から。
Item example = new Item(); example.setName("商品21"); List list = dao.findByExample(example);
と実行すると完全一致のため
ITEM_ID | ITEM_CODE | NAME | INITIAL_PRICE | VERSION |
21 | C003 | 商品21 | 505.5 | 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件になりました。
この記事は
現在のアクセス:14210