Skip to content

Commit 97a4574

Browse files
christophstroblodrotbohm
authored andcommitted
#497 - Add reactive Spring Data Elasticsearch example.
1 parent 50fadd5 commit 97a4574

File tree

12 files changed

+491
-2
lines changed

12 files changed

+491
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ We have separate folders for the samples of individual modules:
5959
## Spring Data Elasticsearch
6060

6161
* `example` - Example how to use basic text search, geo-spatial search and facets.
62+
* `reactive` - Example how to use reactive client, template and repository features.
6263

6364
## Spring Data Neo4j
6465

elasticsearch/pom.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<modules>
1919
<module>example</module>
20+
<module>reactive</module>
2021
</modules>
2122

2223
<dependencies>
@@ -30,13 +31,13 @@
3031
<profile>
3132
<id>spring-data-next-releasetrain</id>
3233
<properties>
33-
<elasticsearch.version>6.6.0</elasticsearch.version>
34+
<elasticsearch.version>6.6.1</elasticsearch.version>
3435
</properties>
3536
</profile>
3637
<profile>
3738
<id>spring-data-next-releasetrain-released</id>
3839
<properties>
39-
<elasticsearch.version>6.6.0</elasticsearch.version>
40+
<elasticsearch.version>6.6.1</elasticsearch.version>
4041
</properties>
4142
</profile>
4243
</profiles>

elasticsearch/reactive/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.project
2+
.classpath
3+
.springBeans
4+
.settings/
5+
target/
6+
7+
#IntelliJ Stuff
8+
.idea
9+
*.iml
10+
11+
##ignore local node data files for unit tests
12+
/data
13+
/.DS_Store

elasticsearch/reactive/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Spring Data Elasticsearch - Reactive Examples
2+
3+
The `ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
4+
It uses the request/response objects provided by the Elasticsearch core project.
5+
Calls are directly operated on the reactive stack, not wrapping async (thread pool bound) responses into reactive types.
6+
7+
````java
8+
class ApplicationConfiguration extends AbstractReactiveElasticsearchConfiguration {
9+
10+
@Bean
11+
@Override
12+
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
13+
return ReactiveRestClients.create(ClientConfiguration.localhost());
14+
}
15+
}
16+
````
17+
18+
The `ReactiveElasticsearchClient` can be used with the `ReactiveElasticsearchOperations` and `ReactiveElasticsearchRepository`.
19+
20+
```java
21+
@Autowired ReactiveElasticsearchOperations operations;
22+
23+
// ...
24+
25+
CriteriaQuery query = new CriteriaQuery("keywords").contains("java");
26+
27+
Flux<Conference> result = operations.find(query, Conference.class);
28+
```
29+
30+
```java
31+
interface ConferenceRepository extends ReactiveCrudRepository<Conference, String> {
32+
33+
Flux<Conference> findAllByKeywordsContains(String keyword);
34+
}
35+
36+
// ...
37+
38+
@Autowired ConferenceRepository repository;
39+
40+
// ...
41+
42+
Flux<Conference> result = repository.findAllByKeywordsContains("java");
43+
```
44+
45+
46+
**Requirements:**
47+
48+
* [Maven](http://maven.apache.org/download.cgi)
49+
* [Elasticsearch](https://www.elastic.co/de/downloads/elasticsearch)
50+
51+
**Running Elasticsearch**
52+
53+
```bash
54+
$ cd elasticsearch
55+
$ ./bin/elasticsearch
56+
```
57+
58+

elasticsearch/reactive/pom.xml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>org.springframework</groupId>
7+
<artifactId>spring-data-elasticsearch-reactive-example</artifactId>
8+
9+
<name>Spring Data Elasticsearch - Reactive Example</name>
10+
<description>Sample projects for Reactive Spring Data Elasticsearch</description>
11+
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
12+
13+
<parent>
14+
<groupId>org.springframework.data.examples</groupId>
15+
<artifactId>spring-data-elasticsearch-examples</artifactId>
16+
<version>2.0.0.BUILD-SNAPSHOT</version>
17+
</parent>
18+
19+
<dependencies>
20+
21+
<dependency>
22+
<groupId>io.projectreactor</groupId>
23+
<artifactId>reactor-test</artifactId>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>io.projectreactor</groupId>
28+
<artifactId>reactor-test</artifactId>
29+
</dependency>
30+
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-webflux</artifactId>
34+
</dependency>
35+
36+
</dependencies>
37+
38+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.elasticsearch.conference;
17+
18+
import javax.annotation.PostConstruct;
19+
import java.io.IOException;
20+
import java.util.Arrays;
21+
22+
import org.elasticsearch.ElasticsearchStatusException;
23+
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
24+
import org.elasticsearch.client.RequestOptions;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.convert.support.DefaultConversionService;
29+
import org.springframework.data.elasticsearch.client.ClientConfiguration;
30+
import org.springframework.data.elasticsearch.client.RestClients;
31+
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
32+
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
33+
import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration;
34+
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
35+
import org.springframework.data.elasticsearch.core.EntityMapper;
36+
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
37+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
38+
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
39+
import reactor.test.StepVerifier;
40+
41+
/**
42+
* @author Christoph Strobl
43+
*/
44+
@Configuration
45+
@EnableReactiveElasticsearchRepositories
46+
class ApplicationConfiguration extends AbstractReactiveElasticsearchConfiguration {
47+
48+
@Autowired ReactiveElasticsearchOperations operations;
49+
@Autowired ConferenceRepository repository;
50+
51+
@Bean
52+
@Override
53+
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
54+
return ReactiveRestClients.create(ClientConfiguration.localhost());
55+
}
56+
57+
@Override
58+
public EntityMapper entityMapper() {
59+
return new ElasticsearchEntityMapper(elasticsearchMappingContext(),
60+
new DefaultConversionService());
61+
}
62+
63+
@PostConstruct
64+
public void insertDataSample() {
65+
66+
try {
67+
RestClients.create(ClientConfiguration.localhost()).rest().indices().create(new CreateIndexRequest("conference-index"), RequestOptions.DEFAULT);
68+
} catch (IOException | ElasticsearchStatusException e) {
69+
// just ignore it
70+
}
71+
72+
// Remove all documents
73+
repository.deleteAll().subscribe();
74+
75+
// Save data sample
76+
repository.save(Conference.builder().date("2014-11-06").name("Spring eXchange 2014 - London")
77+
.keywords(Arrays.asList("java", "spring")).location(new GeoPoint(51.500152D, -0.126236D)).build()).then().as(StepVerifier::create).verifyComplete();
78+
repository.save(Conference.builder().date("2014-12-07").name("Scala eXchange 2014 - London")
79+
.keywords(Arrays.asList("scala", "play", "java")).location(new GeoPoint(51.500152D, -0.126236D)).build()).then().as(StepVerifier::create).verifyComplete();
80+
repository.save(Conference.builder().date("2014-11-20").name("Elasticsearch 2014 - Berlin")
81+
.keywords(Arrays.asList("java", "elasticsearch", "kibana")).location(new GeoPoint(52.5234051D, 13.4113999))
82+
.build()).then().as(StepVerifier::create).verifyComplete();
83+
repository.save(Conference.builder().date("2014-11-12").name("AWS London 2014")
84+
.keywords(Arrays.asList("cloud", "aws")).location(new GeoPoint(51.500152D, -0.126236D)).build()).then().as(StepVerifier::create).verifyComplete();
85+
repository.save(Conference.builder().date("2014-10-04").name("JDD14 - Cracow")
86+
.keywords(Arrays.asList("java", "spring")).location(new GeoPoint(50.0646501D, 19.9449799)).build()).then().as(StepVerifier::create).verifyComplete();
87+
}
88+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.elasticsearch.conference;
17+
18+
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
19+
20+
import java.util.List;
21+
22+
import lombok.Builder;
23+
import lombok.Data;
24+
import org.springframework.data.annotation.Id;
25+
import org.springframework.data.elasticsearch.annotations.Document;
26+
import org.springframework.data.elasticsearch.annotations.Field;
27+
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
28+
29+
/**
30+
* @auhtor Christoph Strobl
31+
*/
32+
@Data
33+
@Builder
34+
@Document(indexName = "conference-index", type = "geo-class-point-type", shards = 1, replicas = 0,
35+
refreshInterval = "-1")
36+
public class Conference {
37+
38+
private @Id String id;
39+
private String name;
40+
private @Field(type = Date) String date;
41+
private GeoPoint location;
42+
private List<String> keywords;
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.elasticsearch.conference;
17+
18+
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
19+
import reactor.core.publisher.Flux;
20+
21+
/**
22+
* @author Christoph Strobl
23+
*/
24+
interface ConferenceRepository extends ReactiveCrudRepository<Conference, String> {
25+
26+
Flux<Conference> findAllByKeywordsContainsAndDateAfter(String keyword, String Date);
27+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Uncomment both entries to connect with local elasticsearch cluster
2+
#spring.data.elasticsearch.clusterName=elasticsearch
3+
#spring.data.elasticsearch.clusterNodes=localhost:9300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.elasticsearch.conference;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import example.springdata.elasticsearch.util.ElasticsearchAvailable;
21+
import reactor.test.StepVerifier;
22+
23+
import java.text.ParseException;
24+
import java.text.SimpleDateFormat;
25+
26+
import org.junit.ClassRule;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.boot.test.context.SpringBootTest;
31+
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
32+
import org.springframework.data.elasticsearch.core.query.Criteria;
33+
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
34+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
35+
36+
/**
37+
* Test case to show Spring Data Elasticsearch functionality.
38+
*
39+
* @author Christoph Strobl
40+
*/
41+
@RunWith(SpringJUnit4ClassRunner.class)
42+
@SpringBootTest(classes = ApplicationConfiguration.class)
43+
public class ReactiveElasticsearchOperationsTest {
44+
45+
public static @ClassRule ElasticsearchAvailable elasticsearchAvailable = ElasticsearchAvailable.onLocalhost();
46+
47+
private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
48+
49+
@Autowired ReactiveElasticsearchOperations operations;
50+
51+
@Test
52+
public void textSearch() {
53+
54+
String expectedDate = "2014-10-29";
55+
String expectedWord = "java";
56+
CriteriaQuery query = new CriteriaQuery(
57+
new Criteria("keywords").contains(expectedWord).and("date").greaterThanEqual(expectedDate));
58+
59+
operations.find(query, Conference.class) //
60+
.as(StepVerifier::create) //
61+
.consumeNextWith(it -> verify(it, expectedWord, expectedDate)) //
62+
.consumeNextWith(it -> verify(it, expectedWord, expectedDate)) //
63+
.consumeNextWith(it -> verify(it, expectedWord, expectedDate)) //
64+
.verifyComplete();
65+
}
66+
67+
void verify(Conference it, String expectedWord, String expectedDate) {
68+
69+
assertThat(it.getKeywords()).contains(expectedWord);
70+
try {
71+
assertThat(format.parse(it.getDate())).isAfter(format.parse(expectedDate));
72+
} catch (ParseException e) {
73+
fail("o_O", e);
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)