DATAMONGO-1165 - Add support for Java 8 Stream as return type for repository methods.
Added support for a MongoDB Cursor backed Iterator that allows the usage of a Java 8 Stream at the repository level. Original pull request: #274.
This commit is contained in:
committed by
Oliver Gierke
parent
a86d704bec
commit
d4f1ef8704
@@ -33,8 +33,10 @@ import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
|
||||
import com.mongodb.CommandResult;
|
||||
import com.mongodb.Cursor;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -165,6 +167,20 @@ public interface MongoOperations {
|
||||
@Deprecated
|
||||
<T> T executeInSession(DbCallback<T> action);
|
||||
|
||||
/**
|
||||
* Executes the given {@link Query} on the entity collection of the specified {@code entityType} backed by a Mongo DB
|
||||
* {@link Cursor}.
|
||||
* <p>
|
||||
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed.
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query
|
||||
* @param entityType
|
||||
* @return
|
||||
* @since 1.7
|
||||
*/
|
||||
<T> CloseableIterator<T> executeAsStream(Query query, Class<T> entityType);
|
||||
|
||||
/**
|
||||
* Create an uncapped collection with a name based on the provided entity class.
|
||||
*
|
||||
|
||||
@@ -95,6 +95,8 @@ import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.mongodb.util.CloseableIterator;
|
||||
import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.jca.cci.core.ConnectionCallback;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -106,6 +108,7 @@ import org.springframework.util.StringUtils;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.Bytes;
|
||||
import com.mongodb.CommandResult;
|
||||
import com.mongodb.Cursor;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
@@ -315,6 +318,31 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
return this.mongoConverter;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> CloseableIterator<T> executeAsStream(final Query query, final Class<T> entityType) {
|
||||
|
||||
return execute(entityType, new CollectionCallback<CloseableIterator<T>>() {
|
||||
|
||||
@Override
|
||||
public CloseableIterator<T> doInCollection(DBCollection collection) throws MongoException, DataAccessException {
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
|
||||
|
||||
DBObject mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity);
|
||||
DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
|
||||
|
||||
DBCursor cursor = collection.find(mappedQuery, mappedFields);
|
||||
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType);
|
||||
|
||||
return new CloseableIterableCusorAdapter<T>(cursor, exceptionTranslator, readCallback);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public String getCollectionName(Class<?> entityClass) {
|
||||
return this.determineCollectionName(entityClass);
|
||||
}
|
||||
@@ -2122,9 +2150,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
* Simple internal callback to allow operations on a {@link DBObject}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
|
||||
private interface DbObjectCallback<T> {
|
||||
static interface DbObjectCallback<T> {
|
||||
|
||||
T doWith(DBObject object);
|
||||
}
|
||||
@@ -2287,4 +2316,76 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}.
|
||||
*
|
||||
* @since 1.7
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
static class CloseableIterableCusorAdapter<T> implements CloseableIterator<T> {
|
||||
|
||||
private volatile Cursor cursor;
|
||||
private PersistenceExceptionTranslator exceptionTranslator;
|
||||
private DbObjectCallback<T> objectReadCallback;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloseableIterableCusorAdapter} backed by the given {@link Cursor}.
|
||||
*
|
||||
* @param cursor
|
||||
* @param exceptionTranslator
|
||||
* @param objectReadCallback
|
||||
*/
|
||||
public CloseableIterableCusorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator,
|
||||
DbObjectCallback<T> objectReadCallback) {
|
||||
|
||||
this.cursor = cursor;
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
this.objectReadCallback = objectReadCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
||||
if (cursor == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return cursor.hasNext();
|
||||
} catch (RuntimeException ex) {
|
||||
throw exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
|
||||
if (cursor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
DBObject item = cursor.next();
|
||||
T converted = objectReadCallback.doWith(item);
|
||||
return converted;
|
||||
} catch (RuntimeException ex) {
|
||||
throw exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
Cursor c = cursor;
|
||||
try {
|
||||
c.close();
|
||||
} catch (RuntimeException ex) {
|
||||
throw exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
} finally {
|
||||
cursor = null;
|
||||
exceptionTranslator = null;
|
||||
objectReadCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2014 the original author or authors.
|
||||
* Copyright 2010-2015 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.
|
||||
@@ -17,9 +17,10 @@ package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
@@ -34,6 +35,8 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.util.CloseableIteratorDisposingRunnable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -48,8 +51,6 @@ import com.mongodb.WriteResult;
|
||||
*/
|
||||
public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
|
||||
|
||||
private final MongoQueryMethod method;
|
||||
private final MongoOperations operations;
|
||||
|
||||
@@ -87,7 +88,9 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
applyQueryMetaAttributesWhenPresent(query);
|
||||
|
||||
if (isDeleteQuery()) {
|
||||
if (method.isStreamQuery()) {
|
||||
return new StreamExecution().execute(query);
|
||||
} else if (isDeleteQuery()) {
|
||||
return new DeleteExecution().execute(query);
|
||||
} else if (method.isGeoNearQuery() && method.isPageQuery()) {
|
||||
|
||||
@@ -415,4 +418,26 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
return writeResult != null ? writeResult.getN() : 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
* @since 1.7
|
||||
*/
|
||||
final class StreamExecution extends Execution {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query)
|
||||
*/
|
||||
@Override
|
||||
Object execute(Query query) {
|
||||
|
||||
Class<?> entityType = getQueryMethod().getEntityInformation().getJavaType();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
CloseableIterator<Object> result = (CloseableIterator<Object>) operations.executeAsStream(query, entityType);
|
||||
Spliterator<Object> spliterator = Spliterators.spliteratorUnknownSize(result, Spliterator.NONNULL);
|
||||
|
||||
return StreamSupport.stream(spliterator, false).onClose(new CloseableIteratorDisposingRunnable(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2014 the original author or authors.
|
||||
* Copyright 2011-2015 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.
|
||||
@@ -65,7 +65,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
|
||||
@Autowired MongoOperations operations;
|
||||
|
||||
Person dave, oliver, carter, boyd, stefan, leroi, alicia;
|
||||
protected Person dave, oliver, carter, boyd, stefan, leroi, alicia;
|
||||
QPerson person;
|
||||
|
||||
List<Person> all;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2014 the original author or authors.
|
||||
* Copyright 2010-2015 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -320,4 +321,10 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
|
||||
@Query("{ ?0 : ?1 }")
|
||||
List<Person> findByKeyValue(String key, String value);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1165
|
||||
*/
|
||||
@Query("{firstname:{$in:?0}}")
|
||||
Stream<Person> findByCustomQueryWithStreamingCursorByFirstnames(List<String> firstnames);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2013 the original author or authors.
|
||||
* Copyright 2010-2015 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.
|
||||
@@ -15,6 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
@@ -24,4 +33,21 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class PersonRepositoryIntegrationTests extends AbstractPersonRepositoryIntegrationTests {}
|
||||
public class PersonRepositoryIntegrationTests extends AbstractPersonRepositoryIntegrationTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1165
|
||||
*/
|
||||
@Test
|
||||
public void shouldAllowReturningJava8StreamInCustomQuery() throws Exception {
|
||||
|
||||
Stream<Person> result = repository.findByCustomQueryWithStreamingCursorByFirstnames(Arrays.asList("Dave"));
|
||||
|
||||
try {
|
||||
List<Person> readPersons = result.collect(Collectors.<Person> toList());
|
||||
assertThat(readPersons, hasItems(dave));
|
||||
} finally {
|
||||
result.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user