Truy Vấn Dữ Liệu Trong Lucene Thông Qua Query API

Giới thiệu về cơ chế truy vấn trong Lucene

Trong Lucene, mọi hoạt động tìm kiếm đều được thực hiện thông qua đối tượng Query. Đối tượng này đóng vai trò định nghĩa cú pháp và logic để lọc các tài liệu phù hợp từ chỉ mục. Ví dụ, biểu thức bookName:java yêu cầu hệ thống tìm kiếm các document có trường bookName chứa từ khóa "java". Có hai phương thức chính để khởi tạo đối tượng Query: sử dụng trực tiếp các lớp con hoặc thông qua trình phân tích cú pháp QueryParser.

Khởi tạo Query bằng các lớp con cụ thể

Phương pháp này cho phép lập trình viên kiểm soát chi tiết từng loại truy vấn bằng cách khởi tạo trực tiếp các đối tượng thuộc các lớp con kế thừa từ Query.

Các lớp Query thường gặp

  • TermQuery: Thực hiện tìm kiếm khớp chính xác từ khóa mà không thông qua phân tích (analyzer). Thích hợp cho các trường dữ liệu định danh như mã đơn hàng, số chứng minh thư.
  • NumericRangeQuery: Dùng để truy vấn dữ liệu số trong một khoảng xác định. Ví dụ: tìm sách có giá từ 80 đến 100.
  • BooleanQuery: Cho phép kết hợp nhiều điều kiện tìm kiếm khác nhau. Các toán tử logic bao gồm:
    • MUST: Điều kiện bắt buộc phải thỏa mãn (tương đương AND).
    • MUST_NOT: Điều kiện bắt buộc không được thỏa mãn (tương đương NOT).
    • SHOULD: Điều kiện nên thỏa mãn (tương đương OR).

Triển khai mã nguồn

Dưới đây là phương thức hỗ trợ thực thi tìm kiếm và xử lý kết quả trả về:

/**
 * Phương thức thực thi tìm kiếm và in kết quả ra console
 */
private void thucHienTimKiem(Query query) throws Exception {
    System.out.println("Cú pháp truy vấn: " + query.toString());
    
    // 1. Mở thư mục chứa chỉ mục
    Directory indexDir = FSDirectory.open(Paths.get("/var/lucene/data/index"));
    
    // 2. Tạo đối tượng đọc chỉ mục
    IndexReader indexReader = DirectoryReader.open(indexDir);
    
    // 3. Khởi tạo công cụ tìm kiếm
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 4. Thực thi tìm kiếm, lấy 10 kết quả đầu tiên
    TopDocs ketQuaTimKiem = indexSearcher.search(query, 10);

    // 5. Xử lý danh sách kết quả
    System.out.println("Tổng số kết quả tìm thấy: " + ketQuaTimKiem.totalHits);
    ScoreDoc[] danhSachDiem = ketQuaTimKiem.scoreDocs;
    
    for (ScoreDoc scoreDoc : danhSachDiem) {
        System.out.println("----------------------------");
        int docId = scoreDoc.doc;
        float score = scoreDoc.score;
        System.out.println("ID tài liệu: " + docId + " | Điểm số: " + score);
        
        // Lấy thông tin chi tiết của tài liệu
        Document doc = indexSearcher.doc(docId);
        System.out.println("Mã sách: " + doc.get("bookId"));
        System.out.println("Tên sách: " + doc.get("bookName"));
        System.out.println("Giá bán: " + doc.get("bookPrice"));
        System.out.println("Hình ảnh: " + doc.get("bookPic"));
        System.out.println("Mô tả: " + doc.get("bookDesc"));
    }
    
    // 6. Giải phóng tài nguyên
    indexReader.close();
}

Sử dụng TermQuery

Ví dụ tìm kiếm các cuốn sách có tên chứa từ khóa "java":

@Test
public void timKiemTheoTuKhoa() throws Exception {
    // Khởi tạo TermQuery với trường bookName và giá trị java
    TermQuery queryTerm = new TermQuery(new Term("bookName", "java"));
    // Thực thi tìm kiếm
    this.thucHienTimKiem(queryTerm);
}

Sử dụng NumericRangeQuery

Ví dụ tìm kiếm sách có giá trong khoảng từ 80 đến 100 (không bao gồm hai đầu mút):

@Test
public void timKiemKhoangSo() throws Exception {
    // Tham số: trường, min, max, includeMin, includeMax
    NumericRangeQuery rangeQuery = NumericRangeQuery.newFloatRange("bookPrice", 80f, 100f, false, false);
    this.thucHienTimKiem(rangeQuery); 
}

Nếu muốn bao gồm cả giá trị 80 và 100, hãy đặt hai tham số cuối cùng là true.

Sử dụng BooleanQuery

Ví dụ kết hợp điều kiện: tên sách chứa "Lucene" VÀ giá nằm trong khoảng 80-100:

@Test
public void timKiemBoolean() throws Exception {
    // Điều kiện 1: Tên sách
    TermQuery q1 = new TermQuery(new Term("bookName", "lucene"));
    
    // Điều kiện 2: Khoảng giá
    NumericRangeQuery q2 = NumericRangeQuery.newFloatRange("bookPrice", 80f, 100f, true, true);
    
    // Kết hợp điều kiện
    BooleanQuery booleanQuery = new BooleanQuery();
    booleanQuery.add(q1, Occur.MUST);
    booleanQuery.add(q2, Occur.MUST);
    
    this.thucHienTimKiem(booleanQuery);
}

Trong cú pháp truy vấn, dấu + đại diện cho điều kiện bắt buộc (MUST), dấu - đại diện cho điều kiện loại trừ (MUST_NOT).

Khởi tạo Query thông qua QueryParser

Phương thức này sử dụng QueryParser để phân tích chuỗi biểu thức tìm kiếm thành đối tượng Query, giúp linh hoạt hơn khi xây dựng truy vấn động.

Cú pháp biểu thức truy vấn

  • Tìm kiếm cơ bản: ten_truong:tu_khoa. Ví dụ: bookname:lucene.
  • Tìm kiếm khoảng: ten_truong:[min TO max]. Ví dụ: price:[80 TO 100].
    Lưu ý: QueryParser mặc định hỗ trợ khoảng chuỗi. Đối với số liệu, nên ưu dùng NumericRangeQuery để đảm bảo hiệu suất và chính xác.
  • Toán tử logic:
    • + hoặc AND: Bắt buộc thỏa mãn.
    • (khoảng trắng) hoặc OR: Thỏa mãn một trong các điều kiện.
    • - hoặc NOT: Loại trừ điều kiện.

Ví dụ sử dụng QueryParser

@Test
public void phanTichCuPhap() throws Exception {
    // 1. Cấu hình bộ phân tích từ khóa
    Analyzer analyzer = new IKAnalyzer();
    // 2. Khởi tạo trình phân tích cú pháp với trường mặc định là bookName
    QueryParser parser = new QueryParser("bookName", analyzer);
    // 3. Phân tích chuỗi truy vấn
    Query query = parser.parse("bookName:java AND bookName:lucene");
    
    // 4. Thực thi tìm kiếm
    this.thucHienTimKiem(query);
}

Khi sử dụng QueryParser, các từ khóa logic như AND, OR, NOT bắt buộc phải viết hoa. Nếu đã thiết lập trường mặc định trong constructor, bạn có thể lược bỏ tên trường trong biểu thức nếu không thay đổi.

Thẻ: lucene Java query-api full-text-search lucene-parser

Đăng vào ngày 13 tháng 6 lúc 19:32