Thursday, June 18, 2015

FullText Search Query : menggunakan Hibernate-Search


Jumpa lagi kita dengan team nostratech. Kali ini kita akan belajar tentang seraching data di database menggunakan Full Text Search. Apa itu full text search?.

Seperti saat kita search di google. Kita ketik keyword apa, maka akan muncul list data yang sesuai (match atau sama persis dengan keyword yang kita ketik) dan juga semua data ada hubungannya dengan keyword yang kita ketik. Misal kita ketik 'hibernate book', maka akan muncul semua list yang ada kata mirip denhan 'hibernate' dan 'book'.



Seperti itulah sederhananya pengertian full text search.

Kita ambil contoh yang sederhana saja.
Misalnya kita punya data list nama contact di tabel seperti ini :


Disini kita asumsikan kita menggunakan ORM hibernate.

Bagaimana caranya search data “Paolo Coelho Jr” ? Kalo menggunakan keyword 'paolo' tentunya kita memakai query :

 select a from Contact where a.name like 'paolo%'  

jika keyword dari usernya 'coelho' :

 select a from Contact where a.name like '%coelho%'  

Nah bagaimana jika keyword dari usernya seperti ini : 'coelho Jr', 'coelho jr paolo', 'famous writer paolo coelho', 'the name of Coelho NM paolo ' dan lain lain.
Tentu kita akan bingung dan akan kesulitan tentunya kita.
Search query classic : name like '%keyword%' tentu tidak bisa menghandle lagi jika sudah menggunakan query yang aneh aneh seperti diatas.
Selain tidak mendapatkan hasil yang diharapkan, tentunya query %% tentu sangat buruk untuk masalah performence kalau datanya sudah terlampau banyak (walaupun field name di tabel Contact sudah di Index).

Solusinya kita bisa menggunakan library hibernate-search untuk menangani search query untuk problem diatas.
Hibernate search didalamnya menggunakan engine apache lucene untuk indexing datanya. Jadi nantinya data Name dari tabel Contact akan disimpan di index lucene.
Hibernate tidak akan langsung mencari data di tabel contact tetapi di cari terlebih dulu di index luncene.

Proses yang terjadi kurang lebih seperti ini :

Hibernate --> search index lucene --> search index name dan get id nya --> search ke tabel contact berdasarkan id --> tampilkan data.

Tambahkan dependency maven di pom.xml

 <dependency>  
   <groupId>org.hibernate</groupId>  
   <artifactId>hibernate-core</artifactId>  
   <version>4.3.9.Final</version>  
 </dependency>  
 <dependency>  
   <groupId>org.hibernate</groupId>  
   <artifactId>hibernate-search-orm</artifactId>  
   <version>5.2.0.Final</version>  
 </dependency>  

Mari kita mulai ke contohnya.

Kita buat dulu model Contact.

Set property mana yang akan diindex menggunakan @Field

 import javax.persistence.Entity;  
 import javax.persistence.Id;  
 import javax.persistence.Table;  
 import org.apache.lucene.analysis.core.LowerCaseFilterFactory;  
 import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;  
 import org.apache.lucene.analysis.standard.StandardTokenizerFactory;  
 import org.hibernate.search.annotations.*;  
 /**  
  * The persistent class for the contact database table.  
  */  
 @Entity  
 @Indexed  
 @AnalyzerDef(name = "customanalyzer",  
     tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
     filters = {  
         @TokenFilterDef(factory = LowerCaseFilterFactory.class)  
         , @TokenFilterDef(factory = SnowballPorterFilterFactory.class  
     )  
     })  
 @Table(name = "contact")  
 public class Contact {  
   @Id  
   @DocumentId  
   private Integer id;  
   @Field(index = Index.YES, store = Store.NO)  
   @Analyzer(definition = "customanalyzer")  
   private String name;  
   private String email;  
      /// setter + getter  
 }  

Hibernate config xml
 <?xml version="1.0" encoding="UTF-8"?>  
 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"   
 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
 <hibernate-configuration>  
   <session-factory>  
    <property name="show_sql">false</property>  
    <property name="format_sql">true</property>  
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>  
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>  
    <property name="connection.url">jdbc:mysql://localhost:3306/test</property>  
    <property name="connection.username">root</property>  
    <property name="connection.password"></property>  
    <property name="hibernate.hbm2ddl.auto">update</property>  
    <property name="hibernate.show_sql">true</property>  
    <property name="hibernate.search.default.directory_provider">filesystem</property>  
    <property name="hibernate.search.default.indexBase">/tmp/lucene/indexes</property>  
    <mapping class="com.srccodes.example.hibernate.Contact"/>  
   </session-factory>  
 </hibernate-configuration>  

untuk menentukan dimana kita menyimpan file index nya.

 <property name="hibernate.search.default.directory_provider">filesystem</property>  
 <property name="hibernate.search.default.indexBase">/tmp/lucene/indexes</property>  


Hibernate session until (kalau kita mengunakan JPA diatas hibernate kita bisa menggunakan entity manager)

 public class HibernateUtil {  
   private static SessionFactory sessionFactory = null;   
   private static ServiceRegistry serviceRegistry = null;   
   private static SessionFactory configureSessionFactory() throws HibernateException {   
     Configuration configuration = new Configuration();   
     configuration.configure();   
     Properties properties = configuration.getProperties();  
    serviceRegistry = new ServiceRegistryBuilder().applySettings(properties).buildServiceRegistry();       
     sessionFactory = configuration.buildSessionFactory(serviceRegistry);   
     return sessionFactory;   
   }  
   static {  
    configureSessionFactory();  
   }  
   private HibernateUtil() {}  
   public static Session getSession() {  
    return sessionFactory.openSession();  
   }  
 }  

Create indexing pada setiap insert, update, delete data agar index di lucene terupdate mengikuti data di tabel.

 private void doIndex() throws InterruptedException {  
   Session session = HibernateUtil.getSession();  
   FullTextSession fullTextSession = Search.getFullTextSession(session);  
   fullTextSession.createIndexer().startAndWait();  
   fullTextSession.close();  
 }  

Create fulltext session dan lucene query yang nantinya akan diterjemahkan ke JPA/hibernate query. Jadi kita tidak perlu secara detail tahu tentang lucene query karen sudah dihandle oleh si hibernate.

 private List<Contact> search(String queryString) {  
   Session session = HibernateUtil.getSession();  
   FullTextSession fullTextSession = Search.getFullTextSession(session);  
   QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Contact.class).get();  
   org.apache.lucene.search.Query luceneQuery = queryBuilder  
   .keyword().onFields("name").matching(queryString).createQuery();  
   org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery(luceneQuery, Contact.class);  
   List<Contact> contactList = fullTextQuery.list();  
   fullTextSession.close();  
   return contactList;  
 }  

jika dilihat di sq log maka query ditabel contact hanya mengunakan where id (yg didapat dari lucene index) tidak ada pencarian berasarkan 'name'. Karena name sudah dihandle oleh hibernate-search.

 Hibernate:   
   select  
     this_.id as id1_0_0_,  
     this_.email as email2_0_0_,  
     this_.name as name3_0_0_   
   from  
     contact this_   
   where  
     (  
       this_.id in (  
         ?  
       )  
     )  


Berikut contoh pencarian data dengan beberapa keyword :
 >>>>>>Record found for 'paolo coelho' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'mr coelho paolo is his name' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'who is mister coelho paolo ?' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'famous writer paolo coelho' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  

Sekedar tambahan file index lucene bisa dibuka menggunakan software Luke.


Setiap kata di property Contact -> name di index oleh lucene (dan disimpan Id nya). Dari sinilah hibernate mencari index beradasarkan keyword yang diinginkan





Happy coding & selamat menjalankan ibadah puasa ramadhan bagi yang menjalankan.




No comments:

Post a Comment