Introduction into Spring Data Elasticsearch

A presentation at Elastic User Group in October 2021 in by Alexander Reelsen

Slide 1

Slide 1

Spring Data Elasticsearch - 2021 edition Alexander Reelsen alex@elastic.co | @spinscale

Slide 2

Slide 2

Agenda Spring Data Introduction Spring Data Elasticsearch Spring Data Elasticsearch Reactive Complex Queries What’s next? Q&A

Slide 3

Slide 3

Spring Data Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store.

Slide 4

Slide 4

Spring Data Implementations: JPA, JDBC, LDAP, MongoDB, Redis, Cassandra Community: Aerospike, ArangoDB, Couchbase, Google Spanner, DynamoDb, Hazelcast, Neo4j, Solr, Yugabyte, Elasticsearch

Slide 5

Slide 5

Spring Data ElasticsearchTemplate helper class Object Mapping, Annotation based metadata ( @Id , @Field ) Repositories

Slide 6

Slide 6

Entity @Document(indexName = “products”) public class Product { @Id private String id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Text) private String description; @Field(type = FieldType.Keyword) private List<String> tags; @Field(type = FieldType.Keyword, name = “image_url”) private String imageUrl; @Field(type= FieldType.Date, format = DateFormat.date_time) private ZonedDateTime created; … }

Slide 7

Slide 7

Repositories method names reflect finders select fields, ranges, specify sorting, i.e. findByNameAndPrice , findByPriceGreaterThan Note: Inefficient queries like findByNameEndingWith / findByNameStartingWith / findByNameContaining

Slide 8

Slide 8

Repositories public interface UserProfileRepository extends ElasticsearchRepository<UserProfile, String> { }

Slide 9

Slide 9

Reactive Repostiories public interface ProductRepository extends ReactiveSortingRepository<Product, String> { { @Query(“”” “bool” : { “must” : [ { “multi_match” : { “query”: “?0”, “fields”: [ “description”, “name”, “tags” ] } } ], “should” : [ { “match” : { “name” : “?0” } } ] } } “”“) Flux<Product> findProducts(String q); }

Slide 10

Slide 10

Reactive Repositories @RestController @RequestMapping(“/products”) public class ProductController { @GetMapping(value = “/product/{id}”) public Mono<Product> retrieveProduct(@PathVariable String id) { // this does not return a 404 when the Mono is empty… weird default mode IMO return repository.findById(id); } @DeleteMapping(“/product/{id}”) public Mono<Void> deleteProduct(@PathVariable String id) { return repository.deleteById(id); } } @GetMapping(value = “/search”) public Flux<Product> search(@RequestParam String q) { return repository.findProducts(q); }

Slide 11

Slide 11

ElasticsearchTemplate final BoolQueryBuilder qb = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery(“url”, contribution.getUrl())) .must(QueryBuilders.termQuery(“submitted_by.email”, login(principal))) .must(QueryBuilders.termQuery(“category”, contribution.getCategory())); final SearchHit<Contribution> hit = elasticsearchRestTemplate.searchOne(new NativeSearchQuery(qb), Contribution.class);

Slide 12

Slide 12

Complex queries QueryBuilder qb = QueryBuilders.boolQuery() .mustNot(QueryBuilders.termQuery(“category”, Contribution.Category.CONTRIBUTION_RATING)) .filter(QueryBuilders.termQuery(“region”, region)) .filter(QueryBuilders.termQuery(“state”, Contribution.State.APPROVED)) .filter(QueryBuilders.termQuery(“cycle”, Cycle.current())); final NativeSearchQuery query = new NativeSearchQuery(qb); query.setPageable(Pageable.unpaged()); // ensure uniqueness by email, then retrieve latest fullname query.addAggregation(AggregationBuilders.terms(“by_user”).field(“submitted_by.email”).size(100) .subAggregation(AggregationBuilders.topHits(“by_name”).size(1).sort(SortBuilders.fieldSort(“created_at”) .order(SortOrder.DESC)).fetchSource(“submitted_by.full_name”, “”))); final SearchHits<Contribution> hits = elasticsearchTemplate.search(query, Contribution.class);

Slide 13

Slide 13

Client configuration @Configuration public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration { @Override @Bean public ReactiveElasticsearchClient reactiveElasticsearchClient() { final String elasticsearchUrl = System.getenv(“ELASTICSEARCH_URL”); if (elasticsearchUrl != null) { final URI uri = URI.create(elasticsearchUrl); final int port = uri.getPort(); final String host = uri.getHost(); final InetSocketAddress addr = new InetSocketAddress(host, port); final ClientConfiguration.MaybeSecureClientConfigurationBuilder builder = ClientConfiguration.builder().connectedTo(addr); if (uri.getScheme().equals(“https”)) { builder.usingSsl(); } if (uri.getUserInfo() != null) { final String[] data = uri.getUserInfo().split(“:”, 2); builder.withBasicAuth(data[0], data[1]); } } } return ReactiveRestClients.create(builder.build()); } else { // fallback to localhost:9200 as default, with SSL final InetSocketAddress addr = new InetSocketAddress(“localhost”, 9200); final ClientConfiguration.MaybeSecureClientConfigurationBuilder builder = ClientConfiguration.builder().connectedTo(addr); return ReactiveRestClients.create(builder.build()); }

Slide 14

Slide 14

What’s next New Elasticsearch Client without Elasticsearch dependency Client gets generated from specification Spring Data Elasticsearch will move to the client at some point, still in development New client has the concept of a Transport, which could be reused when talking to Elastic solutions

Slide 15

Slide 15

Resources https://github.com/spinscale/spring-boot-reactive-observability-demo https://github.com/spinscale/link-rating https://docs.spring.io/springdata/elasticsearch/docs/current/reference/html/ https://github.com/elastic/ecs-logging-java https://spinscale.de/posts/2020-04-15-introduction-into-the-elasticsearchjava-rest-client.html https://spinscale.de/posts/2020-08-06-introduction-into-spring-dataelasticsearch.html

Slide 16

Slide 16

Books

Slide 17

Slide 17

Thanks for listening Q&A Alexander Reelsen alex@elastic.co | @spinscale