Статьи

Веб-приложение Java и ElasticSearch (видео)


Сегодня я демонстрирую использование
ElasticSearch — открытого, распределенного и масштабируемого полнотекстового поискового движка и инструмента анализа данных в веб-приложении Java. 

Прежде всего, я хочу поделиться с вами видео записью. Я сделал это видео с целью визуального подтверждения проблем, с которыми я столкнулся в серии статей о ElasticSearch. 

Теперь мы можем продолжить с того места, где остановились. 

Наши данные будут храниться в системе управления базами данных в корпоративном приложении, где есть большой трафик и где одновременно входящие запросы могут быть выражены сотнями или даже тысячами. Однако маршрутизация всех этих данных в базу данных может создать риск узких мест. Основная цель этого руководства — минимизация операций ввода-вывода в базе данных, которые, скорее всего, создают риск узких мест (
особенно при работе с большими данными ) в веб-приложении с помощью инструмента, подобного ElasticSearch. 

Приложение (
введение происходит в видео ) с Java API, разработанным для этой цели, выполняет некоторые операции, такие как отправка почтовых данных в ElasticSearch, получение, поиск (
полный текст ), а также операции обновления и удаления. ElasticSearch также работает как хранилище данных в этом приложении. Гипотетический пример, которому мы будем следовать, — это запись опубликованных статей на kodcu.com, которая была в предыдущих статьях. 

Инструменты и технологии, которые я использовал в примере приложения: 

  • ElasticSearch версия 0.90.3
  • JSF версия 2.2
  • PrimeFaces версия 3.5
  • Jetty 7.x Maven Плагин
  • Maven версия 3.0.4
  • JDK версия 1.7

1. Структура каталога проекта

Снимок экрана 2013-08-27 в 12.43.22 утра

2.Dependencies

      <dependency>
          <groupId>org.glassfish</groupId>
          <artifactId>javax.faces</artifactId>
          <version>${jsf.version}</version>
      </dependency>
      
      <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>${primeFaces.version}</version>
      </dependency>
        
      <dependency>
          <groupId>org.primefaces.themes</groupId>
          <artifactId>bootstrap</artifactId>
          <version>${primeFacesTheme.version}</version>
      </dependency>

      <dependency>
          <groupId>commons-fileupload</groupId>
	  <artifactId>commons-fileupload</artifactId>
	  <version>${commonsFileUpload.version}</version>
      </dependency>

      <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>${elastic.version}</version>
    </dependency>
    
    <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-j2ee</artifactId>
        <version>${jbossJ2ee.version}</version>
    </dependency>

3. ClientProvider.java

public class ClientProvider {
    
    private static ClientProvider instance = null;
    private static Object lock      = new Object();
    
    private Client client;
    private Node node;

    public static ClientProvider instance(){
        
        if(instance == null) { 
            synchronized (lock) {
                if(null == instance){
                    instance = new ClientProvider();
                }
            }
        }
        return instance;
    }

    public void prepareClient(){
        node   = nodeBuilder().node();
        client = node.client();
    }

    public void closeNode(){
        
        if(!node.isClosed())
            node.close();

    }
    
    public Client getClient(){
        return client;
    }
    
    
    public void printThis() {
        System.out.println(this);
    }
    
}

Мы получим объект client еще раз, и мы будем выполнять все операции над ElasticSearch из верификатора клиента, который был закодирован с помощью
одноэлементного подхода, а не получать снова и снова перед каждой операцией. 

4. ElasticSearchSystemEventListener.java

@Override
    public void processEvent(SystemEvent event) throws AbortProcessingException {
        if(event instanceof PostConstructApplicationEvent){

            /* Preparing the ElasticSearch Client */
            System.out.println("*********************************************");
            System.out.println("Preparing the ElasticSearch Client");
            ClientProvider.instance().prepareClient();
            System.out.println("The ElasticSearch Client was prepared");
            System.out.println("*********************************************");
        }
        
        if(event instanceof PreDestroyApplicationEvent){

            /* ElasticSearch node is closing */
            System.out.println("*********************************************");
            System.out.println("ElasticSearch Node is closing");
            ClientProvider.instance().closeNode();
            System.out.println("ElasticSearch Node was closed");
            System.out.println("*********************************************");
            
        }
        
    }

Поскольку приложение было построено с использованием
JSF , мы использовали
реализацию
SystemEventListener , которая является оптимальным вариантом для создания встроенного узла при возникновении приложения, запроса клиента с этого узла, а затем закрытия его по завершении приложения. 

5. Перечисление данных в DataTable

Чтобы в полной мере использовать функциональные возможности, предоставляемые
компонентом
DataTable в PrimeFaces, документы, полученные с помощью API поиска, необходимо преобразовать в POJO. Эта операция выполняется вручную. Для этого можно использовать
библиотеку
jackson-databind .  

public void prepareDocumentList(){

        wildCardQuery = "";
        ClientProvider.instance().getClient()
                .admin().indices().prepareRefresh().execute().actionGet();

        try {

            SearchResponse response = ClientProvider.instance().getClient()
                    .prepareSearch(INDEX_NAME)
                    .setTypes(TYPE_NAME)
                    .setQuery(matchAllQuery())
                    .execute()
                    .actionGet();

            articleList.clear();

            Article temporary = null;
            String[] tags     = null;

            if (response != null) {
                for (SearchHit hit : response.getHits()) {

                    try {

                        tags = hit.getSource().get("tags").toString().split(",");
                        temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()),
                                hit.getSource().get("title").toString(),
                                hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()),
                                hit.getSource().get("author").toString(), tags);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    articleList.add(temporary);
                }
            }

            collectionSort();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }
    }

6. Реестр и обновление

public void saveArticle() {

        Long ID = 1l;
        try {

            CountResponse countResponse= ClientProvider.instance().getClient()
                                     .prepareCount(INDEX_NAME)
                                     .setQuery(termQuery("_type", TYPE_NAME))
                                     .execute().actionGet();
            ID += countResponse.getCount();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }

        String[] postTags = tag.split(",");

        try {
            
            if(null == selectArticle)
                ClientProvider.instance().getClient().prepareIndex(INDEX_NAME, TYPE_NAME, ID.toString())
                    .setSource(putJsonDocument(ID, article.getTitle(), article.getContent(),
                            article.getPostDate(), postTags,
                            article.getAuthor())).execute().actionGet();
            else {
                
                Map<String, Object> updateObject = new HashMap<String, Object>();
                
                updateObject.put("title", selectArticle.getTitle());
                updateObject.put("content", selectArticle.getContent());
                updateObject.put("postDate", selectArticle.getPostDate());
                updateObject.put("author", selectArticle.getAuthor());
                updateObject.put("tags", postTags);


                ClientProvider.instance().getClient().prepareUpdate(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString())
                        .setScript("ctx._source.title=title; ctx._source.content=content; "
                                + "ctx._source.postDate=postDate; ctx._source.author=author; "
                                + "ctx._source.tags=tags")
                               .setScriptParams(updateObject).execute().actionGet();
            }
            
        } catch (Exception ex){
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString()));
        }

        prepareDocumentList();
        initArticle();
        
    }

7. Удалить

public void removeArticle(){

        try {
            ClientProvider.instance().getClient().prepareDelete(INDEX_NAME, TYPE_NAME, selectArticle.getId().toString()).execute().actionGet();
        } catch (Exception ex){
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Sorry, an error has occurred", ex.toString()));
        }

        prepareDocumentList();
        initArticle();
    }

8. Полный текстовый источник и подстановочный знак

public void fullTextSearch(){

        articleList.clear();
        Article temporary = null;
        String[] tags     = null;

        try {
            QueryBuilder queryBuilder = QueryBuilders.queryString("*"+wildCardQuery+"*").field("_all");
            SearchRequestBuilder searchRequestBuilder = ClientProvider.instance().getClient().prepareSearch(INDEX_NAME);
            searchRequestBuilder.setTypes(TYPE_NAME);
            searchRequestBuilder.setSearchType(SearchType.DEFAULT);
            searchRequestBuilder.setQuery(queryBuilder);
            searchRequestBuilder.setFrom(0).setSize(60).setExplain(true);

            SearchResponse response = searchRequestBuilder.execute().actionGet();

            if (response != null) {

                for (SearchHit hit : response.getHits()) {

                    try {

                        tags = hit.getSource().get("tags").toString().split(",");
                        temporary = new Article(Long.parseLong(hit.getSource().get("id").toString()),
                                hit.getSource().get("title").toString(),
                                hit.getSource().get("content").toString(), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(hit.getSource().get("postDate").toString()),
                                hit.getSource().get("author").toString(), tags);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    articleList.add(temporary);
                }
            }

            collectionSort();

        } catch (IndexMissingException ex){
            System.out.println("IndexMissingException: " + ex.toString());
        }
    }

queryString , который использует анализатор запросов для анализа содержимого, позволяет вам указать поле, в котором
будет работать
queryString , а также использовать
подстановочный знак . Для QueryString поле имеет значение
index.query.default_field, и по умолчанию задано «
_all », а строка запроса как таковая обрабатывается во всех полях. Подстановочный знак «
* » относится к любой строке символов, включая пустые. 

9. Демо-приложение

Применение:
ElasticSearch