Récupération de Documents Elasticsearch avec le Client Java Haut Niveau

Introduction à la Récupération de Données Elasticsearch

La récupération de documents spécifiques dans Elasticsearch est une opération fondamentale pour toute application interagissant avec ce moteur de recherche. Le client Java haut niveau (RestHighLevelClient) fournit une API complète pour interagir avec un cluster Elasticsearch. Cet article détaillle comment utiliser la requête GetRequest pour récupérer des docuemnts par leur identifiant unique, en mettant l'accent sur la sélection des champs source et la gestion des réponses, y compris les appels asynchrones.

Configuration de la Requête GET

Pour récupérer un document, nous instancions une GetRequest, en spécifiant impérativement l'index et l'ID du document cible. Une fonctionnalité clé est la capacité de filtrer les champs source qui seront inclus ou exclus de la réponse via le FetchSourceContext. Cela permet d'optimiser les performances en réduisant la quantité de données transférées et de gérer la confidentialité des informations.

Voici un exemple d'implémentation d'un composant Spring pour la récupération de documents Elasticsearch :


import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.fetch.source.FetchSourceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.CompletableFuture; // Pour la gestion asynchrone plus moderne

/**
 * Gère les opérations de récupération de documents individuels dans Elasticsearch.
 */
@Component
public class ElasticsearchDocumentRetriever {

    private final RestHighLevelClient esClient;

    @Autowired
    public ElasticsearchDocumentRetriever(RestHighLevelClient esClient) {
        this.esClient = esClient;
    }

    /**
     * Récupère un document par son index et son identifiant, avec des options de filtrage de source.
     * Cette méthode effectue une récupération synchrone.
     *
     * @param targetIndex Nom de l'index Elasticsearch.
     * @param docId Identifiant du document à récupérer.
     * @return Le contenu JSON du document, ou {@code null} si non trouvé ou en cas d'erreur.
     */
    public String retrieveDocument(String targetIndex, String docId) {
        GetRequest documentFetchRequest = new GetRequest(targetIndex, docId);

        // Définition des champs à inclure et à exclure de la source du document
        String[] fieldsToInclude = {"username", "content", "*Timestamp"}; // Ex: "user", "message", "*Date"
        String[] fieldsToExclude = {};

        FetchSourceContext sourceFilterContext = new FetchSourceContext(true, fieldsToInclude, fieldsToExclude);
        documentFetchRequest.fetchSourceContext(sourceFilterContext);

        String documentPayload = null;
        try {
            GetResponse esResponse = esClient.get(documentFetchRequest, RequestOptions.DEFAULT);

            if (esResponse.isExists()) {
                documentPayload = esResponse.getSourceAsString();
            } else {
                System.out.println("Document avec l'ID '" + docId + "' non trouvé dans l'index '" + targetIndex + "'.");
            }
        } catch (IOException e) {
            System.err.println("Erreur de communication IO lors de la récupération: " + e.getMessage());
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.NOT_FOUND) {
                System.err.println("Index ou document non trouvé: " + e.getMessage());
            } else if (e.status() == RestStatus.CONFLICT) {
                System.err.println("Conflit de version lors de la récupération: " + e.getMessage());
            } else {
                System.err.println("Erreur Elasticsearch inattendue: " + e.getMessage());
            }
        }
        return documentPayload;
    }

    /**
     * Récupère un document de manière asynchrone.
     *
     * @param targetIndex Nom de l'index.
     * @param docId ID du document.
     * @return Un CompletableFuture contenant le contenu JSON du document. Le future est complété
     *         avec null si le document n'existe pas, ou avec une exception en cas d'échec.
     */
    public CompletableFuture<String> retrieveDocumentAsync(String targetIndex, String docId) {
        GetRequest asyncDocumentRequest = new GetRequest(targetIndex, docId);
        String[] fieldsToInclude = {"username", "content", "*Timestamp"};
        FetchSourceContext sourceFilterContext = new FetchSourceContext(true, fieldsToInclude, new String[]{});
        asyncDocumentRequest.fetchSourceContext(sourceFilterContext);

        CompletableFuture<String> asyncResultFuture = new CompletableFuture<>();

        esClient.getAsync(asyncDocumentRequest, RequestOptions.DEFAULT, new org.elasticsearch.action.ActionListener<GetResponse>() {
            @Override
            public void onResponse(GetResponse response) {
                if (response.isExists()) {
                    asyncResultFuture.complete(response.getSourceAsString());
                } else {
                    System.out.println("Document '" + docId + "' non trouvé lors de l'appel asynchrone.");
                    asyncResultFuture.complete(null); // Document non trouvé
                }
            }

            @Override
            public void onFailure(Exception e) {
                System.err.println("Échec de la récupération asynchrone du document '" + docId + "': " + e.getMessage());
                asyncResultFuture.completeExceptionally(e); // Propager l'exception
            }
        });
        return asyncResultFuture;
    }
}

Gestion des Réponse Asynchrones

L'exécution asynchrone est une pratique courante pour améliorer la réactivité des appliactions, évitant de bloquer les threads appelants. Le RestHighLevelClient facilite cela grâce à l'interface ActionListener. Dans l'exemple ci-dessus, nous encapsulons la logique asynchrone dans un CompletableFuture, offrant une approche plus moderne et gérable pour la composition et la gestion des résultats asynchrones en Java.

Intégration avec Spring Boot

Pour intégrer ce service de récupération de documents dans une application Spring Boot, nous pouvons suivre l'architecture classique en couches, avec un service applicatif et un contrôleur REST.

Couche Service

Cette couche sert de façade pour la logique métier, en validant les entrées et en coordonnant les appels aux composants de données.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Objects;

/**
 * Service applicatif pour les requêtes de documents Elasticsearch.
 */
@Service
public class DocumentManagementService {

    private final ElasticsearchDocumentRetriever documentRetriever;

    @Autowired
    public DocumentManagementService(ElasticsearchDocumentRetriever documentRetriever) {
        this.documentRetriever = documentRetriever;
    }

    /**
     * Récupère le contenu JSON d'un document Elasticsearch de manière synchrone.
     * @param index Nom de l'index.
     * @param id Identifiant du document.
     * @return Le contenu JSON du document ou une chaîne vide si les paramètres sont invalides ou si le document n'est pas trouvé.
     */
    public String getDocumentContentJson(String index, String id) {
        if (!StringUtils.hasText(index) || !StringUtils.hasText(id)) {
            System.err.println("Paramètres d'index ou d'ID invalides pour la récupération.");
            return "";
        }
        return Objects.requireNonNullElse(documentRetriever.retrieveDocument(index, id), "");
    }
}

Contrôleur REST

Le contrôleur expose un point de terminaison HTTP permettant aux clients d'interroger les documents via une API REST.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Contrôleur REST pour l'accès aux documents stockés dans Elasticsearch.
 */
@RestController
@RequestMapping("/api/documents")
public class ElasticsearchDocumentQueryController {

    private final DocumentManagementService docService;

    @Autowired
    public ElasticsearchDocumentQueryController(DocumentManagementService docService) {
        this.docService = docService;
    }

    /**
     * Point de terminaison pour récupérer un document par son index et son ID.
     * Exemple de requête: GET /api/documents/fetch?index=my_data&id=doc_1
     * @param index Le nom de l'index Elasticsearch.
     * @param id L'identifiant du document.
     * @return Le contenu JSON du document ou une réponse 404 si non trouvé.
     */
    @GetMapping("/fetch")
    public ResponseEntity<String> fetchDocument(
            @RequestParam("index") String index,
            @RequestParam("id") String id) {
        String document = docService.getDocumentContentJson(index, id);
        if (!document.isEmpty()) {
            return ResponseEntity.ok(document);
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}

Cette architecture en couches fournit une structure claire et maintenable pour interagir avec Elasticsearch, en tirant parti des capacités du client Java haut niveau et de l'écosystème Spring Boot.

Étiquettes: Elasticsearch Java RestHighLevelClient Spring Boot récupération de données

Publié le 29 juin à 17h08