mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-08 05:02:11 +00:00
parent
44a5c7545f
commit
5a36f5e1e8
@ -39,6 +39,7 @@ import org.springframework.util.Assert;
|
||||
* query.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 4.4
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
@ -329,6 +330,13 @@ class CriteriaQueryProcessor {
|
||||
throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable");
|
||||
}
|
||||
break;
|
||||
case REGEXP:
|
||||
queryBuilder //
|
||||
.regexp(rb -> rb //
|
||||
.field(fieldName) //
|
||||
.value(value.toString()) //
|
||||
.boost(boost)); //
|
||||
break;
|
||||
default:
|
||||
throw new CriteriaQueryException("Could not build query for " + entry);
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import org.springframework.util.Assert;
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author James Bodkin
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
@ -248,6 +249,9 @@ class CriteriaQueryProcessor {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REGEXP:
|
||||
query = regexpQuery(fieldName, value.toString());
|
||||
break;
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mohsin Husen
|
||||
* @author Franck Marchand
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
*/
|
||||
public class Criteria {
|
||||
|
||||
@ -611,6 +612,21 @@ public class Criteria {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link OperationKey#REGEXP} entry to the {@link #queryCriteriaEntries}.
|
||||
*
|
||||
* @param value the regexp value to match
|
||||
* @return this object
|
||||
* @since 5.1
|
||||
*/
|
||||
public Criteria regexp(String value) {
|
||||
|
||||
Assert.notNull(value, "value must not be null");
|
||||
|
||||
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.REGEXP, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region criteria entries - filter
|
||||
@ -954,7 +970,11 @@ public class Criteria {
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
NOT_EMPTY;
|
||||
NOT_EMPTY, //
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
REGEXP;
|
||||
|
||||
/**
|
||||
* @return true if this key does not have an associated value
|
||||
|
@ -15,22 +15,21 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support;
|
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.querybyexample.QueryByExampleElasticsearchExecutor;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
@ -38,6 +37,11 @@ import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
|
||||
|
||||
/**
|
||||
* Factory to create {@link ElasticsearchRepository}
|
||||
*
|
||||
@ -49,6 +53,7 @@ import org.springframework.util.Assert;
|
||||
* @author Christoph Strobl
|
||||
* @author Sascha Woo
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
*/
|
||||
public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@ -122,4 +127,17 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
|
||||
return new ElasticsearchRepositoryMetadata(repositoryInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
|
||||
RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty();
|
||||
|
||||
if (QueryByExampleExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
|
||||
fragments = fragments.append(RepositoryFragment.implemented(QueryByExampleExecutor.class,
|
||||
instantiateClass(QueryByExampleElasticsearchExecutor.class, elasticsearchOperations)));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,15 +25,19 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryMethod;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactivePartTreeElasticsearchQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.querybyexample.ReactiveQueryByExampleElasticsearchExecutor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
@ -45,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Ivan Greene
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFactorySupport {
|
||||
@ -168,4 +173,16 @@ public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
|
||||
RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty();
|
||||
|
||||
if (ReactiveQueryByExampleExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
|
||||
fragments = fragments.append(RepositoryFragment.implemented(ReactiveQueryByExampleExecutor.class,
|
||||
instantiateClass(ReactiveQueryByExampleElasticsearchExecutor.class, operations)));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.support.ExampleMatcherAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Maps a {@link Example} to a {@link org.springframework.data.elasticsearch.core.query.Criteria}
|
||||
*
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
class ExampleCriteriaMapper {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* Builds a {@link ExampleCriteriaMapper}
|
||||
*
|
||||
* @param mappingContext mappingContext to use
|
||||
*/
|
||||
ExampleCriteriaMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
<S> Criteria criteria(Example<S> example) {
|
||||
return buildCriteria(example);
|
||||
}
|
||||
|
||||
private <S> Criteria buildCriteria(Example<S> example) {
|
||||
final ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
|
||||
|
||||
return applyPropertySpecs(new Criteria(), "", example.getProbe(),
|
||||
mappingContext.getRequiredPersistentEntity(example.getProbeType()), matcherAccessor,
|
||||
example.getMatcher().getMatchMode());
|
||||
}
|
||||
|
||||
private Criteria applyPropertySpecs(Criteria criteria, String path, @Nullable Object probe,
|
||||
ElasticsearchPersistentEntity<?> persistentEntity, ExampleMatcherAccessor exampleSpecAccessor,
|
||||
ExampleMatcher.MatchMode matchMode) {
|
||||
|
||||
if (probe == null) {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(probe);
|
||||
|
||||
for (ElasticsearchPersistentProperty property : persistentEntity) {
|
||||
final String propertyName = property.getName();
|
||||
String propertyPath = StringUtils.hasText(path) ? (path + "." + propertyName) : propertyName;
|
||||
if (exampleSpecAccessor.isIgnoredPath(propertyPath) || property.isCollectionLike()
|
||||
|| property.isVersionProperty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object propertyValue = propertyAccessor.getProperty(property);
|
||||
if (property.isMap() && propertyValue != null) {
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) propertyValue).entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
criteria = applyPropertySpec(propertyPath + "." + key, value, exampleSpecAccessor, property, matchMode,
|
||||
criteria);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
criteria = applyPropertySpec(propertyPath, propertyValue, exampleSpecAccessor, property, matchMode, criteria);
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMatcherAccessor exampleSpecAccessor,
|
||||
ElasticsearchPersistentProperty property, ExampleMatcher.MatchMode matchMode, Criteria criteria) {
|
||||
|
||||
if (exampleSpecAccessor.isIgnoreCaseForPath(path)) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Current implementation of Query-by-Example supports only case-sensitive matching.");
|
||||
}
|
||||
|
||||
final Object transformedValue = exampleSpecAccessor.getValueTransformerForPath(path)
|
||||
.apply(Optional.ofNullable(propertyValue)).orElse(null);
|
||||
|
||||
if (transformedValue == null) {
|
||||
criteria = tryToAppendMustNotSentence(criteria, path, exampleSpecAccessor);
|
||||
} else {
|
||||
if (property.isEntity()) {
|
||||
return applyPropertySpecs(criteria, path, transformedValue,
|
||||
mappingContext.getRequiredPersistentEntity(property), exampleSpecAccessor, matchMode);
|
||||
} else {
|
||||
return applyStringMatcher(applyMatchMode(criteria, path, matchMode), transformedValue,
|
||||
exampleSpecAccessor.getStringMatcherForPath(path));
|
||||
}
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private Criteria tryToAppendMustNotSentence(Criteria criteria, String path,
|
||||
ExampleMatcherAccessor exampleSpecAccessor) {
|
||||
if (ExampleMatcher.NullHandler.INCLUDE.equals(exampleSpecAccessor.getNullHandler())
|
||||
|| exampleSpecAccessor.hasPropertySpecifier(path)) {
|
||||
return criteria.and(path).not().exists();
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private Criteria applyMatchMode(Criteria criteria, String path, ExampleMatcher.MatchMode matchMode) {
|
||||
if (matchMode == ExampleMatcher.MatchMode.ALL) {
|
||||
return criteria.and(path);
|
||||
} else {
|
||||
return criteria.or(path);
|
||||
}
|
||||
}
|
||||
|
||||
private Criteria applyStringMatcher(Criteria criteria, Object value, ExampleMatcher.StringMatcher stringMatcher) {
|
||||
return switch (stringMatcher) {
|
||||
case DEFAULT -> criteria.is(value);
|
||||
case EXACT -> criteria.matchesAll(value);
|
||||
case STARTING -> criteria.startsWith(validateString(value));
|
||||
case ENDING -> criteria.endsWith(validateString(value));
|
||||
case CONTAINING -> criteria.contains(validateString(value));
|
||||
case REGEX -> criteria.regexp(validateString(value));
|
||||
};
|
||||
}
|
||||
|
||||
private String validateString(Object value) {
|
||||
if (value instanceof String) {
|
||||
return value.toString();
|
||||
}
|
||||
throw new IllegalArgumentException("This operation requires a String but got " + value.getClass());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.SearchPage;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
public class QueryByExampleElasticsearchExecutor<T> implements QueryByExampleExecutor<T> {
|
||||
|
||||
protected ElasticsearchOperations operations;
|
||||
protected ExampleCriteriaMapper exampleCriteriaMapper;
|
||||
|
||||
public QueryByExampleElasticsearchExecutor(ElasticsearchOperations operations) {
|
||||
this.operations = operations;
|
||||
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Optional<S> findOne(Example<S> example) {
|
||||
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withMaxResults(2).build();
|
||||
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
if (searchHits.getTotalHits() > 1) {
|
||||
throw new org.springframework.dao.IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
return Optional.ofNullable(searchHits).filter(SearchHits::hasSearchHits)
|
||||
.map(result -> (List<S>) SearchHitSupport.unwrapSearchHits(result)).map(s -> s.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example) {
|
||||
CriteriaQuery criteriaQuery = new CriteriaQuery(exampleCriteriaMapper.criteria(example));
|
||||
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
return (List<S>) SearchHitSupport.unwrapSearchHits(searchHits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
|
||||
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withSort(sort).build();
|
||||
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
return (List<S>) SearchHitSupport.unwrapSearchHits(searchHits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
|
||||
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withPageable(pageable)
|
||||
.build();
|
||||
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
SearchPage<S> page = SearchHitSupport.searchPageFor(searchHits, criteriaQuery.getPageable());
|
||||
return (Page<S>) SearchHitSupport.unwrapSearchHits(page);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> long count(Example<S> example) {
|
||||
final CriteriaQuery criteriaQuery = new CriteriaQuery(exampleCriteriaMapper.criteria(example));
|
||||
return operations.count(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> boolean exists(Example<S> example) {
|
||||
return count(example) > 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
throw new UnsupportedOperationException("findBy example and queryFunction is not supported");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
public class ReactiveQueryByExampleElasticsearchExecutor<T> implements ReactiveQueryByExampleExecutor<T> {
|
||||
|
||||
protected ReactiveElasticsearchOperations operations;
|
||||
protected ExampleCriteriaMapper exampleCriteriaMapper;
|
||||
|
||||
public ReactiveQueryByExampleElasticsearchExecutor(ReactiveElasticsearchOperations operations) {
|
||||
this.operations = operations;
|
||||
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<S> findOne(Example<S> example) {
|
||||
return Mono.just(example)
|
||||
.map(e -> CriteriaQuery.builder(exampleCriteriaMapper.criteria(e)).withMaxResults(2).build())
|
||||
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType())))
|
||||
.buffer(2).map(searchHitList -> {
|
||||
if (searchHitList.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
return searchHitList.iterator().next();
|
||||
}).map(SearchHit::getContent).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Flux<S> findAll(Example<S> example) {
|
||||
return Mono.just(example).map(e -> new CriteriaQuery(exampleCriteriaMapper.criteria(e)))
|
||||
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType())))
|
||||
.map(SearchHit::getContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Flux<S> findAll(Example<S> example, Sort sort) {
|
||||
return Mono.just(example).map(e -> CriteriaQuery.builder(exampleCriteriaMapper.criteria(e)).withSort(sort).build())
|
||||
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType())))
|
||||
.map(SearchHit::getContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<Long> count(Example<S> example) {
|
||||
return Mono.just(example).map(e -> new CriteriaQuery(exampleCriteriaMapper.criteria(e)))
|
||||
.flatMap(criteriaQuery -> operations.count(criteriaQuery, example.getProbeType(),
|
||||
operations.getIndexCoordinatesFor(example.getProbeType())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<Boolean> exists(Example<S> example) {
|
||||
return count(example).map(count -> count > 0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T, R, P extends Publisher<R>> P findBy(Example<S> example,
|
||||
Function<FluentQuery.ReactiveFluentQuery<S>, P> queryFunction) {
|
||||
throw new UnsupportedOperationException("findBy example and queryFunction is not supported");
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,7 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class CriteriaQueryProcessorUnitTests {
|
||||
@ -456,4 +457,30 @@ class CriteriaQueryProcessorUnitTests {
|
||||
|
||||
assertEquals(expected, queryString, false);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldBuildRegexpQuery() throws JSONException {
|
||||
String expected = """
|
||||
{
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"regexp": {
|
||||
"field1": {
|
||||
"value": "[^abc]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Criteria criteria = new Criteria("field1").regexp("[^abc]");
|
||||
|
||||
var queryString = queryToJson(CriteriaQueryProcessor.createQuery(criteria), mapper);
|
||||
|
||||
assertEquals(expected, queryString, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class CriteriaQueryProcessorUnitTests {
|
||||
@ -447,4 +448,29 @@ class CriteriaQueryProcessorUnitTests {
|
||||
|
||||
assertEquals(expected, query, false);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldBuildRegexpQuery() throws JSONException {
|
||||
String expected = """
|
||||
{
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"regexp": {
|
||||
"field1": {
|
||||
"value": "[^abc]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Criteria criteria = new Criteria("field1").regexp("[^abc]");
|
||||
|
||||
String queryString = queryProcessor.createQuery(criteria).toString();
|
||||
|
||||
assertEquals(expected, queryString, false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorELCIntegrationTests.Config.class })
|
||||
public class QueryByExampleElasticsearchExecutorELCIntegrationTests
|
||||
extends QueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchTemplateConfiguration.class })
|
||||
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("query-by-example-repository");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorERHLCIntegrationTests.Config.class })
|
||||
public class QueryByExampleElasticsearchExecutorERHLCIntegrationTests
|
||||
extends QueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchRestTemplateConfiguration.class })
|
||||
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("query-by-example-repository-es7");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,638 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.assertj.core.api.AbstractThrowableAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.data.elasticsearch.utils.IdGenerator.nextIdAsString;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
abstract class QueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Autowired private SampleElasticsearchRepository repository;
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SampleEntity.class).createWithMapping();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("All QueryByExampleExecutor operations should work")
|
||||
class QueryByExampleExecutorOperations {
|
||||
@Test // #2418
|
||||
void shouldFindOne() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
Optional<SampleEntity> entityFromElasticSearch = repository.findOne(Example.of(probe));
|
||||
|
||||
// then
|
||||
assertThat(entityFromElasticSearch).contains(sampleEntity2);
|
||||
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldThrowExceptionIfMoreThanOneResultInFindOne() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("some message");
|
||||
final Example<SampleEntity> example = Example.of(probe);
|
||||
AbstractThrowableAssert<?, ? extends Throwable> assertThatThrownBy = assertThatThrownBy(
|
||||
() -> repository.findOne(example));
|
||||
|
||||
// then
|
||||
assertThatThrownBy.isInstanceOf(IncorrectResultSizeDataAccessException.class);
|
||||
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldFindOneWithNestedField() {
|
||||
// given
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntity = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntity.setNestedData("sampleNestedData");
|
||||
sampleNestedEntity.setAnotherNestedData("sampleAnotherNestedData");
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setSampleNestedEntity(sampleNestedEntity);
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntity2 = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntity2.setNestedData("sampleNestedData2");
|
||||
sampleNestedEntity2.setAnotherNestedData("sampleAnotherNestedData2");
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setSampleNestedEntity(sampleNestedEntity2);
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2));
|
||||
|
||||
// when
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntityProbe = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntityProbe.setNestedData("sampleNestedData");
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setSampleNestedEntity(sampleNestedEntityProbe);
|
||||
|
||||
Optional<SampleEntity> entityFromElasticSearch = repository.findOne(Example.of(probe));
|
||||
|
||||
// then
|
||||
assertThat(entityFromElasticSearch).contains(sampleEntity);
|
||||
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldFindAll() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("hello world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("bye world");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("hello world");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(2);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldFindAllWithSort() {
|
||||
// given
|
||||
SampleEntity sampleEntityWithRate11 = new SampleEntity();
|
||||
sampleEntityWithRate11.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate11.setMessage("hello world");
|
||||
sampleEntityWithRate11.setRate(11);
|
||||
sampleEntityWithRate11.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntityWithRate13 = new SampleEntity();
|
||||
sampleEntityWithRate13.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate13.setMessage("hello world");
|
||||
sampleEntityWithRate13.setRate(13);
|
||||
sampleEntityWithRate13.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntityWithRate22 = new SampleEntity();
|
||||
sampleEntityWithRate22.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate22.setMessage("hello world");
|
||||
sampleEntityWithRate22.setRate(22);
|
||||
sampleEntityWithRate22.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntityWithRate11, sampleEntityWithRate13, sampleEntityWithRate22));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
final Iterable<SampleEntity> all = repository.findAll();
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe),
|
||||
Sort.by(Sort.Direction.DESC, "rate"));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(3).containsExactly(sampleEntityWithRate22, sampleEntityWithRate13,
|
||||
sampleEntityWithRate11);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldFindAllWithPageable() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setRate(1);
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("hello world");
|
||||
sampleEntity2.setRate(3);
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hello world");
|
||||
sampleEntity3.setRate(2);
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
Iterable<SampleEntity> page1 = repository.findAll(Example.of(probe),
|
||||
PageRequest.of(0, 2, Sort.Direction.DESC, "rate"));
|
||||
Iterable<SampleEntity> page2 = repository.findAll(Example.of(probe),
|
||||
PageRequest.of(1, 2, Sort.Direction.DESC, "rate"));
|
||||
|
||||
// then
|
||||
assertThat(page1).isNotNull().hasSize(2).containsExactly(sampleEntity2, sampleEntity3);
|
||||
assertThat(page2).isNotNull().hasSize(1).containsExactly(sampleEntity);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldCount() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
final long count = repository.count(Example.of(probe));
|
||||
|
||||
// then
|
||||
assertThat(count).isPositive();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void shouldExists() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
boolean exists = repository.exists(Example.of(probe));
|
||||
|
||||
// then
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("All ExampleMatchers should work")
|
||||
class AllExampleMatchersShouldWork {
|
||||
|
||||
@Test // #2418
|
||||
void defaultStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("hello world");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
|
||||
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.DEFAULT))));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(1);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void exactStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("bye world");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
|
||||
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.EXACT))));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(1);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void startingStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("h");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
|
||||
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(2);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void endingStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("world");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
|
||||
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING))));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(2);
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void regexStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("[(hello)(hola)].*");
|
||||
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
|
||||
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.REGEX))));
|
||||
|
||||
// then
|
||||
assertThat(sampleEntities).isNotNull().hasSize(2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SampleEntity {
|
||||
@Nullable
|
||||
@Id private String documentId;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Keyword, store = true) private String message;
|
||||
@Nullable private Integer rate;
|
||||
@Nullable private Boolean available;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Nested, store = true, fielddata = true) private SampleNestedEntity sampleNestedEntity;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
|
||||
@Nullable
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public void setDocumentId(@Nullable String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(@Nullable String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(@Nullable String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public void setRate(Integer rate) {
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
public void setAvailable(Boolean available) {
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SampleNestedEntity getSampleNestedEntity() {
|
||||
return sampleNestedEntity;
|
||||
}
|
||||
|
||||
public void setSampleNestedEntity(SampleNestedEntity sampleNestedEntity) {
|
||||
this.sampleNestedEntity = sampleNestedEntity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public java.lang.Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(@Nullable java.lang.Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SampleEntity that = (SampleEntity) o;
|
||||
|
||||
if (!Objects.equals(rate, that.rate))
|
||||
return false;
|
||||
if (available != that.available)
|
||||
return false;
|
||||
if (!Objects.equals(documentId, that.documentId))
|
||||
return false;
|
||||
if (!Objects.equals(type, that.type))
|
||||
return false;
|
||||
if (!Objects.equals(message, that.message))
|
||||
return false;
|
||||
if (!Objects.equals(sampleNestedEntity, that.sampleNestedEntity))
|
||||
return false;
|
||||
return Objects.equals(version, that.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = documentId != null ? documentId.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
result = 31 * result + (message != null ? message.hashCode() : 0);
|
||||
result = 31 * result + (rate != null ? rate.hashCode() : 0);
|
||||
result = 31 * result + (available != null ? available.hashCode() : 0);
|
||||
result = 31 * result + (sampleNestedEntity != null ? sampleNestedEntity.hashCode() : 0);
|
||||
result = 31 * result + (version != null ? version.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static class SampleNestedEntity {
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String nestedData;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String anotherNestedData;
|
||||
|
||||
@Nullable
|
||||
public String getNestedData() {
|
||||
return nestedData;
|
||||
}
|
||||
|
||||
public void setNestedData(@Nullable String nestedData) {
|
||||
this.nestedData = nestedData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAnotherNestedData() {
|
||||
return anotherNestedData;
|
||||
}
|
||||
|
||||
public void setAnotherNestedData(@Nullable String anotherNestedData) {
|
||||
this.anotherNestedData = anotherNestedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SampleNestedEntity that = (SampleNestedEntity) o;
|
||||
|
||||
return Objects.equals(nestedData, that.nestedData) && Objects.equals(anotherNestedData, that.anotherNestedData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = nestedData != null ? nestedData.hashCode() : 0;
|
||||
result = 31 * result + (anotherNestedData != null ? anotherNestedData.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleElasticsearchRepository
|
||||
extends ElasticsearchRepository<SampleEntity, String>, QueryByExampleExecutor<SampleEntity> {}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@ContextConfiguration(classes = { ReactiveQueryByExampleElasticsearchExecutorELCIntegrationTests.Config.class })
|
||||
public class ReactiveQueryByExampleElasticsearchExecutorELCIntegrationTests
|
||||
extends ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-query-by-example-repository");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@ContextConfiguration(classes = { ReactiveQueryByExampleElasticsearchExecutorERHLCIntegrationTests.Config.class })
|
||||
public class ReactiveQueryByExampleElasticsearchExecutorERHLCIntegrationTests
|
||||
extends ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-query-by-example-repository-es7");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support.querybyexample;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.data.elasticsearch.utils.IdGenerator.nextIdAsString;
|
||||
|
||||
/**
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 5.1
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
abstract class ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
|
||||
|
||||
@Autowired private SampleReactiveElasticsearchRepository repository;
|
||||
@Autowired private ReactiveElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SampleEntity.class).createWithMapping()
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("All QueryByExampleExecutor operations should work")
|
||||
class ReactiveQueryByExampleExecutorOperations {
|
||||
@Test
|
||||
void shouldFindOne() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
repository.findOne(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(entityFromElasticSearch -> assertThat(entityFromElasticSearch).isEqualTo(sampleEntity2)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionIfMoreThanOneResultInFindOne() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
repository.findOne(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectError(IncorrectResultSizeDataAccessException.class) //
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindOneWithNestedField() {
|
||||
// given
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntity = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntity.setNestedData("sampleNestedData");
|
||||
sampleNestedEntity.setAnotherNestedData("sampleAnotherNestedData");
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setSampleNestedEntity(sampleNestedEntity);
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntity2 = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntity2.setNestedData("sampleNestedData2");
|
||||
sampleNestedEntity2.setAnotherNestedData("sampleAnotherNestedData2");
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setSampleNestedEntity(sampleNestedEntity2);
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity.SampleNestedEntity sampleNestedEntityProbe = new SampleEntity.SampleNestedEntity();
|
||||
sampleNestedEntityProbe.setNestedData("sampleNestedData");
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setSampleNestedEntity(sampleNestedEntityProbe);
|
||||
repository.findOne(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(sampleEntity) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAll() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("hello world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("bye world");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("hello world");
|
||||
repository.findAll(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextSequence(List.of(sampleEntity, sampleEntity2)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAllWithSort() {
|
||||
// given
|
||||
SampleEntity sampleEntityWithRate11 = new SampleEntity();
|
||||
sampleEntityWithRate11.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate11.setMessage("hello world");
|
||||
sampleEntityWithRate11.setRate(11);
|
||||
sampleEntityWithRate11.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntityWithRate13 = new SampleEntity();
|
||||
sampleEntityWithRate13.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate13.setMessage("hello world");
|
||||
sampleEntityWithRate13.setRate(13);
|
||||
sampleEntityWithRate13.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntityWithRate22 = new SampleEntity();
|
||||
sampleEntityWithRate22.setDocumentId(nextIdAsString());
|
||||
sampleEntityWithRate22.setMessage("hello world");
|
||||
sampleEntityWithRate22.setRate(22);
|
||||
sampleEntityWithRate22.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntityWithRate11, sampleEntityWithRate13, sampleEntityWithRate22)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
repository.findAll(Example.of(probe), Sort.by(Sort.Direction.DESC, "rate"))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextSequence(List.of(sampleEntityWithRate22, sampleEntityWithRate13, sampleEntityWithRate11)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCount() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
repository.count(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExists() {
|
||||
// given
|
||||
String documentId = nextIdAsString();
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(documentId);
|
||||
sampleEntity.setMessage("some message");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
String documentId2 = nextIdAsString();
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(documentId2);
|
||||
sampleEntity2.setMessage("some message");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setDocumentId(documentId2);
|
||||
repository.exists(Example.of(probe))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("All ExampleMatchers should work")
|
||||
class AllExampleMatchersShouldWork {
|
||||
|
||||
@Test // #2418
|
||||
void defaultStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("hello world");
|
||||
repository
|
||||
.findAll(Example.of(probe,
|
||||
ExampleMatcher.matching().withMatcher("message",
|
||||
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.DEFAULT))))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(sampleEntity) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void exactStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("bye world");
|
||||
repository
|
||||
.findAll(Example.of(probe,
|
||||
ExampleMatcher.matching().withMatcher("message",
|
||||
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.EXACT))))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(sampleEntity2) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void startingStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("h");
|
||||
repository
|
||||
.findAll(Example.of(probe,
|
||||
ExampleMatcher.matching().withMatcher("message",
|
||||
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextSequence(List.of(sampleEntity, sampleEntity3)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void endingStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("world");
|
||||
repository
|
||||
.findAll(Example.of(probe,
|
||||
ExampleMatcher.matching().withMatcher("message",
|
||||
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING))))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextSequence(List.of(sampleEntity, sampleEntity2)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #2418
|
||||
void regexStringMatcherShouldWork() {
|
||||
// given
|
||||
SampleEntity sampleEntity = new SampleEntity();
|
||||
sampleEntity.setDocumentId(nextIdAsString());
|
||||
sampleEntity.setMessage("hello world");
|
||||
sampleEntity.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity2 = new SampleEntity();
|
||||
sampleEntity2.setDocumentId(nextIdAsString());
|
||||
sampleEntity2.setMessage("bye world");
|
||||
sampleEntity2.setVersion(System.currentTimeMillis());
|
||||
|
||||
SampleEntity sampleEntity3 = new SampleEntity();
|
||||
sampleEntity3.setDocumentId(nextIdAsString());
|
||||
sampleEntity3.setMessage("hola mundo");
|
||||
sampleEntity3.setVersion(System.currentTimeMillis());
|
||||
|
||||
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3L) //
|
||||
.verifyComplete();
|
||||
|
||||
// when
|
||||
SampleEntity probe = new SampleEntity();
|
||||
probe.setMessage("[(hello)(hola)].*");
|
||||
repository
|
||||
.findAll(Example.of(probe,
|
||||
ExampleMatcher.matching().withMatcher("message",
|
||||
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.REGEX))))
|
||||
// then
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextSequence(List.of(sampleEntity, sampleEntity3)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SampleEntity {
|
||||
@Nullable
|
||||
@Id private String documentId;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Keyword, store = true) private String message;
|
||||
@Nullable private Integer rate;
|
||||
@Nullable private Boolean available;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Nested, store = true,
|
||||
fielddata = true) private SampleEntity.SampleNestedEntity sampleNestedEntity;
|
||||
@Nullable
|
||||
@Version private Long version;
|
||||
|
||||
@Nullable
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public void setDocumentId(@Nullable String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(@Nullable String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(@Nullable String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public void setRate(Integer rate) {
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
public void setAvailable(Boolean available) {
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SampleEntity.SampleNestedEntity getSampleNestedEntity() {
|
||||
return sampleNestedEntity;
|
||||
}
|
||||
|
||||
public void setSampleNestedEntity(SampleEntity.SampleNestedEntity sampleNestedEntity) {
|
||||
this.sampleNestedEntity = sampleNestedEntity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public java.lang.Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(@Nullable java.lang.Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SampleEntity that = (SampleEntity) o;
|
||||
|
||||
if (!Objects.equals(rate, that.rate))
|
||||
return false;
|
||||
if (available != that.available)
|
||||
return false;
|
||||
if (!Objects.equals(documentId, that.documentId))
|
||||
return false;
|
||||
if (!Objects.equals(type, that.type))
|
||||
return false;
|
||||
if (!Objects.equals(message, that.message))
|
||||
return false;
|
||||
if (!Objects.equals(sampleNestedEntity, that.sampleNestedEntity))
|
||||
return false;
|
||||
return Objects.equals(version, that.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = documentId != null ? documentId.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
result = 31 * result + (message != null ? message.hashCode() : 0);
|
||||
result = 31 * result + (rate != null ? rate.hashCode() : 0);
|
||||
result = 31 * result + (available != null ? available.hashCode() : 0);
|
||||
result = 31 * result + (sampleNestedEntity != null ? sampleNestedEntity.hashCode() : 0);
|
||||
result = 31 * result + (version != null ? version.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static class SampleNestedEntity {
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String nestedData;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String anotherNestedData;
|
||||
|
||||
@Nullable
|
||||
public String getNestedData() {
|
||||
return nestedData;
|
||||
}
|
||||
|
||||
public void setNestedData(@Nullable String nestedData) {
|
||||
this.nestedData = nestedData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAnotherNestedData() {
|
||||
return anotherNestedData;
|
||||
}
|
||||
|
||||
public void setAnotherNestedData(@Nullable String anotherNestedData) {
|
||||
this.anotherNestedData = anotherNestedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SampleEntity.SampleNestedEntity that = (SampleEntity.SampleNestedEntity) o;
|
||||
|
||||
return Objects.equals(nestedData, that.nestedData) && Objects.equals(anotherNestedData, that.anotherNestedData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = nestedData != null ? nestedData.hashCode() : 0;
|
||||
result = 31 * result + (anotherNestedData != null ? anotherNestedData.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleReactiveElasticsearchRepository
|
||||
extends ReactiveElasticsearchRepository<SampleEntity, String>, ReactiveQueryByExampleExecutor<SampleEntity> {}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user