+5

Spring Data Elasticsearch - Tận dụng Elasticsearch trong ứng dụng Spring Boot

Elasticsearch là một hệ thống tìm kiếm và phân tích văn bản mã nguồn mở dựa trên Lucene. Nó được thiết kế để xử lý và tìm kiếm dữ liệu với tốc độ cực kỳ nhanh, giúp bạn tìm thấy thông tin cần thiết từ tập dữ liệu lớn một cách hiệu quả. Trong môi trường phát triển Java và đặc biệt là trong ứng dụng Spring Boot, Elasticsearch có thể được tích hợp một cách dễ dàng để cung cấp khả năng tìm kiếm mạnh mẽ và phân tích dữ liệu.

1. Lợi ích đem lại khi sử dụng Elasticsearch trong ứng dụng Spring Boot

Việc sử dụng Elasticsearch trong ứng dụng Spring Boot mang lại nhiều lợi ích quan trọng cho việc tìm kiếm, lọc và phân tích dữ liệu. Dưới đây là một vài lợi ích chính khi tích hợp Elasticsearch trong ứng dụng Spring Boot.

  • Tìm kiếm nhanh và hiệu quả: Elasticsearch được thiết kế để cung cấp tìm kiếm văn bản nhanh chóng và hiệu quả. Với việc sử dụng Elasticsearch trong Spring Boot, bạn có thể tìm thấy thông tin cần thiết từ tập dữ liệu lớn một cách nhanh chóng, đáp ứng yêu cầu tìm kiếm thời gian thực của ứng dụng.
  • Phân tích dữ liệu mạnh mẽ: Elasticsearch không chỉ hỗ trợ tìm kiếm mà còn cung cấp các tính năng phân tích mạnh mẽ thông qua Aggregations. Bạn có thể thực hiện các thống kê, tính toán trung bình, tổng hợp và nhiều phân tích khác để trích xuất thông tin quan trọng từ dữ liệu của bạn.
  • Tích hợp dễ dàng với Spring Boot: Spring Data Elasticsearch cung cấp một lớp trừu tượng để tương tác với Elasticsearch từ ứng dụng Spring Boot. Việc tích hợp Elasticsearch vào ứng dụng Spring Boot trở nên dễ dàng hơn bao giờ hết, giúp quản lý tài liệu và truy vấn dữ liệu một các thuận tiện.
  • Tối ưu hóa truy vấn: Elasticsearch cung cấp cú pháp DSL để xác định các truy vấn phức tạp. Bạn có thể tối ưu hóa truy vấn để đảm bảo rằng bạn đang tìm kiếm và lọc dữ liệu một cách chính xác và hiệu quả.
  • Scalability và Tích hợp với Dự án lớn hơn: Elasticsearch có khả năng mở rộng theo nhu cầu, giúp bạn xử lý cả tập dữ liệu lớn và tăng cường khả năng tìm kiếm khi cần. Điều này rất hữu ích khi bạn phát triển dự án lớn và cần xử lý dữ liệu từ nhiều nguồn khác nhau.
  • Tương thích với nhiều loại dữ liệu: Elasticsearch không chỉ hỗ trợ tìm kiếm và phân tích văn bản, mà còn có thể được sử dụng để lưu trữ và tìm kiếm các dạng dữ liệu khác như số liệu thống kê, dữ liệu, địa lý và nhiều loại dữ liệu khác.
  • Cải thiện hiệu suất ứng dụng: Elasticsearch có khả năng tối ưu hóa và caching dữ liệu, giúp cải thiện hiệu suất tìm kiếm và truy vấn. Điều này đảm bảo rằng ứng dụng của bạn hoạt động nhanh chóng và hiệu quả.

2. Spring Data Elasticsearch

Spring Data Elasticsearch là một phần của dự án Spring Data, một phần của hệ sinh thái Spring, cung cấp một lớp trừu tượng để tương tác với Elasticsearch từ ứng dụng Spring Boot. Nó giúp bạn tạo, truy vấn và quản lý các tài liệu trong Elasticsearch một các dễ dàng thông qua các giao diện repository.

  • Tích hợp dễ dàng với Spring Boot: Một điểm mạnh mẽ của Spring Data Elasticsearch là tích hợp dễ dàng với Spring Boot. Bạn chỉ cần thêm dependency cho Spring Data Elasticsearch trong tệp pom.xml hoặc build.gradle của bạn và Spring Boot sẽ tự động cấu hình và khởi tạo một ElasticsearchTemplate.
  • Annotations và Mapping tự động: Spring Data Elasticsearch cho phép bạn sử dụng các Annotations như @Document@Field để xác định các tài liệu và trường trong tài liệu của bạn. Các trường trong model Java được tự động ánh xạ vào các trường tương ứng trong Elasticsearch. Điều này giúp bạn tọa chỉ mục và mapping một các tự động và thuận tiện.
  • Repository Abstraction và tìm kiếm đơn giản: Spring Data Elasticsearch cung cấp các giao diện repository cho các model của bạn, tương tự như Spring Data JPA. Bạn có thể sử dụng các phương thức tiêu chuẩn như save, findById, findAll và thậm chí là các phương thức tìm kiếm tùy chỉnh. Các truy vấn đơn giản có thể được thực hiện thông qua các phương thức repository.
  • Phân tích dữ liệu với Aggregations: Không chỉ tìm kiếm, Spring Data Elasticsearch còn hỗ trợ Aggregations, cho phép bạn thực hiện các phân tích trên dữ liệu. Bạn có thể thực hiện các thống kê, tính toán trung bình, tổng hợp và nhiều phân tích khác để tìm hiểu thông tin quan trọng từ dữ liệu.

3. Làm việc với Spring Data Elasticsearch

a. Thêm Dependency

Đầu tiên để làm việc được với Spring Data Elasticsearch trong dự án Spring Boot, bạn cần thêm dependency cho Spring Data Elasticsearch vào tệp build.gradle hoặc pom.xml của dự án.

  • Đối với Gradle:
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
  • Đối với Maven:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

b. Cấu hình Elasticsearch

Trong tệp application.yml hoặc application.properties, bạn cần cung cấp thông tin cấu hình cho Elasticsearch, bao gồm tên cụm và các nút cụm:

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: localhost:9200

c. Tạo Model

Tạo các lớp Java đại diện cho các tài liệu bạn muốn lưu trữ trong Elasticsearch. Sử dụng các Annotations như @Document để xác định tên tài liệu và @Field để xác định các trường.

@Document(indexName = "my-custom-index")
public class MyDocument {
    @Id
    private String id;
    @Field(type = FieldType.Text)
    private String field1;
    @Field(type = FieldType.Keyword)
    private String field2;
    
    // Getters and setters
}

d. Tạo Repository

Tạo giao diện repository để tương tác với dữ liệu trong Elasticsearch. Sử dụng các phương thức tiêu chuẩn như save, findById, findAll, và các phương thức tìm kiếm tùy chỉnh.

public interface MyDocumentRepository extends ElasticsearchRepository<MyDocument, String> {
    List<MyDocument> findByField1(String field1);
}

e. Tạo Service

Trong service hoặc controller của bạn, bạn có thể sử dụng repository để tương tác với Elasticsearch.

@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public void saveDocument(MyDocument document) {
        documentRepository.save(document);
    }

    public List<MyDocument> searchDocumentsByField(String field) {
        return documentRepository.findByField1(field);
    }
}

4. Elasticsearch Queries và Aggregations

Elasticsearch Queries

Elasticsearch Queries đề cập đến các truy vấn được sử dụng để tìm kiếm và trích xuất dữ liệu từ các chỉ mục Elasticsearch. Elasticsearch hỗ trợ nhiều loại truy vấn khác nhau để bạn có thể tìm kiếm và lọc dữ liệu theo các tiêu chí khác nhau. Dưới đây là một số loại truy vấn phổ biến trong Elasticsearch.

  • Match Query: Truy vấn tìm kiếm dựa trên sự khớp chính xác hoặc gần giống của các từ khóa văn bản.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByContent(String searchTerm) {
        MatchQueryBuilder query = QueryBuilders.matchQuery("content", searchTerm);
        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Term Query: Truy vấn tìm kiếm dựa trên khớp chính xác của một từ khóa cụ thể. Nó thường được sử dụng cho các trường không phải là văn bản, chẳng hạn như trường keyword.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByCategory(String category) {
        TermQueryBuilder query = QueryBuilders.termQuery("category", category);
        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Bool Query: Truy vấn logic AND, OR và NOT để kết hợp nhiều điều kiện tìm kiếm. Bạn có thể sử dụng must, should, và must_not để xác định các điều kiện.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchWithBoolQuery(String searchTerm1, String searchTerm2) {
        BoolQueryBuilder query = QueryBuilders.boolQuery()
            .should(QueryBuilders.matchQuery("content", searchTerm1))
            .should(QueryBuilders.matchQuery("title", searchTerm2));

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Range Query: Truy vấn để tìm kiếm các giá trị trong khoảng cụ thể của một trường. Ví dụ, bạn có thể tìm kiếm các tài liệu có trường "age" nằm trong khoảng từ 18 đến 30.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByAgeRange(int minAge, int maxAge) {
        RangeQueryBuilder query = QueryBuilders.rangeQuery("age")
            .gte(minAge) // Greater than or equal to minAge
            .lte(maxAge); // Less than or equal to maxAge

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Wildcard Query: Truy vấn tìm kiếm sử dụng ký tự đại diện để tìm kiếm các từ hoặc cụm từ khớp với một mẫu cụ thể. Ví dụ, bạn có thể sử dụng * để tìm kiếm các từ bắt đầu bằng "elast".
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByWildcard(String wildcardPattern) {
        WildcardQueryBuilder query = QueryBuilders.wildcardQuery("content", wildcardPattern);

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Prefix Query: Tìm kiếm các từ hoặc cụm từ bắt đầu bằng một tiền tố cụ thể. Ví dụ, prefix query có thể được sử dụng để tìm kiếm các từ bắt đầu bằng "elast".
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByPrefix(String prefix) {
        PrefixQueryBuilder query = QueryBuilders.prefixQuery("title", prefix);

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Fuzzy Query: Truy vấn tìm kiếm với khả năng diễn giải, cho phép tìm kiếm các từ hoặc cụm từ có mức độ tương tự cao với từ khóa được cung cấp.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByFuzzy(String searchTerm) {
        FuzzyQueryBuilder query = QueryBuilders.fuzzyQuery("content", searchTerm)
            .fuzziness(Fuzziness.AUTO) // You can adjust fuzziness level here

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Match Phrase Query: Tìm kiếm chính xác một cụm từ cố định. Nó tìm kiếm các tài liệu chứa cụm từ cụ thể mà thứ tự từng từ trong cụm từ cũng cần phải trùng khớp.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public List<MyDocument> searchByPhrase(String phrase) {
        MatchPhraseQueryBuilder query = QueryBuilders.matchPhraseQuery("content", phrase);

        Iterable<MyDocument> result = documentRepository.search(query);
        return StreamSupport.stream(result.spliterator(), false)
                            .collect(Collectors.toList());
    }
}
  • Nested Query: Được sử dụng khi bạn có trường dạng nested trong tài liệu và muốn tìm kiếm trong các trường con của trường nested đó.
  • More Like This Query: Truy vấn tìm kiếm các tài liệu tương tự với một tài liệu đã biết.

Aggregations

Aggregations là một tính năng mạnh mẽ cho phép bạn thực hiện các phân tích và tính toán tổng hợp dựa trên dữ liệu trong các chỉ mục Elasticsearch. Aggregations giúp bạn hiểu rõ hơn về cấu trúc và xu hướng của dữ liệu bên trong chỉ mục, cho phép bạn trích xuất thông tin phân tích quan trọng. Aggregations tương tự như GROUP BY và các hàm tổng hợp như SUM, AVG, COUNT, MIN, MAX, ... trong SQL, tuy nhiên Elasticsearch cung cấp một khả năng linh hoạt và mở rộng hơn để thực hiện các tính toán phân tích trên tập dữ liệu lớn.

Terms Aggregation: Tổng hợp dữ liệu dựa trên các giá trị duy nhất trong một trường cụ thể. Tương tự như việc sử dụng GROUP BY trong SQL.

  • Trong DSL:
    {
      "aggs": {
        "categories": {
          "terms": {
            "field": "category.keyword"
          }
        }
      }
    }
  • Trong Spring Data Elasticsearch:
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Terms getCategoriesAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("categories")
            .field("category.keyword");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("categories");
    }
}
  • Date Histogram Aggregation: Tổng hợp dữ liệu dựa trên khoảng thời gian như ngày, tuần, tháng.
  • Trong DSL:
{
  "aggs": {
    "posts_over_time": {
      "date_histogram": {
        "field": "timestamp",
        "interval": "month"
      }
    }
  }
}
  • Trong Spring Data Elasticsearch:
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Histogram getPostsOverTimeAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        DateHistogramAggregationBuilder aggregation = AggregationBuilders.dateHistogram("posts_over_time")
            .field("timestamp")
            .calendarInterval(DateHistogramInterval.MONTH);

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("posts_over_time");
    }
}
  • Range Aggregation: Tổng hợp dữ liệu dựa trên các khoảng giá trị mà bạn xác định.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Range getAgeRangeAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        RangeAggregationBuilder aggregation = AggregationBuilders.range("age_range")
            .field("age")
            .addUnboundedTo(30)
            .addRange(30, 40)
            .addUnboundedFrom(40);

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("age_range");
    }
}
  • Avg Aggregation: Tính giá trị trung bình của một trường số.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Avg getAverageAgeAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        AvgAggregationBuilder aggregation = AggregationBuilders.avg("average_age")
            .field("age");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("average_age");
    }
}
  • Sum Aggregation: Tính tổng của các giá trị trong một trường số.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Sum getAgeSumAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        SumAggregationBuilder aggregation = AggregationBuilders.sum("age_sum")
            .field("age");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("age_sum");
    }
}
  • Min Aggregation: Tìm giá trị nhỏ nhất trong một trường số.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Min getMinAgeAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        MinAggregationBuilder aggregation = AggregationBuilders.min("min_age")
            .field("age");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("min_age");
    }
}
  • Max Aggregation: Tìm giá trị lớn nhất trong một trường số.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Max getMaxAgeAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        MaxAggregationBuilder aggregation = AggregationBuilders.max("max_age")
            .field("age");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("max_age");
    }
}
  • Cardinality Aggregation: Đếm số lượng giá trị duy nhất trong một trường.
@Service
public class MyService {
    private final MyDocumentRepository documentRepository;

    @Autowired
    public MyService(MyDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    public Cardinality getUniqueCategoryCountAggregation() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        CardinalityAggregationBuilder aggregation = AggregationBuilders.cardinality("unique_categories")
            .field("category.keyword");

        queryBuilder.addAggregation(aggregation);

        SearchHits<MyDocument> searchHits = documentRepository.search(queryBuilder.build());
        Aggregations aggregations = searchHits.getAggregations();

        return aggregations.get("unique_categories");
    }
}

Aggregations là một tính năng quan trọng trong Elasticsearch cho phép bạn thực hiện các phân tích và tính toán tổng hợp trên dữ liệu trong chỉ mục. Chúng giúp bạn hiểu rõ hơn về cấu trúc và xu hướng của dữ liệu, trích xuất thông tin quan trọng và xây dựng các bảng điều khiển và thống kê. Khi kết hợp với Spring Data Elasticsearch, bạn có thể tận dụng tích hợp sẵn và dễ dàng thực hiện các truy vấn và aggregations.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí