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.
This commit is contained in:
Oliver Gierke
2016-12-14 15:00:39 +01:00
parent 343f5c7228
commit 1a54d065bd
2 changed files with 28 additions and 1 deletions

View File

@@ -27,10 +27,12 @@ import javax.persistence.StoredProcedureQuery;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -323,6 +325,8 @@ public abstract class JpaQueryExecution {
*/
static class StreamExecution extends JpaQueryExecution {
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.";
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
@@ -330,6 +334,10 @@ public abstract class JpaQueryExecution {
@Override
protected Object doExecute(final AbstractJpaQuery query, Object[] values) {
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive()) {
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
}
Query jpaQuery = query.createQuery(values);
PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(query.getEntityManager());
CloseableIterator<Object> iter = persistenceProvider.executeQueryWithResultStream(jpaQuery);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2015 the original author or authors.
* Copyright 2008-2016 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.
@@ -22,10 +22,12 @@ import static org.springframework.data.domain.Sort.Direction.*;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
@@ -37,6 +39,7 @@ import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -68,6 +71,13 @@ public class UserRepositoryFinderTests {
oliver = userRepository.save(new User("Oliver August", "Matthews", "oliver@dmband.com"));
}
@After
public void clearUp() {
userRepository.deleteAll();
roleRepository.deleteAll();
}
/**
* Tests creation of a simple query.
*/
@@ -234,4 +244,13 @@ public class UserRepositoryFinderTests {
public void executesQueryWithProjectionContainingReferenceToPluralAttribute() {
assertThat(userRepository.findRolesAndFirstnameBy(), is(notNullValue()));
}
/**
* @see DATAJPA-1023, DATACMNS-959
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void rejectsStreamExecutionIfNoSurroundingTransactionActive() {
userRepository.findAllByCustomQueryAndStream();
}
}