Elastic Search - Java API - Part 1

Creating a spring boot application for Elastic search 

Elastic search is a search engine based on lucene. Elastic search is fast!! Really fast.
Read the introductory blog on Elastic search here.

We decided to use Elastic search for caching and faster retrieval of data for non critical read API's where a small delay could be accommodated (ES is near real time and not real time).

 
Here is a list of java clients for ES. 

Initially I tired using the spring data elastic search dependency. However this dose not support latest elastic version. Hence moving to the native Java client. 

The below implementation is using the native Java client:

The following dependencies need to be added in the pom file.

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.2.1</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>

However you may face few errors while compiling and building the package hence ensure to also add the below dependencies. 


<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>5.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.8.1</version>
</dependency>
Wondering why you should not use spring-data-elasticsearch?
Spring data elastic dose not seem to support elastic latest versions as yet. Refer links below for more details :


Steps :

Create a Elastic Search config File

ElasticSearchConfig.java
@Configuration
@PropertySource(value = "classpath:application.properties")
public class ElasticSearchConfig {
    @Value("${elasticsearch.host}")
    private String EsHost;
    @Value("${elasticsearch.port}")
    private int EsPort;
    @Value("${elasticsearch.clustername}")
    private String EsClusterName;
    public Client client() throws Exception {
        Settings settings = Settings.builder()
                .put("cluster.name", "elasticsearch").build();
        TransportClient client = new PreBuiltTransportClient(settings)
  .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(EsHost), EsPort));
        return client;
    }
}
Step 2 :
Creating a repository

import java.util.List;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class EsRepository {
    @Autowired
    ElasticSearchConfig esConfig;

    public IndexResponse save(String index, String type, byte[] jsonByte)
            throws Exception {
        Client esclient = esConfig.client();
        IndexResponse response = esclient.prepareIndex(index, type)
                .setSource(jsonByte).get();
        return response;
    }

    public GetResponse get(String index, String type, String docId)
            throws Exception {
        Client esclient = esConfig.client();
        return esclient.prepareGet(index, type, docId).get();
    }

    public SearchResponse search(List<String> indexList,
            List<String> typelist, MatchQueryBuilder query)
            throws Exception {
        String[] indexArray = indexList
                .toArray(new String[indexList.size()]);
        String[] typeArray = typelist.toArray(new String[typelist
                .size()]);
        Client esclient = esConfig.client();
        SearchResponse response = esclient.prepareSearch(indexArray)
                .setTypes(typeArray)
                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setQuery(query) // Query
                .setExplain(true).get();
        return response;
    }

}
Step 3 :  BookServie interface class


import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
public interface BookService {
    IndexResponse save(BookMeta book) throws Exception;
    GetResponse get(String docId) throws Exception;
    SearchResponse search(String title) throws Exception;
}
Step 4 : Implement a service that invokes the repository. The service layer will mostly contain all the business logic. 
In order to have a scalable implementation it is better to use day / month / year wise partition on the index and type.
The search query provides functionality to query multiple indexes and types at the same time.
Will share more details on the search API in the next post.


import java.util.ArrayList;
import java.util.List;

import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;

@Service
@PropertySource(value = "classpath:application.properties")
public class BookImpl implements BookService {
    @Autowired
    EsRepository esrepo;

    @Value("${elasticsearch.book.index}")
    private String bookIndex;

    @Value("${elasticsearch.book.type}")
    private String bookType;

    @Override
    public IndexResponse save(bookMeta bookMeta) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        byte[] jsonByte = mapper.writeValueAsBytes(bookMeta);
        return esrepo.save(bookIndex, bookType, jsonByte);
    }

    @Override
    public GetResponse get(String docId) throws Exception {
        return esrepo.get(bookIndex, bookType, docId);
    }

    @Override
    public SearchResponse search(String title) throws Exception {
        List<String> bookingIndexList = new ArrayList<String>();
        bookingIndexList.add(bookIndex);
        List<String> bookingTypeList = new ArrayList<String>();
        bookingTypeList.add(bookType);
        MatchQueryBuilder query = QueryBuilders.matchQuery("title", title);
        return esrepo.search(bookingIndexList,bookingTypeList,query);
    }
}
Step 4 :  BookServie interface class

import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
public interface BookService {
    IndexResponse save(BookMeta book) throws Exception;
    GetResponse get(String docId) throws Exception;
    SearchResponse search(String title) throws Exception;
}
Step 5 :
The main method that will formulate and structure as required. This could also be a part of the service layer.

Continued in Post 2 

Comments