1. Información general
En este artículo, vamos a profundizar en algunos conceptos clave relacionados con los motores de búsqueda de texto completo, con un enfoque especial en Elasticsearch.
Como este es un artículo orientado a Java, no daremos un tutorial detallado paso a paso sobre cómo configurar Elasticsearch y mostraremos cómo funciona bajo el capó. En su lugar, vamos a apuntar al cliente de Java y cómo usar las funciones principales como indexar , eliminar , obtener y buscar .
2. Configuración
En aras de la simplicidad, usaremos una imagen de Docker para nuestra instancia de Elasticsearch, aunque cualquier instancia de Elasticsearch que escuche en el puerto 9200 servirá .
Comenzamos activando nuestra instancia de Elasticsearch:
docker run -d --name es762 -p 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2
De forma predeterminada, Elasticsearch escucha en el puerto 9200 para las próximas consultas HTTP. Podemos verificar que se ha iniciado correctamente abriendo la URL // localhost: 9200 / en su navegador favorito:
{ "name" : "M4ojISw", "cluster_name" : "docker-cluster", "cluster_uuid" : "CNnjvDZzRqeVP-B04D3CmA", "version" : { "number" : "7.6.2", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "2f4c224", "build_date" : "2020-03-18T23:22:18.622755Z", "build_snapshot" : false, "lucene_version" : "8.4.0", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.8.0-beta1" }, "tagline" : "You Know, for Search" }
3. Configuración de Maven
Ahora que tenemos nuestro clúster Elasticsearch básico en funcionamiento, pasemos directamente al cliente Java. Primero que nada, necesitamos tener la siguiente dependencia de Maven declarada en nuestro archivo pom.xml :
org.elasticsearch elasticsearch 7.6.2
Siempre puede consultar las últimas versiones alojadas por Maven Central con el enlace proporcionado anteriormente.
4. API de Java
Antes de pasar directamente a cómo usar las características principales de la API de Java, debemos iniciar RestHighLevelClient :
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo("localhost:9200").build(); RestHighLevelClient client = RestClients.create(clientConfiguration).rest();
4.1. Indexación de documentos
La función index () permite almacenar un documento JSON arbitrario y hacerlo buscable:
@Test public void givenJsonString_whenJavaObject_thenIndexDocument() { String jsonObject = "{\"age\":10,\"dateOfBirth\":1471466076564," +"\"fullName\":\"John Doe\"}"; IndexRequest request = new IndexRequest("people"); request.source(jsonObject, XContentType.JSON); IndexResponse response = client.index(request, RequestOptions.DEFAULT); String index = response.getIndex(); long version = response.getVersion(); assertEquals(Result.CREATED, response.getResult()); assertEquals(1, version); assertEquals("people", index); }
Tenga en cuenta que es posible utilizar cualquier biblioteca JSON Java para crear y procesar sus documentos. Si no está familiarizado con ninguno de estos, puede usar los ayudantes de Elasticsearch para generar sus propios documentos JSON :
XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .field("fullName", "Test") .field("dateOfBirth", new Date()) .field("age", "10") .endObject(); IndexRequest indexRequest = new IndexRequest("people"); indexRequest.source(builder); IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); assertEquals(Result.CREATED, response.getResult());
4.2. Consulta de documentos indexados
Ahora que tenemos un documento JSON con capacidad de búsqueda indexado, podemos continuar y buscar usando el método search () :
SearchRequest searchRequest = new SearchRequest(); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); SearchHit[] searchHits = response.getHits().getHits(); List results = Arrays.stream(searchHits) .map(hit -> JSON.parseObject(hit.getSourceAsString(), Person.class)) .collect(Collectors.toList());
Los resultados devueltos por el método search () se denominan Hits , cada Hit se refiere a un documento JSON que coincide con una solicitud de búsqueda.
En este caso, la lista de resultados contiene todos los datos almacenados en el clúster. Tenga en cuenta que en este ejemplo estamos usando la biblioteca FastJson para convertir cadenas JSON en objetos Java.
Podemos mejorar la solicitud agregando parámetros adicionales para personalizar la consulta utilizando los métodos de QueryBuilders :
SearchSourceBuilder builder = new SearchSourceBuilder() .postFilter(QueryBuilders.rangeQuery("age").from(5).to(15)); SearchRequest searchRequest = new SearchRequest(); searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH); searchRequest.source(builder); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
4.3. Recuperar y eliminar documentos
Los métodos get () y delete () permiten obtener o eliminar un documento JSON del clúster usando su id:
GetRequest getRequest = new GetRequest("people"); getRequest.id(id); GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); // process fields DeleteRequest deleteRequest = new DeleteRequest("people"); deleteRequest.id(id); DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
La sintaxis es bastante sencilla, solo necesita especificar el índice junto con la identificación del objeto.
5. Ejemplos de QueryBuilders
La clase QueryBuilders proporciona una variedad de métodos estáticos que se utilizan como comparadores dinámicos para encontrar entradas específicas en el clúster. Mientras usamos el método search () para buscar documentos JSON específicos en el clúster, podemos usar creadores de consultas para personalizar los resultados de la búsqueda.
Aquí hay una lista de los usos más comunes de la API de QueryBuilders .
El método matchAllQuery () devuelve un objeto QueryBuilder que coincide con todos los documentos del clúster:
QueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
El rangeQuery () coincide con documentos en los que el valor de un campo está dentro de cierto rango:
QueryBuilder matchDocumentsWithinRange = QueryBuilders .rangeQuery("price").from(15).to(100)
Proporcionar un nombre de campo, por ejemplo , fullName y el valor correspondiente, por ejemplo, John Doe , el método matchQuery () coincide con todos los documentos con el valor de este campo exacto:
QueryBuilder matchSpecificFieldQuery= QueryBuilders .matchQuery("fullName", "John Doe");
También podemos usar el método multiMatchQuery () para construir una versión de múltiples campos de la consulta de coincidencia:
QueryBuilder matchSpecificFieldQuery= QueryBuilders.matchQuery( "Text I am looking for", "field_1", "field_2^3", "*_field_wildcard");
Podemos usar el símbolo de intercalación (^) para impulsar campos específicos .
En nuestro ejemplo, field_2 tiene un valor de impulso establecido en tres, lo que lo hace más importante que los otros campos. Tenga en cuenta que es posible utilizar comodines y consultas de expresiones regulares, pero en cuanto al rendimiento, tenga cuidado con el consumo de memoria y la demora en el tiempo de respuesta cuando se trata de comodines, porque algo como * _apples puede causar un gran impacto en el rendimiento.
The coefficient of importance is used to order the result set of hits returned after executing the search() method.
If you are more familiar with the Lucene queries syntax, you can use the simpleQueryStringQuery() method to customize search queries:
QueryBuilder simpleStringQuery = QueryBuilders .simpleQueryStringQuery("+John -Doe OR Janette");
As you can probably guess, we can use the Lucene's Query Parser syntax to build simple, yet powerful queries. Here're some basic operators that can be used alongside the AND/OR/NOT operators to build search queries:
- The required operator (+): requires that a specific piece of text exists somewhere in fields of a document.
- El operador de prohibición ( - ): excluye todos los documentos que contienen una palabra clave declarada después del símbolo ( - ).
6. Conclusión
En este artículo rápido, hemos visto cómo usar la API Java de ElasticSearch para realizar algunas de las funciones comunes relacionadas con los motores de búsqueda de texto completo.
Puede consultar el ejemplo proporcionado en este artículo en el proyecto GitHub.