JPA(Hibernate)を使って全文検索をやってみる。
Hibernate-Searchで永続化したデータを全文検索する。
build.gradle
そのままだとMETA-INF/persistence.xmlが読み込まれないのでresourcesの出力先をbuild/classes/mainにしておく。
apply plugin: 'idea'
apply plugin: 'groovy'
apply plugin: 'application'
def defaultEncoding = 'UTF-8'
def jdkVersion = '1.7'
sourceCompatibility = jdkVersion
targetCompatibility = jdkVersion
mainClassName = 'JpaLucene'
repositories {
mavenCentral()
}
sourceSets {
main {
output.resourcesDir = 'build/classes/main'
}
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.1.7'
compile 'org.hibernate:hibernate-entitymanager:4.2.6.Final'
compile 'org.hibernate:hibernate-search:4.4.0.Final'
compile 'org.slf4j:slf4j-simple:1.7.5'
compile 'javassist:javassist:3.12.1.GA'
compile 'com.h2database:h2:1.3.173'
compile 'com.google.inject:guice:3.0'
compile 'com.google.inject.extensions:guice-persist:3.0'
}
compileGroovy {
groovyOptions.encoding = defaultEncoding
}
idea {
project {
jdkName = jdkVersion
languageLevel = jdkVersion
}
}
persistence.xml
src/main/resourcesにMETA-INF/persistence.xmlを作る。
hibernate.search.default.indexBaseはHibernate-Search用のプロパティで全文検索のインデックスの保存先を指定する。
ここで指定しなかった場合はプロジェクトの直下にインデックスが作成される。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="example" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:example" />
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.search.default.indexBase" value="indexes" />
</properties>
</persistence-unit>
</persistence>
コードを書く
トランザクションはGuice-Persistで管理する。EntityManagerじゃなくてSessionを使う場合はWarp-Persistを使う。あとSessionでやるとHibernate-ConfigurationでXMLレスにできる。
処理の最後にデータを全削除しているのはインメモリデータベースとインデックスに差異が出ないようにするため。処理開始時にインデックスの再作成をしてもいいだろうけどなんかかっこ悪い。
PersistService#startを実行したらきちんとPersistService#stopを呼び出してあげないとHibernate-Searchで作成されたインデックスが解放されずにプログラムが終了しないので注意。
import com.google.inject.*
import com.google.inject.persist.*
import com.google.inject.persist.jpa.JpaPersistModule
import org.hibernate.search.annotations.*
import org.hibernate.search.jpa.*
import javax.inject.Inject
import javax.persistence.*
class JpaLucene {
@Inject BookDao bookDao
@Inject AuthorDao authorDao
@Inject BookSearcher bookSearcher
def storeAndSearch() {
// store some books
def names1 = ['Dierk', 'Guillaume', 'Jon', 'Andy', 'Paul']
def names2 = ['Craig', 'Groovy']
def authors1 = authorDao.storeAuthors(names1)
def authors2 = authorDao.storeAuthors(names2)
bookDao.store(new Book(authors:authors1, title:'Groovy in Action'))
bookDao.store(new Book(authors:authors2, title:'Spring in Action'))
// find some books
bookSearcher.search('Groovy')
// delete all books
bookDao.deleteAll()
authorDao.deleteAll() // BookDao#deleteAllで全部削除されるので呼ばなくてもいい
}
def static main(args) {
def injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new JpaPersistModule('example'))
bind(JpaLuceneInitializer).asEagerSingleton()
}
})
def initializer = injector.getInstance(JpaLuceneInitializer)
def jpaLucene = injector.getInstance(JpaLucene)
jpaLucene.storeAndSearch()
initializer.stop()
}
}
@Entity
@Indexed
class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id
@OneToMany(cascade=CascadeType.ALL)
@IndexedEmbedded
public Set<Author> authors
@Field
public String title
String toString() { "$title by ${authors.name.join(', ')}" }
}
@Entity
@Indexed
class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id
@Field
public String name
}
class BookDao {
@Inject
EntityManager em
@Transactional
def store(book) {
em.persist(book)
}
@Transactional
def deleteAll() {
em.createQuery('from Book', Book).resultList.each {
em.remove(it)
}
}
}
class AuthorDao {
@Inject
EntityManager em
@Transactional
def storeAuthors(authors) {
Set result = []
authors.each{ def a = new Author(name:it); em.persist(a); result << a }
result
}
@Transactional
def deleteAll() {
em.createQuery('from Author', Author).resultList.each {
em.remove(it)
}
}
}
class BookSearcher {
FullTextEntityManager fullTextEntityManager
@Inject
BookSearcher(EntityManager em) {
fullTextEntityManager = Search.getFullTextEntityManager(em)
}
def search(value) {
def qb = fullTextEntityManager.searchFactory.buildQueryBuilder().forEntity(Book).get()
def query = qb.keyword().onFields('title', 'authors.name').matching(value).createQuery()
def books = fullTextEntityManager.createFullTextQuery(query, Book).resultList
println 'Found ' + books.size() + ' books:'
books.each { println it }
}
}
class JpaLuceneInitializer {
PersistService service
@Inject
JpaLuceneInitializer(PersistService service) {
this.service = service
this.service.start()
}
def stop() {
service.stop()
}
}
実行結果
タイトルと著者に「Groovy」を含むデータ(用意したデータ2件とも)が検索されている。
10 25, 2013 11:53:16 午後 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
10 25, 2013 11:53:16 午後 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.2.6.Final}
10 25, 2013 11:53:16 午後 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
10 25, 2013 11:53:16 午後 org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
10 25, 2013 11:53:17 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
10 25, 2013 11:53:17 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
10 25, 2013 11:53:17 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
10 25, 2013 11:53:17 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [org.h2.Driver] at URL [jdbc:h2:mem:example]
10 25, 2013 11:53:17 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=sa, autocommit=true, release_mode=auto}
10 25, 2013 11:53:17 午後 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
10 25, 2013 11:53:18 午後 org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
10 25, 2013 11:53:18 午後 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
10 25, 2013 11:53:18 午後 org.hibernate.search.Version <clinit>
INFO: HSEARCH000034: Hibernate Search 4.4.0.Final
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book_Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book_Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Book_Author
10 25, 2013 11:53:23 午後 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
10 25, 2013 11:53:23 午後 org.hibernate.search.impl.ConfigContext getLuceneMatchVersion
WARN: HSEARCH000075: Configuration setting hibernate.search.lucene_version was not specified, using LUCENE_CURRENT.
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Author (id, name) values (null, ?)
Hibernate: insert into Book (id, title) values (null, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book (id, title) values (null, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: insert into Book_Author (Book_id, authors_id) values (?, ?)
Hibernate: select this_.id as id1_1_0_, this_.title as title2_1_0_ from Book this_ where (this_.id in (?, ?))
Found 2 books:
Groovy in Action by Andy, Paul, Jon, Dierk, Guillaume
Spring in Action by Craig, Groovy
Hibernate: select book0_.id as id1_1_, book0_.title as title2_1_ from Book book0_
Hibernate: delete from Book_Author where Book_id=?
Hibernate: delete from Book_Author where Book_id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Book where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Author where id=?
Hibernate: delete from Book where id=?
Hibernate: select author0_.id as id1_0_, author0_.name as name2_0_ from Author author0_
10 25, 2013 11:53:26 午後 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:h2:mem:example]