简体   繁体   中英

LazyInitializationException even though @Transactional is used

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";
    }

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM