6

I try to understand why this code doesn't work

In component:

@PostConstruct
public void runAtStart(){

    testStream();
}

@Transactional(readOnly = true)
public void testStream(){
    try(Stream<Person> top10ByFirstName = personRepository.findTop10ByFirstName("Tom")){
        top10ByFirstName.forEach(System.out::println);
    }
}

And repository :

public interface PersonRepository extends JpaRepository<Person, Long> {
    Stream<Person> findTop10ByFirstName(String firstName);
}

I get:

org.springframework.dao.InvalidDataAccessApiUsageException: 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.

Thomas Banderas
  • 1,681
  • 1
  • 21
  • 43
  • 3
    `@Transactional` requires Spring AOP. The default Spring AOP implementation uses standard JDK proxies, which do not work with intra-class calls. This is detailed quite extensively in the [official documentation](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies). If you need AOP semantics within the same class, consider using AspectJ proxies. – manish Aug 20 '18 at 05:48

1 Answers1

9

One key thing about Spring is that many annotated features use proxies to provide the annotation functionality. That is @Transactional, @Cacheable and @Async all rely on Spring detecting those annotations and wrapping those beans in a proxy bean.

That being the case, a proxied method can only be used when invoked on the class and not from within the class. See this about the topic.

Try:

  1. Refactoring and call this @Transactional method from another class in your context, or
  2. By self-autowiring the class into itself and calling the @Transactional method that way.

To demonstrate (1):

public class MyOtherClass {

    @Autowired
    private MyTestStreamClass myTestStreamClass;

    @PostConstruct
    public void runAtStart(){
        // This will invoke the proxied interceptors for `@Transactional`
        myTestStreamClass.testStream();
    }

}

To demonstrate (2):

@Component
public class MyTestStreamClass {

   @Autowired
   private MyTestStreamClass myTestStreamClass;

   @PostConstruct
   public void runAtStart(){
       // This will invoke the proxied interceptors for `@Transactional` since it's self-autowired
       myTestStreamClass.testStream();
   }

   @Transactional(readOnly = true)
   public void testStream(){
       try(Stream<Person> top10ByFirstName = personRepository.findTop10ByFirstName("Tom")){
               top10ByFirstName.forEach(System.out::println);
           }
   }
}
Dovmo
  • 8,121
  • 3
  • 30
  • 44
  • 1
    There is no other clean idea? :P //This working. I try with `((RunAtStart)AopContext.currentProxy()).testStream();` as @manith mentioned, but this gives me:` Invocation of init method failed; nested exception is java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.` – Thomas Banderas Aug 20 '18 at 11:27