atwiki-logo
  • 新規作成
    • 新規ページ作成
    • 新規ページ作成(その他)
      • このページをコピーして新規ページ作成
      • このウィキ内の別ページをコピーして新規ページ作成
      • このページの子ページを作成
    • 新規ウィキ作成
  • 編集
    • ページ編集
    • ページ編集(簡易版)
    • ページ名変更
    • メニュー非表示でページ編集
    • ページの閲覧/編集権限変更
    • ページの編集モード変更
    • このページにファイルをアップロード
    • メニューを編集
    • 右メニューを編集
  • バージョン管理
    • 最新版変更点(差分)
    • 編集履歴(バックアップ)
    • アップロードファイル履歴
    • ページ操作履歴
  • ページ一覧
    • ページ一覧
    • このウィキのタグ一覧
    • このウィキのタグ(更新順)
    • このページの全コメント一覧
    • このウィキの全コメント一覧
    • おまかせページ移動
  • RSS
    • このウィキの更新情報RSS
    • このウィキ新着ページRSS
  • ヘルプ
    • ご利用ガイド
    • Wiki初心者向けガイド(基本操作)
    • このウィキの管理者に連絡
    • 運営会社に連絡(不具合、障害など)
ページ検索 メニュー
とりあえず雑記帳(跡地)
  • ウィキ募集バナー
  • 目安箱バナー
  • 操作ガイド
  • 新規作成
  • 編集する
  • 全ページ一覧
  • 登録/ログイン
ページ一覧
とりあえず雑記帳(跡地)
  • ウィキ募集バナー
  • 目安箱バナー
  • 操作ガイド
  • 新規作成
  • 編集する
  • 全ページ一覧
  • 登録/ログイン
ページ一覧
とりあえず雑記帳(跡地)
ページ検索 メニュー
  • 新規作成
  • 編集する
  • 登録/ログイン
  • 管理メニュー
管理メニュー
  • 新規作成
    • 新規ページ作成
    • 新規ページ作成(その他)
      • このページをコピーして新規ページ作成
      • このウィキ内の別ページをコピーして新規ページ作成
      • このページの子ページを作成
    • 新規ウィキ作成
  • 編集
    • ページ編集
    • ページ編集(簡易版)
    • ページ名変更
    • メニュー非表示でページ編集
    • ページの閲覧/編集権限変更
    • ページの編集モード変更
    • このページにファイルをアップロード
    • メニューを編集
    • 右メニューを編集
  • バージョン管理
    • 最新版変更点(差分)
    • 編集履歴(バックアップ)
    • アップロードファイル履歴
    • ページ操作履歴
  • ページ一覧
    • このウィキの全ページ一覧
    • このウィキのタグ一覧
    • このウィキのタグ一覧(更新順)
    • このページの全コメント一覧
    • このウィキの全コメント一覧
    • おまかせページ移動
  • RSS
    • このwikiの更新情報RSS
    • このwikiの新着ページRSS
  • ヘルプ
    • ご利用ガイド
    • Wiki初心者向けガイド(基本操作)
    • このウィキの管理者に連絡
    • 運営会社に連絡する(不具合、障害など)
  • atwiki
  • とりあえず雑記帳(跡地)
  • Slim3
  • 文字列の部分一致検索とページング

とりあえず雑記帳(跡地)

文字列の部分一致検索とページング

最終更新:2012年06月19日 02:06

Bot(ページ名リンク)

- view
管理者のみ編集可
Slim3で部分一致検索(Like検索)を頑張ってみる

(2012/06/19追記)
  • 検索結果のキャッシュのことを考えたら、本ページのような小細工をせずに、おとなしく結果全件をListで取得したほうが良い気がしてきました…。
  • 検索結果の件数が大きい場合を想定して、Listで取得するのを控えていたのですが、そもそも、そんな検索を許さないようにしたほうが健全ですしね…。

Datastoreでの文字列検索

  • GAEのDatastoreでは、Entityの検索方法として、プロパティの文字列の前方一致をネイティブでサポートしている。
  • よって、Slim3でも基本は文字列の前方一致となる。

package jp.fujiyan.gae.datastoretest;
 
import java.text.DecimalFormat;
import java.util.List;
 
import jp.fujiyan.gae.datastoretest.meta.FooMeta;
import jp.fujiyan.gae.datastoretest.model.Foo;
import junit.framework.Assert;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slim3.datastore.Datastore;
 
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
 
public class QueryTest {
    private final LocalServiceTestHelper helper =
            new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
 
    private static DecimalFormat FORMAT = new DecimalFormat("0000");
 
 
	@Before
	public void setUp() throws Exception {
		helper.setUp();
	}
 
	@After
	public void tearDown() throws Exception {
		helper.tearDown();
	}
 
	@Test
	public void test1() {
		// 2000個のEntityを作成
		for (int i = 1; i <= 2000; i++) {
			Foo foo = new Foo();
			foo.setName("Foo" + FORMAT.format(i));
			Datastore.put(foo);
		}
 
		// "Foo11"で始まるEntityは100個
		List<Foo> list = Datastore.query(Foo.class).filter(FooMeta.get().name.startsWith("Foo11")).asList();
 
		Assert.assertEquals(100, list.size());
	}
}
 


InMemoryFilter

  • 前方一致だけでは何かと不便なので、Slim3では部分一致でも検索が可能な手段を用意している。
  • InMemoryFilterという仕組みで、Datastoreから返された検索結果に対してフィルタリングをかけている。

※以降、面倒なのテストメソッドのみ掲示…
@Test
	public void test1() {
		// 2000個のEntityを作成
		for (int i = 1; i <= 2000; i++) {
			Foo foo = new Foo();
			foo.setName("Foo" + FORMAT.format(i));
			Datastore.put(foo);
		}
 
		// "2"を含むEntityは543個
		List<Foo> list = Datastore.query(Foo.class).filterInMemory(FooMeta.get().name.contains("2")).asList();
 
		Assert.assertEquals(543, list.size());
	}
 


limitとoffset

  • ページングを実施する上で必要な処理として、例えば、ページ毎10件のページング処理で、8ページ目に該当するレコードを取得する場合には、71件目から10件分だけレコードを抽出する処理がある。
  • GAEのDatastoreでは、Queryのn件目から取得(offset)、クエリの結果をn件まで取得(limit)、というのがネイティブでサポートされている。
  • よって、Slim3でもoffsetやlimitが利用可能
  • これさえ使えば、ページングも楽々ですよ。

@Test
	public void test1() {
		// 2000個のEntityを作成
		for (int i = 1; i <= 2000; i++) {
			Foo foo = new Foo();
			foo.setName("Foo" + FORMAT.format(i));
			Datastore.put(foo);
		}
 
		// "Foo11"で始まるEntityは100個
		List<Foo> list = Datastore.query(Foo.class).filter(FooMeta.get().name.startsWith("Foo11")).offset(71).limit(10).asList();
 
		Assert.assertEquals(10, list.size());
		Assert.assertEquals("Foo1171", list.get(0).getName());
		Assert.assertEquals("Foo1172", list.get(1).getName());
		Assert.assertEquals("Foo1173", list.get(2).getName());
		Assert.assertEquals("Foo1174", list.get(3).getName());
		Assert.assertEquals("Foo1175", list.get(4).getName());
		Assert.assertEquals("Foo1176", list.get(5).getName());
		Assert.assertEquals("Foo1177", list.get(6).getName());
		Assert.assertEquals("Foo1178", list.get(7).getName());
		Assert.assertEquals("Foo1179", list.get(8).getName());
		Assert.assertEquals("Foo1180", list.get(9).getName());
	}
 

InMemoryFilterとlimit/offsetは併用できない…

  • じゃあ、あとはInMemoryFilterを使えば、部分一致検索の結果に対してページングが出来そうですね。

@Test
	public void test1() {
		// 2000個のEntityを作成
		for (int i = 1; i <= 2000; i++) {
			Foo foo = new Foo();
			foo.setName("Foo" + FORMAT.format(i));
			Datastore.put(foo);
		}
 
		// "2"を含むEntityは543個
		List<Foo> list = Datastore.query(Foo.class).filterInMemory(FooMeta.get().name.contains("2")).offset(0).limit(10).asList();
 
		// 543個の中から先頭10個をとったはずが…
		Assert.assertEquals(10, list.size());
	}
 

上記のテストの実施結果は

junit.framework.AssertionFailedError: expected:<10> but was:<1>
	...

  • なんと1件しか取れていません。
  • これは、SlimがGAEのネイティブなQueryを先に実施し、その結果に対してInMemoryFilterを適用するからです。
    • 上記の場合、2000個のEntityに対して、最初にoffset(0).limit(10)が実施されます。その結果、Foo0001~Foo0010の10個のEntityが返されます。
    • そのFoo0001~Foo0010の結果に対して、contains("2")のInMemoryFilterが実施されるため、結果はFoo0002の1件しか残らなくなるのです。

じゃあ、地道にasIterator()で拾い上げようと思ったら…

  • offsetやlimitが使えないとなると、クエリ結果を最初から順番に拾い上げて、ページ先頭レコードまでスキップした後、目的の件数分取得する、しかなさそう。
  • ちょうどModelQuery#asIterator()というメソッドがクエリ結果を拾い上げるIteratorを返してくれそうです。では早速…

java.lang.IllegalStateException: In case of asIterator(), you cannot specify filterInMemory().
	...

  • はい、一旦InMemoryFilterを適用してしまうと、asIterator()は使えないのです。
  • まぁ、Slim3の仕様として諦めてください…。

そこそこ自前で処理するしかなさそうです

  • ということで、何もFilterを適用しないModelQueryの結果に対して、指定の条件でフィルタリングした結果を拾い上げるIteratorを作るしか無さそうです。
  • キモなところはApacheのCommons Collectionsを使っています。
    • まぁ代わりにGuavaのIteratorsを使ってもイイかと思います。

package jp.fujiyan.gae.datastoretest;
 
import java.util.Iterator;
 
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.slim3.datastore.InMemoryFilterCriterion;
import org.slim3.datastore.ModelQuery;
 
/**
 * InMemoryFilterでasIterator()が使えないため用意しています。
 * @author Fujiyan
 *
 * @param <M>
 */
public class ModelQueryIterator<M> implements Iterator<M> {
 
	public interface IPredicate<M> {
		boolean evaluate(M model);
	}
 
	protected FilterIterator delegate;
 
	public ModelQueryIterator(ModelQuery<M> query, InMemoryFilterCriterion criterion) {
		final InMemoryFilterCriterion innerCriterion = criterion;
 
		this.delegate = new FilterIterator(query.asIterator(),
			new Predicate() {
				@Override
				public boolean evaluate(Object obj) {
					return innerCriterion.accept((M) obj);
				}
			}
		);
	}
 
	@Override
	public boolean hasNext() {
		return delegate.hasNext();
	}
 
	@Override
	public M next() {
		return (M) delegate.next();
	}
 
	@Override
	public void remove() {
		delegate.remove();
	}
}
 


  • いろいろ工夫の余地はありますが、とりあえず検証用のサンプルとして…。
  • 使い方は下記の通り

@Test
	public void test1() {
		// 2000個のEntityを作成
		for (int i = 1; i <= 2000; i++) {
			Foo foo = new Foo();
			foo.setName("Foo" + FORMAT.format(i));
			Datastore.put(foo);
		}
 
		// "2"を含むEntityは543個
		Iterator<Foo> iterator = new ModelQueryIterator<Foo>(Datastore.query(Foo.class), FooMeta.get().name.contains("2"));
 
		Assert.assertEquals("Foo0002", iterator.next().getName());
		Assert.assertEquals("Foo0012", iterator.next().getName());
		Assert.assertEquals("Foo0020", iterator.next().getName());
		Assert.assertEquals("Foo0021", iterator.next().getName());
		Assert.assertEquals("Foo0022", iterator.next().getName());
		Assert.assertEquals("Foo0023", iterator.next().getName());
		Assert.assertEquals("Foo0024", iterator.next().getName());
		Assert.assertEquals("Foo0025", iterator.next().getName());
		Assert.assertEquals("Foo0026", iterator.next().getName());
		Assert.assertEquals("Foo0027", iterator.next().getName());
	}
 

  • あとは、このModelQueryIteratorで、指定のレコードまでスキップ/指定の件数を取得、というお決まりのページング処理を行えば、何とかなるかと思います。
「文字列の部分一致検索とページング」をウィキ内検索
LINE
シェア
Tweet
とりあえず雑記帳(跡地)
記事メニュー

メニュー

  • トップページ
  • コメント
  • とりあえずインターフェース入門
  • Yesod
  • Haskell
  • Slim3
  • JavaScript
  • Google App Engine
  • Android
  • Facebook
  • GWT
  • OpenSocial
  • Struts 2
  • Subversion
  • Apache
  • JSONIC
  • Flex

  • WebコミックLibraryについて

公式サイト

  • Yesod
  • Haskell
  • Slim3
  • JavaScript - MDN
  • App Engine for Java
  • Android Developers
  • Google Web Toolkit
  • Struts
  • OpenSocial
  • OSDE

  • ToDo

ここを編集
記事メニュー2
間違いの御指摘は
コメントまでm(_ _)m

更新履歴

取得中です。


ここを編集

総数: -
本日: -
昨日: -
人気記事ランキング
  1. Struts 2
  2. Android
  3. Slim3/文字列の部分一致検索とページング
  4. Apache
もっと見る
最近更新されたページ
  • 4398日前

    Haskell
  • 4398日前

    Yesod
  • 4398日前

    トップページ
  • 4507日前

    メニュー
  • 4509日前

    Struts 2
  • 4534日前

    コメント
  • 4703日前

    Google App Engine
  • 4741日前

    Slim3/環境構築とプロジェクト作成
  • 4765日前

    Google App Engine/キャッシュの計画
  • 4775日前

    Slim3/文字列の部分一致検索とページング
もっと見る
人気記事ランキング
  1. Struts 2
  2. Android
  3. Slim3/文字列の部分一致検索とページング
  4. Apache
もっと見る
最近更新されたページ
  • 4398日前

    Haskell
  • 4398日前

    Yesod
  • 4398日前

    トップページ
  • 4507日前

    メニュー
  • 4509日前

    Struts 2
  • 4534日前

    コメント
  • 4703日前

    Google App Engine
  • 4741日前

    Slim3/環境構築とプロジェクト作成
  • 4765日前

    Google App Engine/キャッシュの計画
  • 4775日前

    Slim3/文字列の部分一致検索とページング
もっと見る
ウィキ募集バナー
新規Wikiランキング

最近作成されたWikiのアクセスランキングです。見るだけでなく加筆してみよう!

  1. MadTown GTA (Beta) まとめウィキ
  2. AviUtl2のWiki
  3. R.E.P.O. 日本語解説Wiki
  4. シュガードール情報まとめウィキ
  5. 機動戦士ガンダム EXTREME VS.2 INFINITEBOOST wiki
  6. ソードランページ @ 非公式wiki
  7. シミュグラ2Wiki(Simulation Of Grand2)GTARP
  8. ドラゴンボール Sparking! ZERO 攻略Wiki
  9. 星飼いの詩@ ウィキ
  10. ヒカマーWiki
もっと見る
人気Wikiランキング

atwikiでよく見られているWikiのランキングです。新しい情報を発見してみよう!

  1. アニヲタWiki(仮)
  2. ストグラ まとめ @ウィキ
  3. ゲームカタログ@Wiki ~名作からクソゲーまで~
  4. 初音ミク Wiki
  5. 検索してはいけない言葉 @ ウィキ
  6. 発車メロディーwiki
  7. 機動戦士ガンダム バトルオペレーション2攻略Wiki 3rd Season
  8. Grand Theft Auto V(グランドセフトオート5)GTA5 & GTAオンライン 情報・攻略wiki
  9. オレカバトル アプリ版 @ ウィキ
  10. 英傑大戦wiki
もっと見る
全体ページランキング

最近アクセスの多かったページランキングです。話題のページを見に行こう!

  1. 参加者一覧 - ストグラ まとめ @ウィキ
  2. モンスター一覧_第2章 - モンスター烈伝オレカバトル2@wiki
  3. 魔獣トゲイラ - バトルロイヤルR+α ファンフィクション(二次創作など)総合wiki
  4. 高崎線 - 発車メロディーwiki
  5. 近藤旬子 - 馬主データベース@Wiki
  6. 地獄のデザイナーさん1 - 【トレパク】 きりつき 検証まとめwiki 【地獄のデザイナーさん】
  7. 召喚 - PATAPON(パタポン) wiki
  8. 細田守 - アニヲタWiki(仮)
  9. ステージ攻略 - パタポン2 ドンチャカ♪@うぃき
  10. 鬼レンチャン(レベル順) - 鬼レンチャンWiki
もっと見る

  • このWikiのTOPへ
  • 全ページ一覧
  • アットウィキTOP
  • 利用規約
  • プライバシーポリシー

2019 AtWiki, Inc.