3

The context of this question is within spring-boot, using spring-data-jpa and hibernate.

A colleague wrote an @Service and annotated the service method with @Transactional. The service method loads an entity, and subsequently hits a one-to-many lazily loaded collection (fetch = FetchType.LAZY). The service method is invoked by some custom delegator, which i will come back to. This works fine when invoked from a @RestController endpoint.

When i invoked the service from a camel route (again via the custom delegator) it barfed with a lazy initialization exception.

On digging, found that the service implements an interface, the custom delegator looks up the service (it is injected so has proper proxy) and calls a method on the interface which is actually a java-8 default method. This default-method then locally calls the @Transactional method.

So there's the problem :- this is a LOCAL method call so the aspecting/proxy-ing of the @Transactional annotation is not done (we use aspectJAutoProxy) so the method is NOT invoked within a transaction, so the lazy-loading SHOULD fail. And to double-check, also tried it via an @Scheduled annotation: same behaviour. Barfs like it should.

My Question: So why does it work when called from the @RestController? This is driving me nuts!

There is no transactional annotation on the rest controller endpoint.

I added some debug code to the service using TransactionSynchronizationManager.isActualTransactionActive() and it shows that in no case is there a transaction, even when being called via the controller endpoint.

So why does the lazy loading work when being called from the controller? I dumped all SQL and at no points are the lazy-collection already loaded, so they are not in any hibernate cache.

I remember reading once that lazy loading was a hint, not a command, but still... why does it work in that one case?

Manuel
  • 3,828
  • 6
  • 33
  • 48
Richard
  • 353
  • 1
  • 2
  • 15
  • are u using Open session in View (OSIV)? – ali akbar azizkhani Mar 22 '17 at 19:44
  • thanks Ali, not using OSIV. There is a filter that loads authenticated user specified in a header from the database, but that transaction closes before rest controller code executes. And to verify, TransactionSynchronisationManager.isActualTransactionActive() in the service says false. – Richard Mar 22 '17 at 20:57
  • did you find an answer Richard? It bugs me too. I have a dao layer where i load stuff and when I iterate through that in my controller it lazy loads instead of failing (which i want) – Mario B Oct 27 '17 at 09:26
  • Sorry Mario, have never found the time to solve this. And all that guff in original question about java8 default methods had nothing to do with. I have subsequently encountered the same problem in the simplest possible case: lazy-loading works via a rest controller when it shouldn't; and it doesn't work via other code paths (which is good). – Richard Oct 30 '17 at 05:24
  • @MarioB answer found! – Richard Jun 14 '18 at 02:15

2 Answers2

1

When your method annotate to transactional hibernate session close after return method , if object that return from method have lazy , lazy property not load and you get exception that session close. You can use fetch in query or using OSIV

ali akbar azizkhani
  • 2,213
  • 5
  • 31
  • 48
  • Thanks Ali i understand that. i'm not asking why lazily-loading is failing. i'm asking why it is working from one specific code path (via spring-rest-controller) where as far as i can tell, it shouldn't work. – Richard Mar 23 '17 at 23:44
1

after being perplexed by this on many occasions, have stumbled across the answer:

sprint-boot is doing an open-entity-manager-in-view behind our backs via the OpenEntityManagerInView interceptor. Had no idea this was going on.

See this excellent answer from Vlad Mihalcea https://stackoverflow.com/a/48222934/208687

Richard
  • 353
  • 1
  • 2
  • 15