-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
674dcef
commit f32c3de
Showing
8 changed files
with
993 additions
and
1 deletion.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
boot-opensearch-sample/src/main/java/com/example/opensearch/model/response/PagedResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,63 @@ | ||
package com.example.opensearch.model.response; | ||
|
||
import com.example.opensearch.entities.Restaurant; | ||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.elasticsearch.core.SearchPage; | ||
|
||
@JsonInclude(JsonInclude.Include.NON_NULL) | ||
public record PagedResult<T>( | ||
List<T> data, | ||
long totalElements, | ||
int pageNumber, | ||
int totalPages, | ||
Map<String, Map<String, Long>> aggregationMap, | ||
@JsonProperty("isFirst") boolean isFirst, | ||
@JsonProperty("isLast") boolean isLast, | ||
@JsonProperty("hasNext") boolean hasNext, | ||
@JsonProperty("hasPrevious") boolean hasPrevious) { | ||
|
||
public PagedResult(Page<T> page) { | ||
this( | ||
page.getContent(), | ||
page.getTotalElements(), | ||
page.getNumber() + 1, | ||
page.getTotalPages(), | ||
new HashMap<>(), | ||
page.isFirst(), | ||
page.isLast(), | ||
page.hasNext(), | ||
page.hasPrevious()); | ||
} | ||
|
||
public PagedResult(SearchPage<T> searchPage) { | ||
this( | ||
(List<T>) searchPage.getContent(), | ||
searchPage.getTotalElements(), | ||
searchPage.getNumber() + 1, | ||
searchPage.getTotalPages(), | ||
new HashMap<>(), | ||
searchPage.isFirst(), | ||
searchPage.isLast(), | ||
searchPage.hasNext(), | ||
searchPage.hasPrevious()); | ||
} | ||
|
||
public PagedResult( | ||
SearchPage<Restaurant> searchPage, Map<String, Map<String, Long>> aggregationMap) { | ||
this( | ||
(List<T>) searchPage.getContent(), | ||
searchPage.getTotalElements(), | ||
searchPage.getNumber() + 1, | ||
searchPage.getTotalPages(), | ||
aggregationMap, | ||
searchPage.isFirst(), | ||
searchPage.isLast(), | ||
searchPage.hasNext(), | ||
searchPage.hasPrevious()); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
boot-opensearch-sample/src/main/java/com/example/opensearch/model/response/ResultData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.example.opensearch.model.response; | ||
|
||
import org.springframework.data.geo.Point; | ||
|
||
public record ResultData(String name, Point location, Double dist) {} |
44 changes: 44 additions & 0 deletions
44
...-sample/src/main/java/com/example/opensearch/repositories/CustomRestaurantRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.example.opensearch.repositories; | ||
|
||
import com.example.opensearch.entities.Restaurant; | ||
import com.example.opensearch.model.response.PagedResult; | ||
import java.util.List; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.data.elasticsearch.core.SearchHitsIterator; | ||
import org.springframework.data.elasticsearch.core.SearchPage; | ||
import org.springframework.data.elasticsearch.core.geo.GeoPoint; | ||
|
||
public interface CustomRestaurantRepository { | ||
SearchHitsIterator<Restaurant> searchWithin(GeoPoint geoPoint, Double distance, String unit); | ||
|
||
PagedResult<Restaurant> findByBoroughOrCuisineOrName( | ||
String query, Boolean prefixPhraseEnabled, Pageable pageable); | ||
|
||
PagedResult<Restaurant> termQueryForBorough(String queryTerm, Pageable pageable); | ||
|
||
PagedResult<Restaurant> termsQueryForBorough(List<String> queries, Pageable pageable); | ||
|
||
PagedResult<Restaurant> queryBoolWithShould( | ||
String borough, String cuisine, String name, Pageable pageable); | ||
|
||
PagedResult<Restaurant> wildcardSearch(String queryKeyword, Pageable pageable); | ||
|
||
PagedResult<Restaurant> regExpSearch(String reqEx, Pageable pageable); | ||
|
||
PagedResult<Restaurant> searchSimpleQueryForBoroughAndCuisine( | ||
String queryKeyword, Pageable pageable); | ||
|
||
PagedResult<Restaurant> searchRestaurantIdRange( | ||
Long lowerLimit, Long upperLimit, Pageable pageable); | ||
|
||
PagedResult<Restaurant> searchDateRange(String fromDate, String toDate, Pageable pageable); | ||
|
||
SearchPage<Restaurant> aggregateSearch( | ||
String searchKeyword, | ||
List<String> fieldNames, | ||
Sort.Direction direction, | ||
Integer limit, | ||
Integer offset, | ||
String[] sortFields); | ||
} |
217 changes: 217 additions & 0 deletions
217
...ple/src/main/java/com/example/opensearch/repositories/CustomRestaurantRepositoryImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package com.example.opensearch.repositories; | ||
|
||
import com.example.opensearch.entities.Restaurant; | ||
import com.example.opensearch.model.response.PagedResult; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import org.opensearch.data.client.orhlc.NativeSearchQuery; | ||
import org.opensearch.data.client.orhlc.NativeSearchQueryBuilder; | ||
import org.opensearch.index.query.BoolQueryBuilder; | ||
import org.opensearch.index.query.MultiMatchQueryBuilder; | ||
import org.opensearch.index.query.Operator; | ||
import org.opensearch.index.query.QueryBuilders; | ||
import org.opensearch.search.aggregations.AggregationBuilders; | ||
import org.opensearch.search.aggregations.BucketOrder; | ||
import org.opensearch.search.aggregations.bucket.range.DateRangeAggregationBuilder; | ||
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder; | ||
import org.springframework.data.domain.PageRequest; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.data.elasticsearch.core.*; | ||
import org.springframework.data.elasticsearch.core.geo.GeoPoint; | ||
import org.springframework.data.elasticsearch.core.query.Criteria; | ||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery; | ||
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder; | ||
import org.springframework.data.elasticsearch.core.query.Query; | ||
|
||
public class CustomRestaurantRepositoryImpl implements CustomRestaurantRepository { | ||
|
||
private final ElasticsearchOperations elasticsearchOperations; | ||
|
||
private static final int PAGE_SIZE = 1_000; | ||
|
||
public CustomRestaurantRepositoryImpl(ElasticsearchOperations elasticsearchOperations) { | ||
this.elasticsearchOperations = elasticsearchOperations; | ||
} | ||
|
||
@Override | ||
public SearchHitsIterator<Restaurant> searchWithin( | ||
GeoPoint geoPoint, Double distance, String unit) { | ||
|
||
Query query = | ||
new CriteriaQuery( | ||
new Criteria("address.coord").within(geoPoint, distance.toString() + unit)); | ||
|
||
// add a sort to get the actual distance back in the sort value | ||
Sort sort = Sort.by(new GeoDistanceOrder("address.coord", geoPoint).withUnit(unit)); | ||
query.addSort(sort); | ||
|
||
return elasticsearchOperations.searchForStream(query, Restaurant.class); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> findByBoroughOrCuisineOrName( | ||
String queryKeyWord, Boolean prefixPhraseEnabled, Pageable pageable) { | ||
MultiMatchQueryBuilder multiMatchQuery = | ||
QueryBuilders.multiMatchQuery(queryKeyWord, "borough", "cuisine", "name"); | ||
if (prefixPhraseEnabled) { | ||
multiMatchQuery.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX); | ||
} | ||
|
||
Query query = new NativeSearchQuery(multiMatchQuery); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
private PagedResult<Restaurant> getResults(Query query) { | ||
SearchHits<Restaurant> searchHits = elasticsearchOperations.search(query, Restaurant.class); | ||
SearchPage<Restaurant> searchPage = | ||
SearchHitSupport.searchPageFor(searchHits, query.getPageable()); | ||
return new PagedResult<>(searchPage); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> termQueryForBorough(String queryTerm, Pageable pageable) { | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.termQuery("borough", queryTerm).caseInsensitive(true)); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> termsQueryForBorough(List<String> queries, Pageable pageable) { | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.termsQuery( | ||
"borough", queries.stream().map(String::toLowerCase).toList())); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> queryBoolWithShould( | ||
String borough, String cuisine, String name, Pageable pageable) { | ||
BoolQueryBuilder queryBuilders = new BoolQueryBuilder(); | ||
queryBuilders.should(QueryBuilders.matchQuery("borough", borough)); | ||
queryBuilders.should(QueryBuilders.wildcardQuery("cuisine", "*" + cuisine + "*")); | ||
queryBuilders.should(QueryBuilders.matchQuery("name", name)); | ||
Query query = new NativeSearchQuery(queryBuilders); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> wildcardSearch(String queryKeyword, Pageable pageable) { | ||
BoolQueryBuilder queryBuilders = new BoolQueryBuilder(); | ||
queryBuilders.should(QueryBuilders.wildcardQuery("borough", "*" + queryKeyword + "*")); | ||
queryBuilders.should(QueryBuilders.wildcardQuery("cuisine", "*" + queryKeyword + "*")); | ||
queryBuilders.should(QueryBuilders.wildcardQuery("name", "*" + queryKeyword + "*")); | ||
Query query = new NativeSearchQuery(queryBuilders); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> regExpSearch(String reqEx, Pageable pageable) { | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.regexpQuery("borough", reqEx).caseInsensitive(true)); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> searchSimpleQueryForBoroughAndCuisine( | ||
String queryKeyword, Pageable pageable) { | ||
Map<String, Float> map = new HashMap<>(); | ||
map.put("borough", 1.0F); | ||
map.put("cuisine", 2.0F); | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.simpleQueryStringQuery(queryKeyword).fields(map)); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> searchRestaurantIdRange( | ||
Long lowerLimit, Long upperLimit, Pageable pageable) { | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.rangeQuery("id").gte(lowerLimit).lte(upperLimit)); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public PagedResult<Restaurant> searchDateRange( | ||
String fromDate, String toDate, Pageable pageable) { | ||
Query query = | ||
new NativeSearchQuery( | ||
QueryBuilders.rangeQuery("grades.date").gte(fromDate).lte(toDate)); | ||
query.setPageable(pageable); | ||
|
||
return getResults(query); | ||
} | ||
|
||
@Override | ||
public SearchPage<Restaurant> aggregateSearch( | ||
String searchKeyword, | ||
List<String> fieldNames, | ||
Sort.Direction direction, | ||
Integer limit, | ||
Integer offset, | ||
String[] sortFields) { | ||
TermsAggregationBuilder cuisineTermsBuilder = | ||
AggregationBuilders.terms("MyCuisine") | ||
.field("cuisine") | ||
.size(PAGE_SIZE) | ||
.order(BucketOrder.count(false)); | ||
TermsAggregationBuilder boroughTermsBuilder = | ||
AggregationBuilders.terms("MyBorough") | ||
.field("borough") | ||
.size(PAGE_SIZE) | ||
.order(BucketOrder.count(false)); | ||
DateRangeAggregationBuilder dateRangeBuilder = | ||
AggregationBuilders.dateRange("MyDateRange").field("grades.date"); | ||
addDateRange(dateRangeBuilder); | ||
|
||
Query query = | ||
new NativeSearchQueryBuilder() | ||
.withQuery( | ||
QueryBuilders.multiMatchQuery( | ||
searchKeyword, fieldNames.toArray(String[]::new)) | ||
.operator(Operator.OR)) | ||
.withSort(Sort.by(direction, sortFields)) | ||
.withAggregations( | ||
cuisineTermsBuilder, boroughTermsBuilder, dateRangeBuilder) | ||
.build(); | ||
query.setPageable(PageRequest.of(offset, limit)); | ||
|
||
return SearchHitSupport.searchPageFor( | ||
elasticsearchOperations.search(query, Restaurant.class), query.getPageable()); | ||
} | ||
|
||
private void addDateRange(DateRangeAggregationBuilder dateRangeBuilder) { | ||
ZonedDateTime zonedDateTime = | ||
ZonedDateTime.now().withDayOfMonth(1).toLocalDate().atStartOfDay(ZoneId.of("UTC")); | ||
dateRangeBuilder.addUnboundedTo(zonedDateTime.minusMonths(12)); | ||
for (int i = 12; i > 0; i--) { | ||
dateRangeBuilder.addRange( | ||
zonedDateTime.minusMonths(i), zonedDateTime.minusMonths(i - 1).minusSeconds(1)); | ||
} | ||
dateRangeBuilder.addUnboundedFrom(zonedDateTime); | ||
} | ||
} |
11 changes: 10 additions & 1 deletion
11
...search-sample/src/main/java/com/example/opensearch/repositories/RestaurantRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,20 @@ | ||
package com.example.opensearch.repositories; | ||
|
||
import com.example.opensearch.entities.Restaurant; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; | ||
import org.springframework.data.repository.ListCrudRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface RestaurantRepository | ||
extends ElasticsearchRepository<Restaurant, String>, | ||
ListCrudRepository<Restaurant, String> {} | ||
ListCrudRepository<Restaurant, String>, | ||
CustomRestaurantRepository { | ||
|
||
Page<Restaurant> findByBorough(String borough, Pageable pageable); | ||
|
||
Page<Restaurant> findByBoroughAndCuisineAndName( | ||
String borough, String cuisine, String name, Pageable pageable); | ||
} |
Oops, something went wrong.