4

I have a spring data rest application with relation between types Match and Round

@Entity
public class Match {

    @OneToMany
    private List<Round> rounds;
    ...
}

When a link is created between a match and a round, e.g. like this

curl -X PUT -d "http://localhost:8080/rounds/2" -H "Content-Type:text/uri-list" http://localhost:8080/matches/1/rounds;

i capture this with an EventHandler to do some updates on my domain model:

public class MatchEventHandler

    @HandleAfterLinkSave
    public void handleLinkSave(Match match, List<Round> rounds) {
        ...
}

I need to access the second argument in order to do my update but doing so, e.g. with rounds.get(0), returns

org.hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session

Reading other threads about Hibernates LazyInitializationExceptions i see mainly three approaches that are suggested:

  1. Use Hibernate.initialize() - i don't see on which method i could call this and it would add an ugly dependency to Hibernate framework
  2. Put controller method into a transaction - i understand that spring data already puts everything into a transaction. Also as this is a spring data rest application i'm not using any controller or service layer and therfore i would not know what exactly to put into a transaction.
  3. Set FetchType.EAGER on the relation - although not really a valid solution i did try it. In that case the second argument of the @HandleAfterLinkSave method is an empty list, so it also does not deliver the expected result.
cybodelic
  • 43
  • 5
  • Option 1 and 3 are overkill. Option 2 is not true - spring data DOES NOT put any transactions automatically, so you should explicitly place `@Transactional` on controller (or better service) method and place `@EnableTransactionManagement` on your spring configuration class – Nikolai Shevchenko May 13 '19 at 20:32
  • Thanks for you comment Nikolay. This is a spring data rest application so i don't have a controller or service class, only the domain model, repository interfaces and the ```@RepositoryEventHandler``` class. So where should i place the ```@Transactional``` annotation? I've tried putting it on the handleLinkSave method but it didn't resolve the problem. – cybodelic May 15 '19 at 20:58

1 Answers1

1

The property references are handled in the following Spring class:

org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController

Although it works quite well most of the time, it's buggy here or there. Or let's say this way: It has some interesting behaviours... (I've spent months during the last two years for creating an improved version of Spring Data Rest).

If the request method was POST or PATCH then the 2nd parameter contains the updated collection. But if you use a PUT method, it contains the original collection.

(Also, there is no way to decide which collection was modified, if you have multiple property-collections in your main entity. And there are those questionable results for map-type properties, but that's an other story.)

So the best solution IMHO if you create a repository method in RoundRepository, and use it to reload the collection:

@RestResource(exported = false)
List<Round> findAllByMatch(Match match);

By the way! In regards of your Option 2, please read this topic!

Selindek
  • 3,269
  • 1
  • 18
  • 25
  • Thanks a lot for clarifying this. Unfortunately there's poor documentation on the handling of LinkEvents, or at least i was not able to find anything. So i'm now using POST or PATCH which is sufficient for my use case as there are no further collection properties or map-types in this entity. I still don't really understand why using PUT leads to the LazyInitializationException but i'm ok with this solution. – cybodelic Jun 06 '19 at 20:24
  • During PUT request the parameter contains the original - unmodified - collection. Which was loaded using LAZYloading and was NOT modified (SDR creates a brand new collection, put all the property-elements into it and overrides the original one), so the proxy object was never resolved. When the event handler is called it happens outside of transactions, so it cannot automatically resolved there. – Selindek Jun 06 '19 at 20:35