1

Currently I am getting the following exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.model.Link.subLinks, could not initialize proxy - no Session

I googled and I found another solution for this exception but I would like to know why @Transactional in my case is not working. I am sure that I am doing something wrong. What am I doing wrong?

The weird part is that I used @Transactional already somewhere else in this project and there it works.

Thank you in advance for any tips.

My Link class: The entity Link contains a collection of SubLink.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Link {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @NotEmpty
    private String title;
    
    @NotEmpty
    private String description;
    
    @OneToMany(mappedBy = "link")
    private Collection<SubLink> subLinks;
}

My SubLink class:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class SubLink {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @NotEmpty
    private String type;
    
    @NotEmpty
    @URL
    private String url;
    
    @ManyToOne
    @JoinColumn(name = "link_id", referencedColumnName = "id")
    private Link link;
}

My LinkServiceImpl class:

    @Service
    public class LinkServiceImpl implements LinkService {
        @Autowired
        public LinkRepository linkRepository;
        
        @Override
        @Transactional
        public Link findById(long id) {
            return linkRepository.findById(id);
        }
    }

In my controller class there is the method showLink():

    @GetMapping("/link/{linkId}")
    public String showLink(@PathVariable(value = "linkId") long linkId, Model model) {
        Link link = linkService.findById(linkId);

        
        Collection<SubLink> subLinkCollection = link.getSubLinks(); //Error
        model.addAttribute("subLinkCollection", subLinkCollection);
        return "link";
    }
13
  • Would you please provide the Link Entity Structure? Which will be helpful in understanding. Commented Nov 7, 2020 at 11:13
  • Hi @SebinThomas , I added the Link class. Thanks! Commented Nov 7, 2020 at 11:18
  • By default fetchType is already EAGER if you have not mentioned LAZY. Is your SubLink class contains any Lazy properties? Commented Nov 7, 2020 at 11:25
  • I added the SubLink class too. But to answer your question: I think not Commented Nov 7, 2020 at 11:32
  • In your Repository you have used Query or QueryMethod? If Query, have you tried @Aman suggested? And if find by Id what is the reason for using the Query method? you could have used the default method provided by JpaRepository ie, findById. Just checking with you. Commented Nov 7, 2020 at 11:45

2 Answers 2

1

Aman explained well the cause of the LazyInitializationException in your case and crizzis added more information about why @Transactional is not worked as you expected and how it should be used.

So, to summarize the options you have to solve your problem:


Fetch Eager

Commented by Aman and crizzis, this is the easier but not the best from the point of view of the performance.

To do it, include fetch = FetchType.EAGER in subLinks property definition:

@OneToMany(mappedBy = "link", fetch = FetchType.EAGER)
private Collection<SubLink> subLinks;

However, every time you get from database instances of Link => its collection of related SubLink will be included.


Custom Query

Commented by Aman too (although is not necessary on (link.id = sublink.link.id)). In this case, you can create a new method in your repository to get the SubLink collection of an specific Link.

In this way, you will be able to deal with situations on which you want to receive such "extra information" (findWithSubLinkById) or not (provided findById).

With a bidirectional OneToMany, it is enough with something like:

@Query("select l from Link l left join fetch l.subLinks where l.id = :id")
Link findWithSubLinkById(Long id);

With a unidirectional OneToMany, besides the above query you have to include how "join" both tables:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "link_id")
private Collection<SubLink> subLinks;

Removing link property from SubLink class.

More information in Hibernate associations and unidirectional one-to-many tips


EntityGraph

This is a variation of the previous one. So, instead of create the new method using @Query, an easier choice will be configured with @EntityGraph:

@EntityGraph(attributePaths = "subLinks")
Link findWithSubLinkById(Long id);

More information here (Example 77)


Work inside Transactional method

As crizzis commented, inside the method configured with @Transactional you won't receive such exception. So, another option is move the code that uses Sublink from your controller to your service.

Sign up to request clarification or add additional context in comments.

Comments

1

LazyInitializationException indicates access to unfetched data outside of a session context. To fix this, you have to fetch all required associations within your service layer.

I think in your case you are fetching Link without its SubLink, but trying to access sublinks. So, using JOIN FETCH would be the best strategy.

Note: You can use FetchType.EAGER too, but it might affect performance. Because, it will always fetch fetch the association, even if you don’t use them.

= UPDATE =

Thanks to crizzi for mentioning an interesting feature called Open Session In View(OSIV) (enabled by default in spring-boot applications). Though it depends on who you may ask to label it as a pattern or an antipattern.

The main purpose is to to facilitate working with lazy associations(avoids LazyInitializationException). Very detailed explanation can be found on

16 Comments

Hi, thank you for your help. In fact, to fix this I am using JOIN but I would like to know why '@Transactional' is not working. '@Transactional' should do similar stuff, or am I wrong?
A transaction is a sequence of operations considered as single unit of work. For instance, within a transaction you can fetch and then update an entity. If one of the operations fails, it is considered as both have failed.
@tqnone you said you are using JOIN. is it JOIN or JOIN FETCH?
@tqnone You put @Transactional on top of findById, which means a persistence context is open for the execution of findById and a transaction gets created for the execution of findById. Now, if you called link.getSublinks() from inside findById, there would be no error. However, your code is calling link.getSublinks() outside of findById, where the persistence context is already closed. I hope that clears things up
Thank you very very very much guys. I got it! :-) @[crizzis, Aman, doctore]
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.