DATAREDIS-605 - Query by Example for Redis Repositories.
We now support query by example via Redis repositories. We allow case-sensitive, exact matching of singular simple and nested properties, using any/all match modes, value transformation of the criteria value for indexed properties.
We do not support findAll with sorting, case-insensitive properties or string matchers other than exact.
interface PersonRepository extends QueryByExampleExecutor<Person> {
}
class PersonService {
@Autowired PersonRepository personRepository;
List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
Original Pull Request: #301
This commit is contained in:
committed by
Christoph Strobl
parent
2be6a3b3f7
commit
a3f876f926
@@ -35,6 +35,8 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class RedisKeyValueTemplate extends KeyValueTemplate {
|
||||
|
||||
private final RedisKeyValueAdapter adapter;
|
||||
|
||||
/**
|
||||
* Create new {@link RedisKeyValueTemplate}.
|
||||
*
|
||||
@@ -43,6 +45,15 @@ public class RedisKeyValueTemplate extends KeyValueTemplate {
|
||||
*/
|
||||
public RedisKeyValueTemplate(RedisKeyValueAdapter adapter, RedisMappingContext mappingContext) {
|
||||
super(adapter, mappingContext);
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link RedisKeyValueAdapter}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RedisKeyValueAdapter getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -54,6 +65,7 @@ public class RedisKeyValueTemplate extends KeyValueTemplate {
|
||||
return (RedisMappingContext) super.getMappingContext();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve entities by resolving their {@literal id}s and converting them into required type. <br />
|
||||
* The callback provides either a single {@literal id} or an {@link Iterable} of {@literal id}s, used for retrieving
|
||||
|
||||
@@ -159,20 +159,23 @@ class RedisQueryEngine extends QueryEngine<RedisKeyValueAdapter, RedisOperationC
|
||||
@Override
|
||||
public long count(RedisOperationChain criteria, String keyspace) {
|
||||
|
||||
if (criteria == null) {
|
||||
if (criteria == null || criteria.isEmpty()) {
|
||||
return this.getAdapter().count(keyspace);
|
||||
}
|
||||
|
||||
return this.getAdapter().execute(connection -> {
|
||||
|
||||
String key = keyspace + ":";
|
||||
byte[][] keys = new byte[criteria.getSismember().size()][];
|
||||
int i = 0;
|
||||
for (Object o : criteria.getSismember()) {
|
||||
keys[i] = getAdapter().getConverter().getConversionService().convert(key + o, byte[].class);
|
||||
long result = 0;
|
||||
|
||||
if (!criteria.getOrSismember().isEmpty()) {
|
||||
result += connection.sUnion(keys(keyspace + ":", criteria.getOrSismember())).size();
|
||||
}
|
||||
|
||||
return (long) connection.sInter(keys).size();
|
||||
if (!criteria.getSismember().isEmpty()) {
|
||||
result += connection.sInter(keys(keyspace + ":", criteria.getSismember())).size();
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,8 @@ package org.springframework.data.redis.core.convert;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -990,6 +981,16 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
return this.mappingContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.convert.RedisConverter#getIndexResolver()
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public IndexResolver getIndexResolver() {
|
||||
return this.indexResolver;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityConverter#getConversionService()
|
||||
|
||||
@@ -19,11 +19,13 @@ import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Redis specific {@link EntityConverter}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public interface RedisConverter
|
||||
@@ -35,4 +37,11 @@ public interface RedisConverter
|
||||
*/
|
||||
@Override
|
||||
RedisMappingContext getMappingContext();
|
||||
|
||||
/**
|
||||
* @return the configured {@link IndexResolver}, may be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
IndexResolver getIndexResolver();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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.redis.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher.MatchMode;
|
||||
import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer;
|
||||
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.redis.core.convert.IndexResolver;
|
||||
import org.springframework.data.redis.core.convert.IndexedData;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentProperty;
|
||||
import org.springframework.data.support.ExampleMatcherAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Mapper for Query-by-Example examples to an actual query.
|
||||
* <p/>
|
||||
* This mapper creates a {@link RedisOperationChain} for a given {@link Example} considering exact matches,
|
||||
* {@link PropertyValueTransformer value transformations} and {@link MatchMode} for indexed simple and nested type
|
||||
* properties. {@link java.util.Map} and {@link java.util.Collection} properties are not considered.
|
||||
* <p/>
|
||||
* Example matching is limited to case-sensitive and exact matches only.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ExampleQueryMapper {
|
||||
|
||||
private final Set<StringMatcher> SUPPORTED_MATCHERS = EnumSet.of(StringMatcher.DEFAULT, StringMatcher.EXACT);
|
||||
|
||||
private final MappingContext<RedisPersistentEntity<?>, RedisPersistentProperty> mappingContext;
|
||||
private final IndexResolver indexResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExampleQueryMapper} given {@link MappingContext} and {@link IndexResolver}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param indexResolver must not be {@literal null}.
|
||||
*/
|
||||
public ExampleQueryMapper(MappingContext<RedisPersistentEntity<?>, RedisPersistentProperty> mappingContext,
|
||||
IndexResolver indexResolver) {
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!");
|
||||
Assert.notNull(indexResolver, "IndexResolver must not be null!");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a mapped {@link RedisOperationChain} to query secondary indexes given {@link Example}.
|
||||
*
|
||||
* @param example must not be {@literal null}.
|
||||
* @return the mapped {@link RedisOperationChain}.
|
||||
*/
|
||||
public RedisOperationChain getMappedExample(Example<?> example) {
|
||||
|
||||
RedisOperationChain chain = new RedisOperationChain();
|
||||
|
||||
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
|
||||
|
||||
applyPropertySpecs("", example.getProbe(), mappingContext.getRequiredPersistentEntity(example.getProbeType()),
|
||||
matcherAccessor, example.getMatcher().getMatchMode(), chain);
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
private void applyPropertySpecs(String path, @Nullable Object probe, RedisPersistentEntity<?> persistentEntity,
|
||||
ExampleMatcherAccessor exampleSpecAccessor, MatchMode matchMode, RedisOperationChain chain) {
|
||||
|
||||
if (probe == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(probe);
|
||||
|
||||
Set<IndexedData> indexedData = getIndexedData(path, probe, persistentEntity);
|
||||
Set<String> indexNames = indexedData.stream().map(IndexedData::getIndexName).distinct().collect(Collectors.toSet());
|
||||
|
||||
persistentEntity.forEach(property -> {
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String propertyPath = StringUtils.hasText(path) ? path + "." + property.getName() : property.getName();
|
||||
|
||||
if (exampleSpecAccessor.isIgnoredPath(propertyPath) || property.isCollectionLike() || property.isMap()) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyPropertySpec(propertyPath, indexNames::contains, exampleSpecAccessor, propertyAccessor, property, matchMode,
|
||||
chain);
|
||||
});
|
||||
}
|
||||
|
||||
private void applyPropertySpec(String path, Predicate<String> hasIndex, ExampleMatcherAccessor exampleSpecAccessor,
|
||||
PersistentPropertyAccessor propertyAccessor, RedisPersistentProperty property, MatchMode matchMode,
|
||||
RedisOperationChain chain) {
|
||||
|
||||
StringMatcher stringMatcher = exampleSpecAccessor.getDefaultStringMatcher();
|
||||
boolean ignoreCase = exampleSpecAccessor.isIgnoreCaseEnabled();
|
||||
Object value = propertyAccessor.getProperty(property);
|
||||
|
||||
if (exampleSpecAccessor.hasPropertySpecifiers()) {
|
||||
stringMatcher = exampleSpecAccessor.getStringMatcherForPath(path);
|
||||
ignoreCase = exampleSpecAccessor.isIgnoreCaseForPath(path);
|
||||
}
|
||||
|
||||
if (ignoreCase) {
|
||||
throw new InvalidDataAccessApiUsageException("Redis Query-by-Example supports only case-sensitive matching.");
|
||||
}
|
||||
|
||||
if (!SUPPORTED_MATCHERS.contains(stringMatcher)) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Redis Query-by-Example does not support string matcher %s. Supported matchers are: %s.",
|
||||
stringMatcher, SUPPORTED_MATCHERS));
|
||||
}
|
||||
|
||||
if (exampleSpecAccessor.hasPropertySpecifier(path)) {
|
||||
|
||||
PropertyValueTransformer valueTransformer = exampleSpecAccessor.getValueTransformerForPath(path);
|
||||
value = valueTransformer.apply(Optional.ofNullable(value)).orElse(null);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.isEntity()) {
|
||||
applyPropertySpecs(path, value, mappingContext.getRequiredPersistentEntity(property), exampleSpecAccessor,
|
||||
matchMode, chain);
|
||||
} else {
|
||||
|
||||
if (matchMode == MatchMode.ALL) {
|
||||
if (hasIndex.test(path)) {
|
||||
chain.sismember(path, value);
|
||||
}
|
||||
} else {
|
||||
chain.orSismember(path, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<IndexedData> getIndexedData(String path, Object probe, RedisPersistentEntity<?> persistentEntity) {
|
||||
|
||||
String keySpace = persistentEntity.getKeySpace();
|
||||
return keySpace == null ? Collections.emptySet()
|
||||
: indexResolver.resolveIndexesFor(persistentEntity.getKeySpace(), path, persistentEntity.getTypeInformation(),
|
||||
probe);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.springframework.util.Assert;
|
||||
* Simple set of operations required to run queries against Redis.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class RedisOperationChain {
|
||||
@@ -42,6 +43,10 @@ public class RedisOperationChain {
|
||||
|
||||
private @Nullable NearPath near;
|
||||
|
||||
public boolean isEmpty() {
|
||||
return near == null && sismember.isEmpty() && orSismember.isEmpty();
|
||||
}
|
||||
|
||||
public void sismember(String path, Object value) {
|
||||
sismember(new PathAndValue(path, value));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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.redis.repository.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.data.redis.core.RedisKeyValueTemplate;
|
||||
import org.springframework.data.redis.core.convert.IndexResolver;
|
||||
import org.springframework.data.redis.core.convert.PathIndexResolver;
|
||||
import org.springframework.data.redis.core.convert.RedisConverter;
|
||||
import org.springframework.data.redis.repository.query.ExampleQueryMapper;
|
||||
import org.springframework.data.redis.repository.query.RedisOperationChain;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Repository fragment implementing Redis {@link QueryByExampleExecutor Query-by-Example} operations.
|
||||
* <p/>
|
||||
* This executor uses {@link ExampleQueryMapper} to map {@link Example}s into {@link KeyValueQuery} to execute its query
|
||||
* methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class QueryByExampleRedisExecutor<T> implements QueryByExampleExecutor<T> {
|
||||
|
||||
private final EntityInformation<T, ?> entityInformation;
|
||||
private final RedisKeyValueTemplate keyValueTemplate;
|
||||
private final ExampleQueryMapper mapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link QueryByExampleRedisExecutor} given {@link EntityInformation} and {@link RedisKeyValueTemplate}.
|
||||
* This constructor uses the configured {@link IndexResolver} from the converter.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param keyValueTemplate must not be {@literal null}.
|
||||
*/
|
||||
public QueryByExampleRedisExecutor(EntityInformation<T, ?> entityInformation,
|
||||
RedisKeyValueTemplate keyValueTemplate) {
|
||||
|
||||
Assert.notNull(entityInformation, "EntityInformation must not be null!");
|
||||
Assert.notNull(keyValueTemplate, "RedisKeyValueTemplate must not be null!");
|
||||
|
||||
this.entityInformation = entityInformation;
|
||||
this.keyValueTemplate = keyValueTemplate;
|
||||
|
||||
RedisConverter converter = keyValueTemplate.getAdapter().getConverter();
|
||||
IndexResolver indexResolver = converter.getIndexResolver();
|
||||
|
||||
this.mapper = new ExampleQueryMapper(keyValueTemplate.getMappingContext(),
|
||||
indexResolver != null ? indexResolver : new PathIndexResolver(converter.getMappingContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link QueryByExampleRedisExecutor} given {@link EntityInformation} and {@link RedisKeyValueTemplate}.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param keyValueTemplate must not be {@literal null}.
|
||||
*/
|
||||
public QueryByExampleRedisExecutor(EntityInformation<T, ?> entityInformation, RedisKeyValueTemplate keyValueTemplate,
|
||||
IndexResolver indexResolver) {
|
||||
|
||||
Assert.notNull(entityInformation, "EntityInformation must not be null!");
|
||||
Assert.notNull(keyValueTemplate, "RedisKeyValueTemplate must not be null!");
|
||||
Assert.notNull(indexResolver, "IndexResolver must not be null!");
|
||||
|
||||
this.entityInformation = entityInformation;
|
||||
this.keyValueTemplate = keyValueTemplate;
|
||||
|
||||
this.mapper = new ExampleQueryMapper(keyValueTemplate.getMappingContext(),
|
||||
new PathIndexResolver(keyValueTemplate.getMappingContext()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> Optional<S> findOne(Example<S> example) {
|
||||
|
||||
RedisOperationChain operationChain = getQuery(example);
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = new KeyValueQuery<>(operationChain);
|
||||
Iterable<T> result = keyValueTemplate.find(query.limit(1), entityInformation.getJavaType());
|
||||
Iterator<T> iterator = result.iterator();
|
||||
|
||||
return iterator.hasNext() ? Optional.of((S) iterator.next()) : Optional.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example) {
|
||||
|
||||
RedisOperationChain operationChain = getQuery(example);
|
||||
|
||||
return (Iterable<S>) keyValueTemplate.find(new KeyValueQuery<>(operationChain), entityInformation.getJavaType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
|
||||
throw new UnsupportedOperationException("Ordering is not supported");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
|
||||
RedisOperationChain operationChain = getQuery(example);
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = new KeyValueQuery<>(operationChain);
|
||||
Iterable<T> result = keyValueTemplate.find(
|
||||
query.orderBy(pageable.getSort()).skip(pageable.getOffset()).limit(pageable.getPageSize()),
|
||||
entityInformation.getJavaType());
|
||||
|
||||
long count = operationChain.isEmpty() ? keyValueTemplate.count(entityInformation.getJavaType())
|
||||
: keyValueTemplate.count(query, entityInformation.getJavaType());
|
||||
|
||||
List<S> list = new ArrayList<>();
|
||||
for (T t : result) {
|
||||
list.add((S) t);
|
||||
}
|
||||
|
||||
return new PageImpl<>(list, pageable, count);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> long count(Example<S> example) {
|
||||
|
||||
RedisOperationChain operationChain = getQuery(example);
|
||||
|
||||
return keyValueTemplate.count(new KeyValueQuery<>(operationChain), entityInformation.getJavaType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T> boolean exists(Example<S> example) {
|
||||
return count(example) > 0;
|
||||
}
|
||||
|
||||
private <S extends T> RedisOperationChain getQuery(Example<S> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return mapper.getMappedExample(example);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,16 @@ package org.springframework.data.redis.repository.support;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
|
||||
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
|
||||
import org.springframework.data.redis.repository.core.MappingRedisEntityInformation;
|
||||
import org.springframework.data.redis.repository.query.RedisQueryCreator;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
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.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
|
||||
@@ -70,6 +75,28 @@ public class RedisRepositoryFactory extends KeyValueRepositoryFactory {
|
||||
this.operations = keyValueOperations;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryFragments(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
|
||||
|
||||
RepositoryFragments fragments = RepositoryFragments.empty();
|
||||
|
||||
if (QueryByExampleExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
|
||||
|
||||
RedisMappingContext mappingContext = (RedisMappingContext) this.operations.getMappingContext();
|
||||
RedisPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(metadata.getDomainType());
|
||||
MappingRedisEntityInformation<?, ?> entityInformation = new MappingRedisEntityInformation<>(persistentEntity);
|
||||
|
||||
fragments = fragments.append(RepositoryFragment.implemented(QueryByExampleExecutor.class,
|
||||
getTargetRepositoryViaReflection(QueryByExampleRedisExecutor.class, entityInformation, operations)));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory#getEntityInformation(java.lang.Class)
|
||||
|
||||
Reference in New Issue
Block a user