Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transactional and Stream in Spring

Tags:

spring

java-8

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.

like image 526
Thomas Banderas Avatar asked Jan 28 '23 13:01

Thomas Banderas


1 Answers

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);
           }
   }
}
like image 157
Dovmo Avatar answered Feb 08 '23 14:02

Dovmo