Skip to content

Commit 1a54d06

Browse files
committed
DATAJPA-1023 - StreamExceution now rejects non-transactional execution.
StreamExecution now makes use of the newly introduced SurroundingTransactionDetectingMethodInterceptor to find out about whether the transaction is used in code that already has a transaction running. This is necessary to make sure the Stream returned by the method can actually be consumed as the surrounding transaction keeps the transaction open. Related tickets: DATACMNS-959.
1 parent 343f5c7 commit 1a54d06

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java

+8
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import org.springframework.core.convert.ConversionService;
2828
import org.springframework.core.convert.support.ConfigurableConversionService;
2929
import org.springframework.core.convert.support.DefaultConversionService;
30+
import org.springframework.dao.InvalidDataAccessApiUsageException;
3031
import org.springframework.data.domain.Pageable;
3132
import org.springframework.data.domain.Slice;
3233
import org.springframework.data.domain.SliceImpl;
3334
import org.springframework.data.jpa.provider.PersistenceProvider;
35+
import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
3436
import org.springframework.data.repository.query.ParameterAccessor;
3537
import org.springframework.data.repository.query.Parameters;
3638
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -323,13 +325,19 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, Object[] values) {
323325
*/
324326
static class StreamExecution extends JpaQueryExecution {
325327

328+
private static final String NO_SURROUNDING_TRANSACTION = "You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction.";
329+
326330
/*
327331
* (non-Javadoc)
328332
* @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
329333
*/
330334
@Override
331335
protected Object doExecute(final AbstractJpaQuery query, Object[] values) {
332336

337+
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive()) {
338+
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
339+
}
340+
333341
Query jpaQuery = query.createQuery(values);
334342
PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(query.getEntityManager());
335343
CloseableIterator<Object> iter = persistenceProvider.executeQueryWithResultStream(jpaQuery);

src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2015 the original author or authors.
2+
* Copyright 2008-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,10 +22,12 @@
2222
import java.util.Arrays;
2323
import java.util.List;
2424

25+
import org.junit.After;
2526
import org.junit.Before;
2627
import org.junit.Test;
2728
import org.junit.runner.RunWith;
2829
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.dao.InvalidDataAccessApiUsageException;
2931
import org.springframework.data.domain.Page;
3032
import org.springframework.data.domain.PageRequest;
3133
import org.springframework.data.domain.Slice;
@@ -37,6 +39,7 @@
3739
import org.springframework.data.repository.query.QueryLookupStrategy;
3840
import org.springframework.test.context.ContextConfiguration;
3941
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
42+
import org.springframework.transaction.annotation.Propagation;
4043
import org.springframework.transaction.annotation.Transactional;
4144

4245
/**
@@ -68,6 +71,13 @@ public void setUp() {
6871
oliver = userRepository.save(new User("Oliver August", "Matthews", "[email protected]"));
6972
}
7073

74+
@After
75+
public void clearUp() {
76+
77+
userRepository.deleteAll();
78+
roleRepository.deleteAll();
79+
}
80+
7181
/**
7282
* Tests creation of a simple query.
7383
*/
@@ -234,4 +244,13 @@ public void translatesNotContainsToNotMemberOf() {
234244
public void executesQueryWithProjectionContainingReferenceToPluralAttribute() {
235245
assertThat(userRepository.findRolesAndFirstnameBy(), is(notNullValue()));
236246
}
247+
248+
/**
249+
* @see DATAJPA-1023, DATACMNS-959
250+
*/
251+
@Test(expected = InvalidDataAccessApiUsageException.class)
252+
@Transactional(propagation = Propagation.NOT_SUPPORTED)
253+
public void rejectsStreamExecutionIfNoSurroundingTransactionActive() {
254+
userRepository.findAllByCustomQueryAndStream();
255+
}
237256
}

0 commit comments

Comments
 (0)