DATAES-594 - Polishing.

This commit is contained in:
P.J.Meisch 2019-06-20 12:11:19 +02:00
parent d22b12874d
commit 6a3042c437
3 changed files with 87 additions and 24 deletions

View File

@ -32,10 +32,15 @@ import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean @NoRepositoryBean
public interface ElasticsearchRepository<T, ID> extends ElasticsearchCrudRepository<T, ID> { public interface ElasticsearchRepository<T, ID> extends ElasticsearchCrudRepository<T, ID> {
<S extends T> S indexWithoutRefresh(S entity);
<S extends T> S index(S entity); <S extends T> S index(S entity);
/**
* This method is intended to be used when many single inserts must be made that cannot be aggregated to be inserted
* with {@link #saveAll(Iterable)}. This might lead to a temporary inconsistent state until {@link #refresh()} is
* called.
*/
<S extends T> S indexWithoutRefresh(S entity);
Iterable<T> search(QueryBuilder query); Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable); Page<T> search(QueryBuilder query, Pageable pageable);

View File

@ -61,6 +61,7 @@ import org.springframework.util.Assert;
public abstract class AbstractElasticsearchRepository<T, ID> implements ElasticsearchRepository<T, ID> { public abstract class AbstractElasticsearchRepository<T, ID> implements ElasticsearchRepository<T, ID> {
static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchRepository.class); static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchRepository.class);
protected ElasticsearchOperations elasticsearchOperations; protected ElasticsearchOperations elasticsearchOperations;
protected Class<T> entityClass; protected Class<T> entityClass;
protected ElasticsearchEntityInformation<T, ID> entityInformation; protected ElasticsearchEntityInformation<T, ID> entityInformation;
@ -76,6 +77,7 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
public AbstractElasticsearchRepository(ElasticsearchEntityInformation<T, ID> metadata, public AbstractElasticsearchRepository(ElasticsearchEntityInformation<T, ID> metadata,
ElasticsearchOperations elasticsearchOperations) { ElasticsearchOperations elasticsearchOperations) {
this(elasticsearchOperations); this(elasticsearchOperations);
Assert.notNull(metadata, "ElasticsearchEntityInformation must not be null!"); Assert.notNull(metadata, "ElasticsearchEntityInformation must not be null!");
@ -93,19 +95,23 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
} }
private void createIndex() { private void createIndex() {
elasticsearchOperations.createIndex(getEntityClass()); elasticsearchOperations.createIndex(getEntityClass());
} }
private void putMapping() { private void putMapping() {
elasticsearchOperations.putMapping(getEntityClass()); elasticsearchOperations.putMapping(getEntityClass());
} }
private boolean createIndexAndMapping() { private boolean createIndexAndMapping() {
return elasticsearchOperations.getPersistentEntityFor(getEntityClass()).isCreateIndexAndMapping(); return elasticsearchOperations.getPersistentEntityFor(getEntityClass()).isCreateIndexAndMapping();
} }
@Override @Override
public Optional<T> findById(ID id) { public Optional<T> findById(ID id) {
GetQuery query = new GetQuery(); GetQuery query = new GetQuery();
query.setId(stringIdRepresentation(id)); query.setId(stringIdRepresentation(id));
return Optional.ofNullable(elasticsearchOperations.queryForObject(query, getEntityClass())); return Optional.ofNullable(elasticsearchOperations.queryForObject(query, getEntityClass()));
@ -113,134 +119,167 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
@Override @Override
public Iterable<T> findAll() { public Iterable<T> findAll() {
int itemCount = (int) this.count(); int itemCount = (int) this.count();
if (itemCount == 0) { if (itemCount == 0) {
return new PageImpl<>(Collections.<T> emptyList()); return new PageImpl<>(Collections.<T> emptyList());
} }
return this.findAll(PageRequest.of(0, Math.max(1, itemCount))); return this.findAll(PageRequest.of(0, Math.max(1, itemCount)));
} }
@Override @Override
public Page<T> findAll(Pageable pageable) { public Page<T> findAll(Pageable pageable) {
SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withPageable(pageable).build(); SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withPageable(pageable).build();
return elasticsearchOperations.queryForPage(query, getEntityClass()); return elasticsearchOperations.queryForPage(query, getEntityClass());
} }
@Override @Override
public Iterable<T> findAll(Sort sort) { public Iterable<T> findAll(Sort sort) {
int itemCount = (int) this.count(); int itemCount = (int) this.count();
if (itemCount == 0) { if (itemCount == 0) {
return new PageImpl<>(Collections.<T> emptyList()); return new PageImpl<>(Collections.<T> emptyList());
} }
SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withPageable(PageRequest.of(0, itemCount, sort)).build(); .withPageable(PageRequest.of(0, itemCount, sort)).build();
return elasticsearchOperations.queryForPage(query, getEntityClass()); return elasticsearchOperations.queryForPage(query, getEntityClass());
} }
@Override @Override
public Iterable<T> findAllById(Iterable<ID> ids) { public Iterable<T> findAllById(Iterable<ID> ids) {
Assert.notNull(ids, "ids can't be null."); Assert.notNull(ids, "ids can't be null.");
SearchQuery query = new NativeSearchQueryBuilder().withIds(stringIdsRepresentation(ids)).build(); SearchQuery query = new NativeSearchQueryBuilder().withIds(stringIdsRepresentation(ids)).build();
return elasticsearchOperations.multiGet(query, getEntityClass()); return elasticsearchOperations.multiGet(query, getEntityClass());
} }
@Override @Override
public long count() { public long count() {
SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
return elasticsearchOperations.count(query, getEntityClass()); return elasticsearchOperations.count(query, getEntityClass());
} }
@Override @Override
public <S extends T> S save(S entity) { public <S extends T> S save(S entity) {
Assert.notNull(entity, "Cannot save 'null' entity."); Assert.notNull(entity, "Cannot save 'null' entity.");
elasticsearchOperations.index(createIndexQuery(entity)); elasticsearchOperations.index(createIndexQuery(entity));
elasticsearchOperations.refresh(entityInformation.getIndexName()); elasticsearchOperations.refresh(entityInformation.getIndexName());
return entity; return entity;
} }
public <S extends T> List<S> save(List<S> entities) { public <S extends T> List<S> save(List<S> entities) {
Assert.notNull(entities, "Cannot insert 'null' as a List."); Assert.notNull(entities, "Cannot insert 'null' as a List.");
Assert.notEmpty(entities, "Cannot insert empty List."); Assert.notEmpty(entities, "Cannot insert empty List.");
List<IndexQuery> queries = new ArrayList<>(); List<IndexQuery> queries = new ArrayList<>();
for (S s : entities) { for (S s : entities) {
queries.add(createIndexQuery(s)); queries.add(createIndexQuery(s));
} }
elasticsearchOperations.bulkIndex(queries); elasticsearchOperations.bulkIndex(queries);
elasticsearchOperations.refresh(entityInformation.getIndexName()); elasticsearchOperations.refresh(entityInformation.getIndexName());
return entities; return entities;
} }
@Override @Override
public <S extends T> S index(S entity) { public <S extends T> S index(S entity) {
return save(entity); return save(entity);
} }
/**
* This method might lead to a temporary inconsistent state until
* {@link org.springframework.data.elasticsearch.repository.ElasticsearchRepository#refresh() refresh} is called.
*/
@Override @Override
public <S extends T> S indexWithoutRefresh(S entity) { public <S extends T> S indexWithoutRefresh(S entity) {
Assert.notNull(entity, "Cannot save 'null' entity."); Assert.notNull(entity, "Cannot save 'null' entity.");
elasticsearchOperations.index(createIndexQuery(entity)); elasticsearchOperations.index(createIndexQuery(entity));
return entity; return entity;
} }
@Override @Override
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) { public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Cannot insert 'null' as a List."); Assert.notNull(entities, "Cannot insert 'null' as a List.");
List<IndexQuery> queries = new ArrayList<>(); List<IndexQuery> queries = new ArrayList<>();
for (S s : entities) { for (S s : entities) {
queries.add(createIndexQuery(s)); queries.add(createIndexQuery(s));
} }
elasticsearchOperations.bulkIndex(queries); elasticsearchOperations.bulkIndex(queries);
elasticsearchOperations.refresh(entityInformation.getIndexName()); elasticsearchOperations.refresh(entityInformation.getIndexName());
return entities; return entities;
} }
@Override @Override
public boolean existsById(ID id) { public boolean existsById(ID id) {
return findById(id).isPresent(); return findById(id).isPresent();
} }
@Override @Override
public Iterable<T> search(QueryBuilder query) { public Iterable<T> search(QueryBuilder query) {
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).build(); SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).build();
int count = (int) elasticsearchOperations.count(searchQuery, getEntityClass()); int count = (int) elasticsearchOperations.count(searchQuery, getEntityClass());
if (count == 0) { if (count == 0) {
return new PageImpl<>(Collections.<T> emptyList()); return new PageImpl<>(Collections.<T> emptyList());
} }
searchQuery.setPageable(PageRequest.of(0, count)); searchQuery.setPageable(PageRequest.of(0, count));
return elasticsearchOperations.queryForPage(searchQuery, getEntityClass()); return elasticsearchOperations.queryForPage(searchQuery, getEntityClass());
} }
@Override @Override
public Page<T> search(QueryBuilder query, Pageable pageable) { public Page<T> search(QueryBuilder query, Pageable pageable) {
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).withPageable(pageable).build(); SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).withPageable(pageable).build();
return elasticsearchOperations.queryForPage(searchQuery, getEntityClass()); return elasticsearchOperations.queryForPage(searchQuery, getEntityClass());
} }
@Override @Override
public Page<T> search(SearchQuery query) { public Page<T> search(SearchQuery query) {
return elasticsearchOperations.queryForPage(query, getEntityClass()); return elasticsearchOperations.queryForPage(query, getEntityClass());
} }
@Override @Override
public Page<T> searchSimilar(T entity, String[] fields, Pageable pageable) { public Page<T> searchSimilar(T entity, String[] fields, Pageable pageable) {
Assert.notNull(entity, "Cannot search similar records for 'null'."); Assert.notNull(entity, "Cannot search similar records for 'null'.");
Assert.notNull(pageable, "'pageable' cannot be 'null'"); Assert.notNull(pageable, "'pageable' cannot be 'null'");
MoreLikeThisQuery query = new MoreLikeThisQuery(); MoreLikeThisQuery query = new MoreLikeThisQuery();
query.setId(stringIdRepresentation(extractIdFromBean(entity))); query.setId(stringIdRepresentation(extractIdFromBean(entity)));
query.setPageable(pageable); query.setPageable(pageable);
if (fields != null) { if (fields != null) {
query.addFields(fields); query.addFields(fields);
} }
return elasticsearchOperations.moreLikeThis(query, getEntityClass()); return elasticsearchOperations.moreLikeThis(query, getEntityClass());
} }
@Override @Override
public void deleteById(ID id) { public void deleteById(ID id) {
Assert.notNull(id, "Cannot delete entity with id 'null'."); Assert.notNull(id, "Cannot delete entity with id 'null'.");
elasticsearchOperations.delete(entityInformation.getIndexName(), entityInformation.getType(), elasticsearchOperations.delete(entityInformation.getIndexName(), entityInformation.getType(),
stringIdRepresentation(id)); stringIdRepresentation(id));
elasticsearchOperations.refresh(entityInformation.getIndexName()); elasticsearchOperations.refresh(entityInformation.getIndexName());
@ -248,13 +287,16 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
@Override @Override
public void delete(T entity) { public void delete(T entity) {
Assert.notNull(entity, "Cannot delete 'null' entity."); Assert.notNull(entity, "Cannot delete 'null' entity.");
deleteById(extractIdFromBean(entity)); deleteById(extractIdFromBean(entity));
elasticsearchOperations.refresh(entityInformation.getIndexName()); elasticsearchOperations.refresh(entityInformation.getIndexName());
} }
@Override @Override
public void deleteAll(Iterable<? extends T> entities) { public void deleteAll(Iterable<? extends T> entities) {
Assert.notNull(entities, "Cannot delete 'null' list."); Assert.notNull(entities, "Cannot delete 'null' list.");
for (T entity : entities) { for (T entity : entities) {
delete(entity); delete(entity);
@ -263,6 +305,7 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
@Override @Override
public void deleteAll() { public void deleteAll() {
DeleteQuery deleteQuery = new DeleteQuery(); DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.setQuery(matchAllQuery()); deleteQuery.setQuery(matchAllQuery());
elasticsearchOperations.delete(deleteQuery, getEntityClass()); elasticsearchOperations.delete(deleteQuery, getEntityClass());
@ -271,10 +314,12 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
@Override @Override
public void refresh() { public void refresh() {
elasticsearchOperations.refresh(getEntityClass()); elasticsearchOperations.refresh(getEntityClass());
} }
private IndexQuery createIndexQuery(T entity) { private IndexQuery createIndexQuery(T entity) {
IndexQuery query = new IndexQuery(); IndexQuery query = new IndexQuery();
query.setObject(entity); query.setObject(entity);
query.setId(stringIdRepresentation(extractIdFromBean(entity))); query.setId(stringIdRepresentation(extractIdFromBean(entity)));
@ -285,11 +330,13 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Class<T> resolveReturnedClassFromGenericType() { private Class<T> resolveReturnedClassFromGenericType() {
ParameterizedType parameterizedType = resolveReturnedClassFromGenericType(getClass()); ParameterizedType parameterizedType = resolveReturnedClassFromGenericType(getClass());
return (Class<T>) parameterizedType.getActualTypeArguments()[0]; return (Class<T>) parameterizedType.getActualTypeArguments()[0];
} }
private ParameterizedType resolveReturnedClassFromGenericType(Class<?> clazz) { private ParameterizedType resolveReturnedClassFromGenericType(Class<?> clazz) {
Object genericSuperclass = clazz.getGenericSuperclass(); Object genericSuperclass = clazz.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) { if (genericSuperclass instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
@ -298,11 +345,13 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
return parameterizedType; return parameterizedType;
} }
} }
return resolveReturnedClassFromGenericType(clazz.getSuperclass()); return resolveReturnedClassFromGenericType(clazz.getSuperclass());
} }
@Override @Override
public Class<T> getEntityClass() { public Class<T> getEntityClass() {
if (!isEntityClassSet()) { if (!isEntityClassSet()) {
try { try {
this.entityClass = resolveReturnedClassFromGenericType(); this.entityClass = resolveReturnedClassFromGenericType();
@ -310,20 +359,25 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
throw new InvalidDataAccessApiUsageException("Unable to resolve EntityClass. Please use according setter!", e); throw new InvalidDataAccessApiUsageException("Unable to resolve EntityClass. Please use according setter!", e);
} }
} }
return entityClass; return entityClass;
} }
private boolean isEntityClassSet() { private boolean isEntityClassSet() {
return entityClass != null; return entityClass != null;
} }
public final void setEntityClass(Class<T> entityClass) { public final void setEntityClass(Class<T> entityClass) {
Assert.notNull(entityClass, "EntityClass must not be null."); Assert.notNull(entityClass, "EntityClass must not be null.");
this.entityClass = entityClass; this.entityClass = entityClass;
} }
public final void setElasticsearchOperations(ElasticsearchOperations elasticsearchOperations) { public final void setElasticsearchOperations(ElasticsearchOperations elasticsearchOperations) {
Assert.notNull(elasticsearchOperations, "ElasticsearchOperations must not be null."); Assert.notNull(elasticsearchOperations, "ElasticsearchOperations must not be null.");
this.elasticsearchOperations = elasticsearchOperations; this.elasticsearchOperations = elasticsearchOperations;
} }
@ -332,11 +386,14 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
} }
private List<String> stringIdsRepresentation(Iterable<ID> ids) { private List<String> stringIdsRepresentation(Iterable<ID> ids) {
Assert.notNull(ids, "ids can't be null."); Assert.notNull(ids, "ids can't be null.");
List<String> stringIds = new ArrayList<>(); List<String> stringIds = new ArrayList<>();
for (ID id : ids) { for (ID id : ids) {
stringIds.add(stringIdRepresentation(id)); stringIds.add(stringIdRepresentation(id));
} }
return stringIds; return stringIds;
} }

View File

@ -35,7 +35,6 @@ import org.elasticsearch.action.ActionRequestValidationException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version; import org.springframework.data.annotation.Version;
@ -586,7 +585,9 @@ public class SimpleElasticsearchRepositoryTests {
// then // then
Page<SampleEntity> entities = repository.search(termQuery("id", documentId), PageRequest.of(0, 50)); Page<SampleEntity> entities = repository.search(termQuery("id", documentId), PageRequest.of(0, 50));
assertThat(entities.getTotalElements()).isEqualTo(0L); assertThat(entities.getTotalElements()).isEqualTo(0L);
repository.refresh(); repository.refresh();
entities = repository.search(termQuery("id", documentId), PageRequest.of(0, 50)); entities = repository.search(termQuery("id", documentId), PageRequest.of(0, 50));
assertThat(entities.getTotalElements()).isEqualTo(1L); assertThat(entities.getTotalElements()).isEqualTo(1L);
} }