Indexing Search Lucene
Jan 17, 2012 by Mila Yuliani
Lucene merupakan salah satu project dari Apache Foundation. lucene ini biasa digunakan untuk proses indexing search engine library. konsep ini juga yang dijadikan pedoman bagi si mbah(baca : google) untuk indexing library "keywords" yang dimasukkan oleh usernya.
pada saat aplikasi di develop mungkin sebagian orang developer langsung melakukan query pada saat ada request data dari user. apa yang terjadi ketika data yang kita miliki berjumlah jutaan, atau ratusan juta?? disamping itu, ada berapa banyak user yang mengakses aplikasi kita?! o:< connection poolnya pasti jebol kan yah!.bbbrr
nah, lucene ini akan melakukan proses indexing yang kemudian akan melakukan proses Query ke database hanya dari list yang didapat pada saat proses indexing. dengan kata lain, Query di db akan dilakukan ketika parameter object yang akan dicari diisi oleh keyword yang diinput user.
lucene ini dapat dapat diimplementasikan dengan java atau bahasa pemrograman lain.untuk lebih lengkap documentationnya disini
kemarin, saya coba membuat aplikasi sederhana menggunakan lucene+hibernate+maven+hsqldb.
ada yang menarik dari integrasi tersebut, hsqldb yang dijadikan sebagai database untuk aplikasi kita, mempunyai 3 version yaitu server,file dan memory sebagai tempat penyimpanan. jadi kita tidak perlu bingung membuat dummy database tanpa harus menginstallnya dikomputer kita.
ok, back to topic awal. kita buat pseudocode nya terlebih dahulu yah untuk dapat membuat aplikasi sederhana lucene itu. yaitu sbb :
1. create maven project
2. tambahkan dependency apa saja yang terkait untuk library kebutuhan aplikasi kita.
3. mvn eclipse:eclipse untuk install semua dependency di aplikasi kita
4. development aplikasi lucene
nah, kita sudah tau pseudocode yang akan kita buat. saya coba deklarasikan satu-satu yah
1. create maven project
untuk membuat maven project, ada 2 opsi :
a. jika plugins m2eclipse sudah terinstall dikomputer kita, maka kita tinggal membuat project maven saja.
b. jika pluginsnya tidak tersedia kita bisa menggunakan console untuk membuat maven project dengan command :
$ mvn archetype:generate (untuk dapat diimport di eclipse kita gunakan perintah mvn eclipse:eclipse) tentunya pastikan dikomputer kita sudah terinstall maven.
setelah proses generate archetype atau menggunakan create new maven project selesai, maka setelah itu
2. dependency (pom.xml)pada saat aplikasi di develop mungkin sebagian orang developer langsung melakukan query pada saat ada request data dari user. apa yang terjadi ketika data yang kita miliki berjumlah jutaan, atau ratusan juta?? disamping itu, ada berapa banyak user yang mengakses aplikasi kita?! o:< connection poolnya pasti jebol kan yah!.bbbrr
nah, lucene ini akan melakukan proses indexing yang kemudian akan melakukan proses Query ke database hanya dari list yang didapat pada saat proses indexing. dengan kata lain, Query di db akan dilakukan ketika parameter object yang akan dicari diisi oleh keyword yang diinput user.
lucene ini dapat dapat diimplementasikan dengan java atau bahasa pemrograman lain.untuk lebih lengkap documentationnya disini
kemarin, saya coba membuat aplikasi sederhana menggunakan lucene+hibernate+maven+hsqldb.
ada yang menarik dari integrasi tersebut, hsqldb yang dijadikan sebagai database untuk aplikasi kita, mempunyai 3 version yaitu server,file dan memory sebagai tempat penyimpanan. jadi kita tidak perlu bingung membuat dummy database tanpa harus menginstallnya dikomputer kita.
ok, back to topic awal. kita buat pseudocode nya terlebih dahulu yah untuk dapat membuat aplikasi sederhana lucene itu. yaitu sbb :
1. create maven project
2. tambahkan dependency apa saja yang terkait untuk library kebutuhan aplikasi kita.
3. mvn eclipse:eclipse untuk install semua dependency di aplikasi kita
4. development aplikasi lucene
nah, kita sudah tau pseudocode yang akan kita buat. saya coba deklarasikan satu-satu yah
1. create maven project
untuk membuat maven project, ada 2 opsi :
a. jika plugins m2eclipse sudah terinstall dikomputer kita, maka kita tinggal membuat project maven saja.
b. jika pluginsnya tidak tersedia kita bisa menggunakan console untuk membuat maven project dengan command :
$ mvn archetype:generate (untuk dapat diimport di eclipse kita gunakan perintah mvn eclipse:eclipse) tentunya pastikan dikomputer kita sudah terinstall maven.
setelah proses generate archetype atau menggunakan create new maven project selesai, maka setelah itu
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"3. Setelah itu, jalankan mvn clean install untuk menginstall dependency yang sudah dideklarasikan di pom.xml
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hibernate.search</groupId>
<artifactId>simple-lucene</artifactId>
<version>1.0-SNAPSHOT</version>
<name>simple-lucene</name>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-common</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-snowball</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>JBoss repository</id>
<url>http://repository.jboss.com/maven2/</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.11</version>
</plugin>
</plugins>
</build>
</project>
~$ mvn clean install<enter>4. Development application lucene
~$ mvn eclipse:eclipse<enter>
Step development lucene search index :
a. hibernate.cfg.xml pada /src/main/resources
<?xml version='1.0' encoding='utf-8'?>b. class entity pada /src/main/java
<!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="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:file:/tmp/db/testdb</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="connection.pool_size">100000</property>
<property name="current_session_context_class">org.hibernate.context.ManagedSessionContext</property>
<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.use_query_cache">false</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">create</property>
<!-- MAPPING -->
<mapping class="simple.lucene.entity.Book" />
<mapping class="simple.lucene.entity.Author" />
<mapping class="simple.lucene.entity.Publisher" />
</session-factory>
</hibernate-configuration>
Kemudian kita buat class entity yang akan digunakan sebagai object pada proses search index.hubungan yang terjadi yaitu ManyToMany dengan class entity Author. dan indexing akan terjadi di class Book sebagai parent daripada Author. jadi dalam hal ini kita bisa melakukan proses indexing ke table Author melalui Book.
- Book.java
package simple.lucene.entity;- Author.java
import java.sql.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FullTextFilterDef;
import org.hibernate.search.annotations.FullTextFilterDefs;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;
@Entity
@Indexed
@FullTextFilterDefs({
@FullTextFilterDef(name="filterBookToAuthor", impl = simple.lucene.entity.Author.class)
})
@Table(name="book" )
public class Book {
@Id@GeneratedValue
@DocumentId
private Integer id;
@Column
@Field(index = Index.TOKENIZED, store = Store.YES)
private String title;
@Column
@Field(index = Index.TOKENIZED, store = Store.NO)
private String subtitle;
@IndexedEmbedded
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
private Date publicationDate;
public Book(){
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public Set<Author> getAuthors() {
return authors;
}
public void setAuthors(Set<Author> authors) {
this.authors = authors;
}
public Date getPublicationDate() {
return publicationDate;
}
public void setPublicationDate(Date publicationDate) {
this.publicationDate = publicationDate;
}
}
Class Author disini harus mendefinisikan @Key dan @Factory. hal ini dimaksudkan untuk mendefinisikan key yang diinputkan dan definisi key yang diinput berhubungan dengan column yang mana di class entity tersebut.
package simple.lucene.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.CachingWrapperFilter;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.TermQuery;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Key;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.filter.FilterKey;
import org.hibernate.search.filter.StandardFilterKey;
@Entity
@Table(name = "author" )
@Indexed
public class Author {
@Id@GeneratedValue
private Integer id;
@Column
@Field(index = Index.TOKENIZED, store = Store.NO)
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Key
public FilterKey getKey() {
StandardFilterKey key = new StandardFilterKey();
key.addParameter(name);
return key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Factory
public Filter getFilter(){
assert(getName() != null);
return new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("name",getName()))));
}
}
c. class test pada /src/test/java
Banyak cara untuk mengimplementasikan Lucene Search Index. bisa menggunakan hibernate, ejb3-persistence, dll. tergantung dengan apa connection yang kita buat untuk mendapatkan session. disini ada 2 sample untuk mengimplementasikannya, yang pertama menggunakan hibernate(sessionFactory) dan ejb3-persistence(EntityManager). bedanya cuman dari pembuatan sessionnya saja.
- HibernateSearchSample.java
package simple.lucene.actions;- PersistenceSearchSample.java
import java.net.URL;
import java.sql.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.classic.Session;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import simple.lucene.entity.Author;
import simple.lucene.entity.Book;
public class HibernateSearchSample{
private static final Logger log = LoggerFactory.getLogger(HibernateSearchSample.class);
private Book book = new Book();
@Test
@SuppressWarnings("unchecked" )
public void connection() throws ParseException {
String path = "/hibernate.cfg.xml";
URL url = HibernateSearchSample.class.getResource(path);
AnnotationConfiguration configuration = new AnnotationConfiguration().configure(url);
SessionFactory sf = configuration.buildSessionFactory();
Session session = sf.openSession();
if (session.isConnected()) {
log.info("connect" + session);
for (Entry<String, Object> entry : ((Map<String, Object> ) session.getSessionFactory().getAllClassMetadata()).entrySet()) {
log.info("found " + entry.getKey() + ", value = "
+ entry.getValue());
}
} else {
log.info("ga connect, " + session);
}
FullTextSession fullTextSession = Search.getFullTextSession(session);
Transaction tx = fullTextSession.beginTransaction();
//insert into schema
//book.setAuthors((Set<Author> ) new Author());
book.setPublicationDate(new Date(System.currentTimeMillis()));
book.setSubtitle("Franch" ) ;
book.setTitle("UNREAD" ) ;
session.save(book);
book = new Book();
//book.setAuthors((Set<Author> ) new Author());
book.setPublicationDate(new Date(System.currentTimeMillis()));
book.setSubtitle("Dutchs" ) ;
book.setTitle("De el" ) ;
session.save(book);
Author author = new Author();
author.setName("JK.Rowling" );
session.save(author);
author = new Author();
author.setName("chaos@work" );
session.save(author);
List<Book> books = session.createQuery("from Book as book" ).list();
for (Book b : books) {
fullTextSession.index(b);
}
//mendefinisikan entity field di class book
String[] fields = new String[]{"title", "subtitle", "authors.name", "publicationDate"};
//parser field untuk dapat mengenerate query
MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
String queryCondition = "Franch";
//proses indexing menggunakan lucene
org.apache.lucene.search.Query qry = parser.parse(queryCondition);
org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery(qry, Book.class);
List results = hibQuery.list();
for(Object b : results){
System.out.println("results "+b.getClass());
}
//filter
String[] fieldsAuthor = new String[]{"id", "name"};
MultiFieldQueryParser parserAuthor = new MultiFieldQueryParser(fieldsAuthor, new StandardAnalyzer());
String queryConditionAuthor = "JK.Rowling";
org.apache.lucene.search.Query qryAuthor = parserAuthor.parse(queryConditionAuthor);
FullTextQuery ftq = fullTextSession.createFullTextQuery(qryAuthor, Book.class).setProjection("name" );
ftq.enableFullTextFilter("filterBookToAuthor" ).setParameter("name", queryConditionAuthor);
String resultsAuthor = ftq.getQueryString();
System.out.println("resultsAuthor "+resultsAuthor);
tx.commit();
}
}
package simple.lucene.actions;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.testng.annotations.Test;
import simple.lucene.entity.Book;
public class PersistenceSearchSample {
@Test
public void persistenceQuery() throws ParseException{
Ejb3Configuration configuration = new Ejb3Configuration();
EntityManagerFactory emf = configuration.configure("/hibernate.cfg.xml" ).buildEntityManagerFactory();
EntityManager em = emf.createEntityManager();
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
em.getTransaction().begin();
String[] fields = new String[]{"title", "subtitle", "authors.name", "publicationDate"};
MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
org.apache.lucene.search.Query query = parser.parse( "Indonesian" );
javax.persistence.Query persistenceQuery = fullTextEntityManager.createFullTextQuery(query, Book.class);
List results = persistenceQuery.getResultList();
System.out.println("result persistence "+results.size());
em.getTransaction().commit();
em.close();
}
}
Konsep dari lucene itu sendiri yaitu dia akan melakukan query parser dan akan mengembalikan nilai yang didefinisikan di ".projection" sehingga nilai return value nya itu yang akan diquery ke database menggunakan List<TypeList>. jadi tidak akan mengakses ke database terlebih dahulu sebelum mendapatkan index mana saja yang sesuai dengan kriteria yang ditetapkan di filter nya.
-Mila Yuliani-
