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) => //artifact.name + "." + artifact.extension "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の設定
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
https://community.jboss.org/thread/201248?tstart=0
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
そして、この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名の設定と、各キャッシュの名前を明示的に設定しました。
<!-- 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" />
キャッシュの名前は、指定しなければデフォルトになるのでそれでもいいですが、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 = { /* インデックスの保存先をInfinispanにして、 * かつクラスタにする場合はこのコードは外す val fullTextEm = Search.getFullTextEntityManager(em) fullTextEm.createIndexer().purgeAllOnStart(true).startAndWait() */ } }
起動時に、インデックスの初期化をしないようにします。これは、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
それぞれを、以下の様に起動します。
# Node1 $ wildfly-8.0.0.Final/bin/standalone.sh -c standalone-ha.xml -Djboss.node.name=node1 # Node2 $ 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