WildFlyに、Hibernate Searchが同梱されるようになったと聞き、せっかくなので試してみることにしました。
Hibernate Searchが使用する、Luceneのインデックスの保存先はInfinispanとします。また、最終的にはWildFlyにCache Containerを定義して、クラスタリングするところまでを目標に頑張りたいと思います。
それに、Hibernate SearchってこれまでInfinispan越しに使っていましたが、JPAと合わせたことはありませんし。
JPA(もちろん実装はHibernate)とHibernate Searchを統合することで、JPAでの更新時に一緒にLuceneのインデックスを作成してくれるみたいです。この時のインデックスの保存先は、メモリ、ファイルシステム、Infinispanから選ぶことができますが、前述の通り今回はInfinispanに保存します。
では、いってみましょう。
準備
まずは、依存関係の定義。
build.sbt
name := "hibernate-search-with-jpa"
version := "0.0.1-SNAPSHOT"
scalaVersion := "2.10.3"
organization := "org.lilttlewings"
scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked")
seq(webSettings: _*)
artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) =>
"javaee7-web." + artifact.extension
}
{
val jettyVersion = "9.1.2.v20140210"
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-webapp" % jettyVersion % "container",
"org.eclipse.jetty" % "jetty-plus" % jettyVersion% "container",
"javax" % "javaee-web-api" % "7.0" % "provided",
"org.hibernate" % "hibernate-search-orm" % "4.5.0.Final" % "provided",
"org.apache.lucene" % "lucene-kuromoji" % "3.6.2" excludeAll(
ExclusionRule(organization = "org.apache.lucene", name = "lucene-core"),
ExclusionRule(organization = "org.apache.lucene", name = "lucene-analyzers")
)
)
}
Hibernate SearchはWildFlyのモジュールとして登録されているので、providedでOKです。その関係でWildFlyにはLucene 3.6.2が入っているのですが、形態素解析器Kuromojiは入っていないので、別途追加しています。
xsbt-web-pluginの設定。
project/plugins.sbt
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.7.0")
Hibernate Searchモジュールを使用するため、jboss-deployment-structure.xmlファイルを作成します。
src/main/webapp/WEB-INF/jboss-deployment-structure.xml
xml version="1.0" encoding="UTF-8"
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">
<deployment>
<dependencies>
<module name="org.hibernate.search.orm" services="export" />
</dependencies>
</deployment>
</jboss-deployment-structure>
以下で紹介しているサンプルに習って「services="export"」を付けているんですけど、意味はわかってません…。
では、Hibernate SearchをJPAと合わせて使っていきます。コードを書くにあたり、こちらを参考にしています。
TicketMonster Tutorial Adding a full-text search engine
http://www.jboss.org/jdf/examples/ticket-monster/tutorial/HibernateSearch/
JPAの設定ファイルに、Hibernate Searchの設定を追加します。
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="javaee7.web.pu" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:jboss/datasources/mysqlXaDs</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<!-- Hibernate Search -->
<property name="hibernate.search.default.directory_provider" value="infinispan" />
<property name="hibernate.search.lucene_version" value="LUCENE_36" />
</properties>
</persistence-unit>
</persistence>
「hibernate.search.lucene_version」はなくても動きますが、何も書かないと延々と警告されるので。「hibernate.search.default.directory_provider」で、インデックスの保存先(というかLuceneのDirectoryの実装)を設定します。
Entity
ここから書くコードは、先のエントリ
JPAのSecond Level Cacheを試してみる
http://d.hatena.ne.jp/Kazuhira/20140215/1392453183
で書いた内容をベースにしています。ただ、L2キャッシュの部分は取り外しました。
Entityのコード。
src/main/scala/org/littlewings/javaee7/entity/Book.scala
package org.littlewings.javaee7.entity
import scala.beans.BeanProperty
import javax.persistence.{Column, Entity, Id, Table, Version}
import org.apache.lucene.analysis.ja.JapaneseAnalyzer
import org.hibernate.search.annotations.{Analyze, Analyzer, Field, Index, Indexed}
@SerialVersionUID(1L)
@Entity
@Table(name = "book")
@Indexed
class Book extends Serializable {
@Id
@Column
@Field(analyze = Analyze.NO)
@BeanProperty
var isbn: String = _
@Column
@Field
@Analyzer(impl = classOf[JapaneseAnalyzer])
@BeanProperty
var title: String = _
@Column
@Field(analyze = Analyze.NO)
@BeanProperty
var price: Int = _
@Column
@Field
@Analyzer(impl = classOf[JapaneseAnalyzer])
@BeanProperty
var summary: String = _
@Column(name = "version_no")
@BeanProperty
@Version
var versionNo: Int = _
override def toString: String =
s"isbn = $isbn, title = $title, price = $price, summary = $summary, versionNo = $versionNo"
}
Entityのクラス宣言の部分に、@Indexedアノテーションを付与しています。
@Indexed
class Book extends Serializable {
テーマは書籍ですが、タイトルと概要はKuromojiで形態素解析するように設定。
@Column
@Field
@Analyzer(impl = classOf[JapaneseAnalyzer])
@BeanProperty
var title: String = _
インデックスの初期化
サンプルに習い、起動時にインデックスを再作成するクラスを作成しました。ここだけ、EJB…。
src/main/scala/org/littlewings/javaee7/bootstrap/ContextInitializer.scala
package org.littlewings.javaee7.bootstrap
import javax.annotation.PostConstruct
import javax.ejb.{Singleton, Startup}
import javax.persistence.{EntityManager, PersistenceContext}
import org.hibernate.search.jpa.Search
@Singleton
@Startup
class ContextInitializer {
@PersistenceContext
private var em: EntityManager = _
@PostConstruct
def initialize(): Unit = {
val fullTextEm = Search.getFullTextEntityManager(em)
fullTextEm.createIndexer().purgeAllOnStart(true).startAndWait()
}
}
起動時に、EntityManagerからHibernate SearchのFullTextEntityManagerを取得し、インデックスの再作成を行うコードです。
ただ、この部分は後のクラスタ化の際に取り外すことになります。
FullTextEntityManagerを取得する
他のクラスで@InjectしたEntityManagerから毎回FullTextEntityManager取得してもいいのですが、面倒なのでFullTextEntityManagerを取得するこんなクラスを定義。
src/main/scala/org/littlewings/javaee7/cdi/FullTextEntityManagerProducer.scala
package org.littlewings.javaee7.cdi
import javax.enterprise.context.Dependent
import javax.enterprise.inject.Produces
import javax.persistence.{EntityManager, PersistenceContext}
import org.hibernate.search.jpa.{Search, FullTextEntityManager}
@Dependent
class FullTextEntityManagerProducer {
@PersistenceContext
private var em: EntityManager = _
@Produces
def createFullTextEntityManager: FullTextEntityManager =
Search.getFullTextEntityManager(em)
}
これで、FullTextEntityManagerが@Injectできるようになります。
検索を行うクラス
実際に、Hibernate SearchのAPIを使用して、検索を行うクラスを作成します。
src/main/scala/org/littlewings/javaee7/service/BookSearchService.scala
package org.littlewings.javaee7.service
import scala.collection.JavaConverters._
import javax.enterprise.context.RequestScoped
import javax.inject.Inject
import org.apache.lucene.search.Query
import org.hibernate.search.jpa.FullTextEntityManager
import org.littlewings.javaee7.entity.Book
@RequestScoped
class BookSearchService {
@Inject
private var fullTextEm: FullTextEntityManager = _
def indexing(): Unit =
fullTextEm.createIndexer().startAndWait()
def search(title: String, summary: String): (Query, Iterable[Book]) = {
val queryBuilder =
fullTextEm
.getSearchFactory
.buildQueryBuilder
.forEntity(classOf[Book])
.get
val queries =
List(
Option(title).map { t =>
queryBuilder
.keyword
.onField("title")
.matching(t)
.createQuery
},
Option(summary).map { s =>
queryBuilder
.keyword
.onField("summary")
.matching(s)
.createQuery
}
).flatten
val booleanJunction = queryBuilder.bool
val luceneQuery =
queries match {
case titleQuery :: summaryQuery :: Nil =>
booleanJunction.should(titleQuery).should(summaryQuery).createQuery
case query :: Nil =>
booleanJunction.should(query).createQuery
case _ =>
throw new IllegalArgumentException(s"Unknown Query[${queries.toString}")
}
(luceneQuery,
fullTextEm
.createFullTextQuery(luceneQuery, classOf[Book])
.getResultList
.asScala
.asInstanceOf[Iterable[Book]])
}
}
ここは、普通にHibernate SearchのAPIを使っているだけです。まあ、Scalaでいろいろやってますが…。
一応、ここでは検索対象はタイトルと概要ですね。指定された方、または両方をORでクエリに加えます。あと、ここでもインデックス作成の処理を定義してたりします…。
このServiceクラスを使用する、JAX-RSのリソースクラス。
src/main/scala/org/littlewings/javaee7/rest/BookSearchResource.scala
package org.littlewings.javaee7.rest
import scala.collection.JavaConverters._
import javax.inject.Inject
import javax.ws.rs.{DELETE, GET, Path, PathParam, POST, PUT, Produces, QueryParam}
import javax.ws.rs.core.MediaType
import org.littlewings.javaee7.entity.Book
import org.littlewings.javaee7.service.BookSearchService
@Path("book/search")
class BookSearchResource {
@Inject
private var bookSearchService: BookSearchService = _
@GET
@Produces(Array(MediaType.APPLICATION_JSON))
def search(@QueryParam("title") title: String, @QueryParam("summary") summary: String): java.util.Map[String, AnyRef] = {
val result = bookSearchService.search(title, summary)
Map("query" -> result._1.toString,
"hits" -> new Integer(result._2.size),
"books" -> result._2.asJava)
.asJava
}
@POST
@Path("indexing")
@Produces(Array(MediaType.TEXT_PLAIN))
def indexing: String = {
bookSearchService.indexing()
"OK" + System.lineSeparator
}
}
検索リクエストは、どんなクエリを投げたのかもわかるようにしておきます。
その他、通常のデータベースに対するCRUDを行うServiceや、それを利用するJAX-RSのクラスは
JPAのSecond Level Cacheを試してみる
http://d.hatena.ne.jp/Kazuhira/20140215/1392453183
で作成したものをそのまま使用します。
動作確認
ここで作成したWARファイルを、WildFlyにデプロイします。
デプロイすると、起動時にEJBが初期化を行うため、こんな感じでHibernate Searchがデータ取得を行っていることが確認できます。
21:51:59,204 INFO [stdout] (Hibernate Search: identifierloader-1) Hibernate:
21:51:59,205 INFO [stdout] (Hibernate Search: identifierloader-1) select
21:51:59,206 INFO [stdout] (Hibernate Search: identifierloader-1) count(*) as y0_
21:51:59,206 INFO [stdout] (Hibernate Search: identifierloader-1) from
21:51:59,206 INFO [stdout] (Hibernate Search: identifierloader-1) book this_
21:51:59,210 INFO [org.hibernate.search.impl.SimpleIndexingProgressMonitor] (Hibernate Search: identifierloader-1) HSEARCH000027: Going to reindex 6 entities
21:51:59,211 INFO [stdout] (Hibernate Search: identifierloader-1) Hibernate:
21:51:59,212 INFO [stdout] (Hibernate Search: identifierloader-1) select
21:51:59,212 INFO [stdout] (Hibernate Search: identifierloader-1) this_.isbn as y0_
21:51:59,212 INFO [stdout] (Hibernate Search: identifierloader-1) from
21:51:59,213 INFO [stdout] (Hibernate Search: identifierloader-1) book this_
21:51:59,216 INFO [stdout] (Hibernate Search: entityloader-1) Hibernate:
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) select
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) this_.isbn as isbn1_0_0_,
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) this_.price as price2_0_0_,
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) this_.summary as summary3_0_0_,
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) this_.title as title4_0_0_,
21:51:59,217 INFO [stdout] (Hibernate Search: entityloader-1) this_.version_no as version_5_0_0_
21:51:59,218 INFO [stdout] (Hibernate Search: entityloader-1) from
21:51:59,218 INFO [stdout] (Hibernate Search: entityloader-1) book this_
21:51:59,218 INFO [stdout] (Hibernate Search: entityloader-1) where
21:51:59,218 INFO [stdout] (Hibernate Search: entityloader-1) this_.isbn in (
21:51:59,218 INFO [stdout] (Hibernate Search: entityloader-1) ?, ?, ?, ?, ?, ?
21:51:59,219 INFO [stdout] (Hibernate Search: entityloader-1) )
で、インデキシングが終わりましたよと。
21:51:59,558 INFO [org.hibernate.search.impl.SimpleIndexingProgressMonitor] (ServerService Thread Pool -- 76) HSEARCH000028: Reindexed 6 entities
なお、入っているデータは前回同様、このようなJSONから作成したデータです。
book.json
[
{
"isbn": "978-4798124605",
"title": "Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava",
"price": 4410,
"summary": "エンタープライズJava入門書の決定版!Java EE 6は、大規模な情報システム構築に用いられるエンタープライズ環境向けのプログラミング言語です。"
},
{
"isbn": "978-4798120546",
"title": "マスタリングJavaEE5 第2版",
"price": 5670,
"summary": "EJB3.0、JPA、JSF、Webサービスを完全網羅。新たにJBoss AS、Hibernateにも対応!JavaEE5は、J2EEの高い機能性はそのままに、アプリケーションの開発生産性を高めることを主眼とした、サーバサイドJavaにおけるプラットフォーム、開発、デプロイメントに関する標準仕様です。"
},
{
"isbn": "978-4873114675",
"title": "JavaによるRESTfulシステム構築",
"price": 3360,
"summary": "Java EE 6でサポートされたJAX-RSの特徴とRESTfulアーキテクチャ原則を使って、Javaでの分散Webサービスを設計開発する方法を学ぶ書籍。"
},
{
"isbn": "978-4774127804",
"title": "Apache Lucene 入門 Java・オープンソース・全文検索システムの構築",
"price": 3360,
"summary": "Luceneは全文検索システムを構築するためのJavaのライブラリです。Luceneを使えば,一味違う高機能なWebアプリケーションを作ることができます。"
},
{
"isbn": "978-4774161631",
"title": "[改訂新版] Apache Solr入門 オープンソース全文検索エンジン",
"price": 3780,
"summary": "最新版Apaceh Solr Ver.4.5.1に対応するため大幅な書き直しと原稿の追加を行い、現在の開発環境に合わせて完全にアップデートしました。Apache Solrは多様なプログラミング言語に対応した全文検索エンジンです。"
},
{
"isbn": "978-1933988177",
"title": "Lucene in Action",
"price": 5548,
"summary": "When Lucene first hit the scene five years ago, it was nothing short of amazing. By using this open-source, highly scalable, super-fast search engine, developers could integrate search into applications quickly and efficiently."
}
]
クエリに、titleを「java」、summaryを全文検索で検索。
$ curl "http://localhost:8080/javaee7-web/rest/book/search?title=java&summary=%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2"
{"query":"title:java (summary:全文 summary:検索)","hits":4,"books":[{"isbn":"978-4774127804","title":"Apache Lucene 入門 Java・オープンソース・全文検索システムの構築","price":3360,"summary":"Luceneは全文検索システムを構築するためのJavaのライブラリです。Luceneを使えば,一味違う高機能なWebアプリケーションを作ることができます。","versionNo":0},{"isbn":"978-4873114675","title":"JavaによるRESTfulシステム構築","price":3360,"summary":"Java EE 6でサポートされたJAX-RSの特徴とRESTfulアーキテクチャ原則を使って、Javaでの分散Webサービスを設計開発する方法を学ぶ書籍。","versionNo":0},{"isbn":"978-4774161631","title":"[改訂新版] Apache Solr入門 オープンソース全文検索エンジン","price":3780,"summary":"最新版Apaceh Solr Ver.4.5.1に対応するため大幅な書き直しと原稿の追加を行い、現在の開発環境に合わせて完全にアップデートしました。Apache Solrは多様なプログラミング言語に対応した全文検索エンジンです。","versionNo":0},{"isbn":"978-4798124605","title":"Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava","price":4410,"summary":"エンタープライズJava入門書の決定版!Java EE 6は、大規模な情報システム構築に用いられるエンタープライズ環境向けのプログラミング言語です。","versionNo":0}]}
クエリは、こんな感じになりました。
"query":"title:java (summary:全文 summary:検索)"
summaryの方は、ちゃんと形態素解析されてますね。
titleだけを指定して、「lucene」で。
$ curl "http://localhost:8080/javaee7-web/rest/book/search?title=lucene"
{"query":"title:lucene","hits":2,"books":[{"isbn":"978-1933988177","title":"Lucene in Action","price":5548,"summary":"When Lucene first hit the scene five years ago, it was nothing short of amazing. By using this open-source, highly scalable, super-fast search engine, developers could integrate search into applications quickly and efficiently.","versionNo":0},{"isbn":"978-4774127804","title":"Apache Lucene 入門 Java・オープンソース・全文検索システムの構築","price":3360,"summary":"Luceneは全文検索システムを構築するためのJavaのライブラリです。Luceneを使えば,一味違う高機能なWebアプリケーションを作ることができます。","versionNo":0}]}
2件ヒット。
ちゃんと動いてそうですね。
WildFlyにCache Containerを定義してデプロイする
続いて、せっかくInfinispanにインデックスを保存しているのですから、今度はクラスタにしてみます。
なんですけど、先ほどまでのコードでは、Infinispanの設定はまったく行っていませんでした。Hibernate Searchが自身で持つデフォルトのInfinispanの設定で、デプロイの度に毎回新しくInfinispanを起動していたからです。
それはそれでもいいのですが、デプロイ/アンデプロイを繰り返すとJMX上で増えてみたりしますし、設定もしてみたいので以下のファイルに専用のCache Containerを定義することにしました。
wildfly-8.0.0.Final/standalone/configuration/standalone-ha.xml
今回は、Infinispanサブシステムに以下のような設定を追加します。
<cache-container name="hibernate-search" default-cache="lucene-index" jndi-name="java:jboss/infinispan/container/hibernateSearch" start="EAGER">
<transport lock-timeout="60000"/>
<replicated-cache name="lucene-indexes-locking" mode="SYNC">
<transaction mode="NONE"/>
<eviction strategy="NONE"/>
</replicated-cache>
<replicated-cache name="lucene-indexes-metadata" mode="SYNC">
<transaction mode="NON_XA"/>
<eviction strategy="NONE"/>
</replicated-cache>
<distributed-cache name="lucene-indexes-data" mode="SYNC">
<transaction mode="NON_XA"/>
<eviction strategy="NONE"/>
</distributed-cache>
</cache-container>
JNDI名を与え、「start="EAGER"」とするところが、たぶんポイントかと…。
設定は、ここを見ながら。
https://docs.jboss.org/author/display/WFLY8/Infinispan+Subsystem
そのうち、ここも見るはず…。
https://docs.jboss.org/author/display/WFLY8/JGroups+Subsystem
ところで、transaction modeの書き方が通常のInfinispanの設定からは見慣れないものになっていますが、こういうことらしいです…。
NONE - no transactions are used in any sense
NON_XA - it maps to the following settings in Infinispan: transaction.useSynchronization == true, transaction.recovery.enabled == false
NON_DURABLE_XA - transaction.useSynchronization == false, transaction.recovery.enabled == false, transaction.syncCommitPhase == true, transaction.syncRollbackPhase == true
FULL_XA - transaction.syncCommitPhase == true, transaction.syncRollbackPhase == true, transaction.useSynchronization == false, transaction.recovery.enabled == true
https://community.jboss.org/thread/201248?tstart=0
そして、このCache Containerを使用するために、persistence.xmlを以下の様に変更します。
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="javaee7.web.pu" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:jboss/datasources/mysqlXaDs</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<!-- Hibernate Search -->
<property name="hibernate.search.default.directory_provider" value="infinispan" />
<property name="hibernate.search.lucene_version" value="LUCENE_36" />
<!-- Infinispan Hibernate Search Integration -->
<property name="hibernate.search.infinispan.cachemanager_jndiname" value="java:jboss/infinispan/container/hibernateSearch" />
<property name="hibernate.search.default.locking_cachename" value="lucene-indexes-locking" />
<property name="hibernate.search.default.data_cachename" value="lucene-indexes-data" />
<property name="hibernate.search.default.metadata_cachename" value="lucene-indexes-metadata" />
<property name="hibernate.search.default.chunk_size" value="16384" />
</properties>
</persistence-unit>
</persistence>
JNDI名の設定と、各キャッシュの名前を明示的に設定しました。
<property name="hibernate.search.infinispan.cachemanager_jndiname" value="java:jboss/infinispan/container/hibernateSearch" />
<property name="hibernate.search.default.locking_cachename" value="lucene-indexes-locking" />
<property name="hibernate.search.default.data_cachename" value="lucene-indexes-data" />
<property name="hibernate.search.default.metadata_cachename" value="lucene-indexes-metadata" />
<property name="hibernate.search.default.chunk_size" value="16384" />
キャッシュの名前は、指定しなければデフォルトになるのでそれでもいいですが、Cache Containerでデフォルトの名前で設定することになりますが…。
参考)
3.3.1. Infinispan Directory configuration
http://docs.jboss.org/hibernate/search/4.5/reference/en-US/html_single/#infinispan-directories
あとは、デプロイすれば、このCache Containerを使うように動作してくれます。
WildFlyを起動させる時は、standalone-ha.xmlを使用するので、以下のようになります。
$ wildfly-8.0.0.Final/bin/standalone.sh -c standalone-ha.xml
最後、Infinispanのクラスタリングへ。
クラスタ化する前に、以下のコードに変更を加えます。
@Singleton
@Startup
class ContextInitializer {
@PersistenceContext
private var em: EntityManager = _
@PostConstruct
def initialize(): Unit = {
}
}
起動時に、インデックスの初期化をしないようにします。これは、LuceneのIndexWriterを複数持てないためで、後から起動したノードがIndexWriterのロックを取れずに
20:46:28,248 ERROR [org.hibernate.search.exception.impl.LogErrorHandler] (Hibernate Search: Index updates queue processor for index org.littlewings.javaee7.entity.Book-1) HSEARCH000058: Exception occurred org.apache.lucene.store.LockObtainFailedException: Lock obtain timed out: org.infinispan.lucene.locking.BaseLuceneLock@d0fd340
Primary Failure:
Entity org.littlewings.javaee7.entity.Book Id null Work Type org.hibernate.search.backend.PurgeAllLuceneWork
とか
20:46:28,253 ERROR [org.hibernate.search.backend.impl.lucene.LuceneBackendQueueTask] (Hibernate Search: Index updates queue processor for index org.littlewings.javaee7.entity.Book-1) HSEARCH000072: Couldn't open the IndexWriter because of previous error: operation skipped, index ouf of sync!
20:46:29,255 ERROR [org.hibernate.search.exception.impl.LogErrorHandler] (ServerService Thread Pool -- 56) HSEARCH000058: HSEARCH000117: IOException on the IndexWriter: org.apache.lucene.store.LockObtainFailedException: Lock obtain timed out: org.infinispan.lucene.locking.BaseLuceneLock@d0fd340
みたいなエラーを見ることになります。
で、それを修正したらWARにして、とりあえずWildFlyを停止してWARファイルを消去。
$ rm wildfly-8.0.0.Final/standalone/deployments/javaee7-web.war*
Cache Containerの設定を含み、同じ構成のWildFlyのコピーを作成します。
$ cp -Rp wildfly-8.0.0.Final wildfly-8.0.0.Final-2
それぞれを、以下の様に起動します。
$ wildfly-8.0.0.Final/bin/standalone.sh -c standalone-ha.xml -Djboss.node.name=node1
$ wildfly-8.0.0.Final-2/bin/standalone.sh -c standalone-ha.xml -Djboss.socket.binding.port-offset=1000 -Djboss.node.name=node2
2つ目のノードは、ポートを1,000ずらしています。
また、ノード名を変えているのは、何もしないとクラスタ上でのノード名が衝突するからです。
起動したら、それぞれにWARファイルをデプロイして、どちらかのノードでインデックスを初期化します。
$ curl -X POST http://localhost:8080/javaee7-web/rest/book/search/indexing
OK
これができると、クラスタになっているので、もう片方のノードからも全文検索の実施が可能になります。
$ curl "http://localhost:9080/javaee7-web/rest/book/search?title=java&summary=%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2"
{"query":"title:java (summary:全文 summary:検索)","hits":4,"books":[{"isbn":"978-4774127804","title":"Apache Lucene 入門 Java・オープンソース・全文検索システムの構築","price":3360,"summary":"Luceneは全文検索システムを構築するためのJavaのライブラリです。Luceneを使えば,一味違う高機能なWebアプリケーションを作ることができます。","versionNo":0},{"isbn":"978-4873114675","title":"JavaによるRESTfulシステム構築","price":3360,"summary":"Java EE 6でサポートされたJAX-RSの特徴とRESTfulアーキテクチャ原則を使って、Javaでの分散Webサービスを設計開発する方法を学ぶ書籍。","versionNo":0},{"isbn":"978-4774161631","title":"[改訂新版] Apache Solr入門 オープンソース全文検索エンジン","price":3780,"summary":"最新版Apaceh Solr Ver.4.5.1に対応するため大幅な書き直しと原稿の追加を行い、現在の開発環境に合わせて完全にアップデートしました。Apache Solrは多様なプログラミング言語に対応した全文検索エンジンです。","versionNo":0},{"isbn":"978-4798124605","title":"Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava","price":4410,"summary":"エンタープライズJava入門書の決定版!Java EE 6は、大規模な情報システム構築に用いられるエンタープライズ環境向けのプログラミング言語です。","versionNo":0}]}
だいぶ長くなりましたが、こんなところで。
今回作成したソースコードは、こちらにアップしています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/hibernate-search-with-jpa