[playframework]CJKAnalyzerで日本語の全文検索

[playframework]CJKAnalyzerで日本語の全文検索
-- 環境
-- Play framework 1.2.4
-- Search-2.0
-- 前回記事([playframework]全文検索モジュールsearchをお試し)の続き

■application.conf アナライザの指定
play.search.analyser=org.apache.lucene.analysis.cjk.CJKAnalyzer
※lucene-analyzers-3.0.3.jarに入ってる

■索引を作るクラスRes 変更なし
■Resクラスがぶら下がるThreadクラス 変更なし
■テストコード
import org.apache.lucene.queryParser.ParseException;
import org.junit.*;
import java.util.*;

import play.modules.search.Query;
import play.modules.search.Search;
import play.test.*;
import models.*;
import models.Thread;

/**
 * 全文検索のテスト
 */
public class SearchJpTest extends UnitTest {

@Test
public void search() {
  // Create a new user and save it
  // スレッド作成
  Thread thread = new Thread(
      "おはようございます!", // スレッドタイトル
      "ダンディ", // 名前
      "Dandy@email.com", // メールアドレス
      "わたしの名前はダンディです。"); // 内容★検索対象
  // 書き込み
  thread.addResponse(
          "HOGE", "Hoge@email.com", "わたしの名前はHOGEです。");
  thread.addResponse(
          "moge", "Moge@email.com", "わたしの名前はmogeです。");
  thread.save();
  
  Res res;
  Query query;
  List ids;
  List list;

  // 複数条件はAND/ORで結ぶ
  query = Search.search(
          "desc:名前 AND desc:ダンディ", Res.class);
  // idのリスト
  ids = query.fetchIds();
  // オブジェクトのリスト
  list= query.fetch();
  // カウント
  assertEquals(1, query.count());
  // メールアドレスチェック
  assertEquals("Dandy@email.com", list.get(0).email);

  // 前方一致検索
  query = Search.search(
          "desc:名前 AND desc:ダ*", Res.class);
  ids = query.fetchIds();
  res = Res.findById(ids.get(0));
  assertEquals("Dandy@email.com", res.email);

  // ★前方一致検索では、全角アルファベットでも半角アルファベットで検索しなければいけない
  // 全角アルファベットも半角でインデックスしている
  // 前方一致でなければクエリも半角に統一して検索してくれる
  // 前方一致の場合は統一してくれない
  // こうする事情があるのか、新しいバージョンでは違っているのか要調査
  query = Search.search("desc:名前 AND desc:H*", Res.class);
  ids = query.fetchIds();
  res = Res.findById(ids.get(0));
  assertEquals("Hoge@email.com", res.email);
  // 半角なら大文字小文字どちらでもよい
  query = Search.search("desc:名前 AND desc:h*", Res.class);
  ids = query.fetchIds();
  res = Res.findById(ids.get(0));
  assertEquals("Hoge@email.com", res.email);

  // 全角アルファベットで検索すると0件
  query = Search.search("desc:名前 AND desc:H*", Res.class);
  assertEquals(0, query.count());

  // 後方一致は例外
  // 後方一致検索はReverseStringAnalyzerを使えば可能だが、
  // searchモジュールで複数のアナライザを使えるか?
  // 参考:
  //   ReverseStringFilter(2.9) | 関口宏司のLuceneブログ
  //   (http://lucene.jugem.jp/?eid=288)
  try {
      query = Search.search(
              "desc:名前 AND desc:*E", Res.class);
  } catch (Exception e) {
      assertEquals(ParseException.class, e.getClass());
  }

  // 条件をまとめる時は()で括る
  query = Search.search(
          "desc:名前 AND (desc:ダンディ OR desc:moge)",
          Res.class);
  list = query.fetch();
  assertEquals(2, query.count());

  // 前方一致検索でなければ、全角半角大文字小文字どれでもヒットする
  query = Search.search(
          "desc:名前 AND (desc:MOGE AND desc:moge AND desc:moge AND desc:MOGE AND desc:Moge)",
          Res.class);
  list = query.fetch();
  assertEquals(1, query.count());

}

}

[playframework]全文検索モジュールsearchをお試し

-- 環境
-- Play framework 1.2.4
-- Search-2.0

■searchモジュールインストール
>play install search
※2012/3/21
 search-2.0はplay1.1互換だけどいい?と聞いてくるのでy+enter

■searchモジュールの有効化
/conf/application.confに以下の行を追加
module.search=${play.path}/modules/search-2.0

■eclipse用に設定ファイル更新
>play ec

■索引を作るクラス、カラムに@Indexed @Fieldを追加
package models;

import javax.persistence.Entity;
import javax.persistence.FieldResult;
import javax.persistence.ManyToOne;

import play.db.jpa.Model;
import play.modules.search.Field;
import play.modules.search.Indexed;

@Indexed // ★search 索引を作るクラスに@Indexedを追加
@Entity
public class Res extends Model {

	public String name;
	public String email;
	
	@Field // ★search 索引を作るカラムに@Fieldを追加
	public String desc;

	@ManyToOne
	public Thread thread;
	
	public Res(Thread thread, String name, String email, String desc) {
		this.thread = thread;
		this.name = name;
		this.email = email;
		this.desc = desc;
	}

}

■Resクラスがぶら下がるThreadクラス
※Searchモジュールの為に手を入れる所はありません。

package models;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;

import play.db.jpa.Model;

@Entity
public class Thread extends Model {

	public String title;
	
	@OneToMany(mappedBy="thread", cascade=CascadeType.ALL)
	public List ress;

	public Thread(String title) {
		this.title = title;
	}

	public Thread(String title, String name, String email, String desc) {
		this.title = title;
		ress = new ArrayList();
		addResponse(name, email, desc);
	}
	
	public Thread addResponse(Res res) {
		ress.add(res);
		return this;
	}

	public Thread addResponse(String name, String email, String desc) {
		Res res = new Res(this, name, email, desc);
		addResponse(res);
		return this;
	}
}

■テストコード
import org.apache.lucene.queryParser.ParseException;
import org.junit.*;
import java.util.*;

import play.modules.search.Query;
import play.modules.search.Search;
import play.test.*;
import models.*;
import models.Thread;

/**
 * 全文検索のテスト
 */
public class SearchTest extends UnitTest {

    @Test
    public void search() {
        // Create a new user and save it
    	// スレッド作成
    	Thread thread = new Thread(
    			"Good morning!!", // スレッドタイトル
    			"Dandy", // 名前
    			"Dandy@email.com", // メールアドレス
    			"Hi my name is Dandy."); // 内容★検索対象
    	// 書き込み
    	thread.addResponse(
    			"Hoge", "Hoge@email.com", "Hi my name is Hoge!");
    	thread.addResponse(
    			"Moge", "Moge@email.com", "Hi my name is Moge!");
    	thread.save();
    	
    	Res res;
    	Query query;
    	List ids;
    	List list;

    	// 複数条件はAND/ORで結ぶ
    	query = Search.search(
    			"desc:name AND desc:Dandy", Res.class);
    	// idのリスト
    	ids = query.fetchIds();
    	// オブジェクトのリスト
    	list= query.fetch();
    	// カウント
        assertEquals(1, query.count());
        // メールアドレスチェック
        assertEquals("Dandy@email.com", list.get(0).email);

        // 前方一致検索
    	query = Search.search(
    			"desc:name AND desc:Hog*", Res.class);
    	ids = query.fetchIds();
    	res = Res.findById(ids.get(0));
        assertEquals("Hoge@email.com", res.email);

        // 後方一致は例外
        // 後方一致検索はReverseStringAnalyzerを使えば可能だが、
        // searchモジュールで複数のアナライザを使えるか?
        // 参考:
        //   ReverseStringFilter(2.9) | 関口宏司のLuceneブログ
        //   (http://lucene.jugem.jp/?eid=288)
        try {
        	query = Search.search(
        			"desc:name AND desc:*oge", Res.class);
        } catch (Exception e) {
        	assertEquals(ParseException.class, e.getClass());
		}

        // 条件をまとめる時は()で括る
        query = Search.search(
        		"desc:name AND (desc:Dandy OR desc:Moge)",
        		Res.class);
    	list = query.fetch();
        assertEquals(2, query.count());
    }

}

■おまけ
■application.conf インデックスの格納ディレクトリ指定
play.search.path=/tmp/myDevApplication
デフォルト(無指定)は${application.path}/data/search

■application.conf アナライザの指定
play.search.analyser=org.apache.lucene.analysis.standard.StandardAnalyzer

searchモジュールにluceneのjarも同時にインストールされている。
ここの、lucene-analyzers-3.0.3.jarとlucene-core-3.0.3.jarに
アナライザが色々入っているので好きなのを使うと楽。

CJKAnalyzerなら「org.apache.lucene.analysis.cjk.CJKAnalyzer」を指定する

[play framework]messagesファイルを別のディレクトリに配置する

messagesファイルだけftpで更新するために、playの外のディレクトリに置いてリンクを張る
-- 環境
-- Ubuntu 11.10
-- Play framework 1.2.4

■messagesファイルを移動
$ mkdir /home/usr/play/ftp/conf -p
$ mv /home/usr/play/yabe/conf/messages /home/usr/play/ftp/conf/

■シンボリックリンク作成
$ cd /home/usr/play/yabe/conf/
$ ln -s /home/usr/play/ftp/conf/messages

これで、/home/usr/play/ftp/conf/messagesファイルをftpで更新すると、ブラウザから見た時も更新されるようになる

■publicフォルダもシンボリックリンクにする
■publicフォルダを移動
mv /home/usr/play/yabe/public /home/usr/play/ftp/

■シンボリックリンク作成
$ cd /home/usr/play/yabe/
$ ln -s /home/user/play/ftp/public public

これで、/home/user/play/ftp/publicディレクトリをftpで更新すると
画像ファイルだけを差し替えたりできる。
※publicディレクトリまるごとよりは、中の一部のディレクトリをftp更新するようにした方がいいかもしれない。

■windowsの場合は以下を利用するといいかもしれない
窓の杜 - 【REVIEW】エクスプローラ上で手軽にシンボリックリンクを作成「Link Shell Extension」
http://www.forest.impress.co.jp/article/2008/12/11/linkshellext.html

タグクラウド
QRコード
QRコード
  • ライブドアブログ