Melakukan Query Full-Text Search Dengan Hibernate Search


Pada artikel Memakai Full-Text Search Di MySQL Server, saya menunjukkan bagaimana menerapkan Full-Text Search (FTS) dengan menggunakan MySQL Server. Kali ini, saya akan mencoba alternatif lain dimana FTS dilakukan dari sisi aplikasi dengan menggunakan Hibernate Search. Dengan demikian, FTS tetap dapat dilakukan pada database apapun yang didukung oleh Java. Tidak perlu lagi terikat pada syntax MATCH(...) AGAINST(...) yang hanya berlaku di MySQL Server.

Saya tidak akan ragu menggunakan Hibernate Search pada aplikasi web. Akan tetapi pada pada aplikasi desktop yang tidak memiliki server aplikasi, ia bisa menimbulkan kerumitan baru. Agar index yang dibuat Hibernate Search dapat diperbaharui dan dipakai bersama oleh banyak klien (disebut juga slave), maka dibutuhkan sebuah program yang menjalankan Hibernate Search sebagai master. Hibernate Search mendukung kasus seperti ini dengan memanfaatkan JMS atau JGroups. Tentu saja ini akan lebih repot bila dibandingkan dengan memakai fitur yang sudah ada di server database.

Sebagai latihan, saya akan memakai Hibernate Search pada sebuah aplikasi Griffon. Saya akan mulai dengan menambahkan dependency di BuildConfig.groovy seperti berikut ini:

compile('org.hibernate:hibernate-search-orm:5.0.0.Final')

Hibernate Search terintegrasi sangat baik dengan Hibernate JPA. Hal ini bisa terlihat dari pengaturan Hibernate Search yang dilakukan pada persistence.xml milik JPA. Sebagai contoh, saya menambahkan baris konfigurasi berikut ini:

<?xml...>
  <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <class>domain.inventory.Produk</class>
    ...
    <properties>
      ...
      <property name="hibernate.search.default.directory_provider" value="filesystem"/>
      <property name="hibernate.search.default.indexBase" value="C:/indexes"/>
    </properties>
  </persistence-unit>
</persistence>

Hibernate Search akan memakai Apache Lucene untuk melakukan indexing. Pada konfigurasi di atas, saya akan memakai DirectoryProvider berupa FSDirectoryProvider yang akan menyimpan index Lucene ke dalam folder yang telah ditentukan oleh hibernate.search.default.indexBase.

Berikutnya, saya akan memberikan annotation Hibernate Search pada entity JPA yang sudah ada. Saya perlu menambahkan @Indexed bila ingin entity JPA diproses. Selain itu, saya perlu menambahkan @Field pada property yang hendak di-index. Sebagai contoh, entity JPA saya akan terlihat seperti berikut ini:

@DomainClass @Entity
@Indexed
class Produk {

    ...

    @NotBlank @Size(min=3, max=150)
    @Field
    String nama

    ...

}

Sekarang, setiap kali entity JPA ditambah, dimodifikasi atau di-update, maka index akan diperbaharui. Saya akan menemukan informasi index disimpan di folder yang saya tentukan pada hibernate.search.default.indexBase. Isi file dalam index sesuai dengan format yang dipakai oleh Apache Lucene.

Lalu, bagaimana bila saya memiliki data lama yang belum ada index-nya? Saya dapat menghasilkan index untuk data yang sudah ada dengan menggunakan kode program seperti berikut ini:

Search.getFullTextEntityManager(getEntityManager()).createIndexer().startAndWait()

Untuk melakukan query, saya bisa memakai cara yang JPA-friendly seperti pada contoh berikut ini:

import simplejpa.SimpleJpaUtil
import org.hibernate.search.jpa.FullTextEntityManager
import org.hibernate.search.jpa.Search
import org.hibernate.search.query.dsl.QueryBuilder
import javax.persistence.Query
import domain.Produk

def produkRepository = SimpleJpaUtil.instance.repositoryManager.findRepository('Produk')
produkRepository.withTransaction {
   FullTextEntityManager fem = Search.getFullTextEntityManager(entityManager)
   QueryBuilder b = fem.getSearchFactory().buildQueryBuilder().forEntity(Produk).get()
   def luceneQuery = b.keyword().onField("nama").matching("digital").createQuery();
   def hasil = fem.createFullTextQuery(luceneQuery).getResultList()   
   println hasil
}

Kode program di atas akan mencetak semua produk yang namanya mengandung kata 'digital'. Method fem.createFullTextQuery() akan menghasilkan sebuah Query JPA biasa yang memiliki method getResultList() untuk menghasilkan List berisi object Produk yang ditemukan.

org.hibernate.search.query.dsl.QueryBuilder adalah builder bawaan Hibernate Search untuk mempermudah programmer sehingga tidak perlu membuat query Lucene secara langsung. Builder tersebut menyediakan fluent API untuk menghasilkan sebuah org.apache.lucene.search.Query pada saat createQuery() dipanggil.

Untuk menunjukkan kemampuan FTS pada Hibernate Search, saya bisa menambahkan fuzzy() pada QueryBuilder seperti berikut ini:

def luceneQuery = b
  .keyword()
  .fuzzy()
  .onField("nama")
  .matching("telephone")
  .createQuery();

Query di atas akan mencari nama produk yang mengandung nama 'telephone' dan yang mendekatinya! Pada produk dengan nama Indonesia, kata yang paling sering digunakan adalah 'telepon'. Bila terdapat .fuzzy(), maka produk yang namanya mengandung 'telepon' juga akan ikut disertakan pada hasil pencarian.

Bila saya melakukan pencarian untuk lebih dari 1 kata seperti pada:

def luceneQuery = b
  .keyword()
  .onField("nama")
  .matching("kabel telepon")
  .createQuery();

maka object Produk yang memiliki nama 'kabel telepon' akan ditampilkan pada baris paling awal. Produk lain yang hanya mengandung kata 'kabel' atau 'telepon' tetap akan dikembalikan, tetapi pada urutan yang lebih rendah.

Walaupun membantu dalam melakukan FTS, saya menemukan kendala dimana versi Hibernate Search yang saya pakai tidak mendukung named entity graph yang diperkenalkan oleh JPA 2.1. Tanpa named entity graph, saya harus menentukan apa saja relasi yang perlu di-fetch secara manual setiap kali melakukan pencarian.

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: