1

I have a Spring-boot project where I have a service bean with 2 @Transactional annotated methods.

These methods do read-only JPA (hibernated) actions to fetch data from an HSQL file database, using both JPA repositories and lazy loaded getters in entities.

I also have a cli bean that handles commands (Using PicoCLI). From one of these commands I try to call both @Transactional annotated methods, but I get the following error during execution of the second method:

org.hibernate.LazyInitializationException - could not initialize proxy - no Session
        at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
        at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
        at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)
        at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)
        at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
        at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
        at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
        at <mypackage>.SomeImpl.getThings(SomeImpl.java:<linenr>)
...

If I mark the method that calls both @Transactional annotated methods with @Transactional itself, the code seems to work (due to there now only being 1 top level transaction I presume?).

I just want to find out why I cannot start multiple transactions in a single session or why the second transaction doesn't start a new session if there are none.

So my questions are:

  • Does this have to do with how hibernate starts a session, how transactions close sessions or anything related to the HSQL database?
  • Is adding an encompassing transaction the right way to fix the issue or is this just fighting the symptom?
  • What would be the best way to be able to use multiple @Transactional annotated methods from one method?

EDIT: I want to make clear that I don't expose the entities outside of the transactional methods, so on the surface it looks to me like the 2 transactional methods should be working independently from one another.

EDIT2: for more clarification: the transactional methods need to be available in an api and the user of the api should be able to call multiple of these transactional methods, without needing to use transactional annotations and without getting the LazyInitializationException

Api:

public interface SomeApi {
    List<String> getSomeList();
    List<Something> getThings(String somethingGroupName);
}

Implementation:

public class SomeImpl implements SomeApi {

    @Transactional
    public List<String> getSomeList() {
        return ...; //Do jpa stuff to get the list
    }

    @Transactional
    public List<Something> getThings(String somethingGroupName) {
        return ...; //Do other jpa stuff to get the result from the group name
    }
}

Usage by 3rd party (who might not know what transactionality is):

public someMethod(String somethingGroupName) {
    ...

    SomeApi someApi = ...; // Get an implementation of the api in some way

    List<String> someList = someApi.someList();
    if (someList.contains(somethingGroupName) {
        System.out.println(someApi.getThings(somethingGroupName));
    }

    ...
}
Rik Schaaf
  • 983
  • 1
  • 8
  • 30
  • From description above you wnat to use nested transacations? – Mykhailo Moskura Apr 13 '20 at 20:00
  • @MykhailoMoskura I describe above that I am doing that right now. What I wanted to know is if this is the right way to go or if there is a way to start a new session for transactional methods that don't expose their entities, so they shouldn't interfere with eachother. – Rik Schaaf Apr 13 '20 at 23:08
  • @MykhailoMoskura Also, I don't want the burden of whether or not to make their method transactional to fall upon the user of these methods. I want them to be able to use them as is, so it is not preferred to have to mark the calling method as transactional as well if they want to use more than one transactional annotated method (these methods will be available through an API library, so 3rd parties will be involved). – Rik Schaaf Apr 13 '20 at 23:21
  • Due to your edit, I ask, where in your code is the LIE occurring? Inside someMethod? Does the List that you return is an managed one (some implementation of Hibernate list) or a list built inside your API? – Paulo Araújo Apr 15 '20 at 15:04
  • @PauloAraújo getSomeList actually doesn't return a string but a POJO containing said string + another list of strings, I simplified it for the example. It calls a findAll on a certain repository to get an iterable of a certain entity. Then I create a stream using that iterable's spliterator with `StreamSupport`. I map the entity to a stream of a certain POJO using a self-implemented apache collections4 transformer. This transformer will also access one of the entity's one->many relations, to retrieve certain string values. Finally the stream gets turned into a list with Collectors.toList(). – Rik Schaaf Apr 15 '20 at 18:19
  • @PauloAraújo the exception arises during the execution of the second method. Here I find the something group entity by the something group name as optional, resolve if it is present (throw an illegal argument exception if it is not) and get the something entities through a many->many relation, as a set (using `Collections.unmodifyableSet(...)` in the getter). Then I stream this set and map it to a Something POJO (that only contains Strings and UUIDs) using an apache collections4 transformer and then collect it with `Collectors.toList()`. I get the exception on the last line of the 2nd method – Rik Schaaf Apr 15 '20 at 18:31
  • It happens in the last `collect()` of that method – Rik Schaaf Apr 15 '20 at 18:58
  • I think the issue might be related to what I found in this answer to another SO question: https://stackoverflow.com/a/32046337/2877358 – Rik Schaaf Apr 15 '20 at 19:12
  • Your second transformer (on the second method) should get a new reference to the target entity (via refresh) before the terminal operation (collect) and use this refreshed entity to make the actual transformation. Sorry, but it seems you have some dangling entities as a product of the first method, but the lack of code keeps me from giving a better solution. See that the Session probably won't garbage collect the entities, avoiding a performance penalty if you get the entities again into your transformer. – Paulo Araújo Apr 15 '20 at 19:28
  • I found the solution, after looking more thoroughly at the stack trace. The problem was that I didnt set `hibernate.enable_lazy_load_no_trans` to true, so there was no session during the second transaction. I created an answer for this below. Feel free to comment on it, if you believe this isn't the whole story. – Rik Schaaf Apr 15 '20 at 19:45

2 Answers2

0

It seems that you are accessing some not initialized data from your entities after the transactions have ended. In that cases, the persistence provider may throw the lazyinitialization exception.

If you need to retrieve some information not eagerly loaded with the entities, you may use one of two strategies:

  • annotate the calling method also with @Transactional annotation, as you did: it does not start a new transaction for each call, but makes the opened transaction active until your calling method ends, avoiding the exception; or
  • make the called methods load eagerly the required fields USING the JOIN FETCH JPQL idiom.

Transaction boundaries requires some analysis of your scenario. Please, read this answer and search for better books or tutorials to master it. Probably only you will be able to define aptly your requirements.

Paulo Araújo
  • 414
  • 3
  • 6
  • The eager loading doesn't work for me, since the methods don't expose their entities and work independently from each other. It could be that only 1 of the 2 is called, or the other or both (one after the other as described above), so knowing which relations to load eagerly without impacting the performance too much seems guesswork, especially if I add more methods later. – Rik Schaaf Apr 13 '20 at 23:14
  • Right now, every time a command is fired from the cli, it seems that a new session is created and is available until the transaction closes (correct me if this is wrong). Would it be possible to create another session if another transaction is used later, or is there a way to keep the session open after a transaction commits? – Rik Schaaf Apr 13 '20 at 23:17
  • It seems that your approach must be the first one. It will guarantee that no LIE occurs and gives the developer freedom to extract all managed fields to build the response. There is not a one-to-one relation between Session and Transaction, but when the method annotated with transactional ends, the calling transaction is commited (hopefully) and the entities that were managed by the Session should be evicted - losing its managed state and allowing the LIE to occur. Even if the same Session is used in the next transactional methods, the entities evicted may throw LIE. – Paulo Araújo Apr 13 '20 at 23:51
  • Would it be possible to start a new session for the second transaction? – Rik Schaaf Apr 14 '20 at 07:42
  • I am not aware of the specific internals of hibernate to force a new Session creation. Why would you want that? Smells like a [xyproblem](http://xyproblem.info/). – Paulo Araújo Apr 14 '20 at 11:18
  • I would like to make it possible for the caller of the method to call it without having to think about transactions. This method will be part of an API, intended for less experienced programmers that might not even know about transactions. Therefore, to my knowledge, each of these transactional methods will need their own session. – Rik Schaaf Apr 14 '20 at 19:39
  • The example of calling it from a PicoCLI command is how I am manually testing it at the moment. Those other programmers might not even use PicoCLI and use other methods of handling commands, possibly without them even using spring themselves and me providing the spring beans through plain old java singleton methods or static methods. – Rik Schaaf Apr 14 '20 at 19:39
  • I still don't understand why you would need to isolate Sessions in your scenario. You may annotate all three methods (the actual called and both that are called by it) with Transactional. In any case, there will be only one transaction (the started by the first Transactional annotated method called). Your users won't have to know anything about transactions. – Paulo Araújo Apr 15 '20 at 11:17
  • Ah I see that I didn't clarify that the caller of the transactional methods can be those using the API. And since I don't want the users of my API to have to understand what transactionality is, they should be able to use all those transactional methods separately. Therefore it is not possible for me to make the caller method transactional, since that will not be part of my code. – Rik Schaaf Apr 15 '20 at 13:01
  • In that case, you must make all accessible fields of your entity eagerly loaded (or load them inside your called methods). This obviously can lead to worst performance, but I guess your users must understand the limitations of the API. – Paulo Araújo Apr 15 '20 at 13:15
  • At no point during the first transaction does it need to load these relations. These are 2 completely separate calls, 2 completely separate database interactions. What I need is a way to tell my code that this is the case. Eagerly loading is not an option, because the number of elements that need or needn't be loaded numbers in the tens of thousands (or mabye even 100`000s). Why would it be possible to have 2 separate commands from PicoCLI have the ability to perform a database transaction, but it wouldn't be possible to perform 2 database transactions from 1 command? – Rik Schaaf Apr 15 '20 at 13:27
0

I found that hibernate out of the box doesn't reopen a session and therefore doesn't enable lazy loading after the first transaction has ended, whether or not subsequent jpa statements are in a transaction or not. There is however a property in hibernate to enable this feature:

spring:
  jpa:
    properties:
      hibernate.enable_lazy_load_no_trans: true

This will make sure that if there is no session, then a temp session will be created. I believe that it will also prevent a session from ending after a transaction, but I don't know this for sure.

Partial credit goes to the following answers from other StackOverflow questions:

WARNING: In hibernate 4.1.8 there is a bug that could lead to loss of data! Make sure that you are using 4.2.12, 4.3.5 or newer versions of hibernate. See: https://hibernate.atlassian.net/browse/HHH-7971.

Rik Schaaf
  • 983
  • 1
  • 8
  • 30