Reputation: 5543
This is the exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed
Here is the model:
@Entity
@Table(name = "T_TOPIC")
public class Topic {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@ManyToOne
@JoinColumn(name="USER_ID")
private User author;
@Enumerated(EnumType.STRING)
private Tag topicTag;
private String name;
private String text;
@OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
...
public Collection<Comment> getComments() {
return comments;
}
}
The controller, which calls model looks like the following:
@Controller
@RequestMapping(value = "/topic")
public class TopicController {
@Autowired
private TopicService service;
private static final Logger logger = LoggerFactory.getLogger(TopicController.class);
@RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
public ModelAndView details(@PathVariable(value="topicId") int id) {
Topic topicById = service.findTopicByID(id);
Collection<Comment> commentList = topicById.getComments();
Hashtable modelData = new Hashtable();
modelData.put("topic", topicById);
modelData.put("commentList", commentList);
return new ModelAndView("/topic/details", modelData);
}
}
The jsp-page looks li the following:
<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>View Topic</title>
</head>
<body>
<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>
</c:forEach>
</ul>
</body>
</html>
The exception is raised when viewing the jsp. In the line with c:forEach loop
Upvotes: 493
Views: 823860
Reputation: 2387
I was getting this error in a spring boot integration test while trying to log the result of a JPA repository findAll()
. Adding @Transactional
to the test method fixed it.
log.debug("entities {}", userRepository.findAll());
Upvotes: 2
Reputation: 473
@Controller
@RequestMapping(value = "/topic")
@Transactional
I solved this problem by adding @Transactional
, I think this can open a session.
Upvotes: 44
Reputation: 2521
From my experience, I have the following methods to solve the famous LazyInitializationException
:
(1) Use Hibernate.initialize
Hibernate.initialize(topics.getComments());
(2) Use JOIN FETCH
You can use the JOIN FETCH
syntax in your JPQL to explicitly fetch the child collection out. This is somehow like EAGER
fetching.
(3) Use OpenSessionInViewFilter
LazyInitializationException
often occurs in the view layer. If you use Spring framework, you can use OpenSessionInViewFilter
. However, I do not suggest you to do so. It may lead to performance issues if not used correctly.
Upvotes: 223
Reputation: 6145
In my case the problem was passing a detached entity to a component (even annotated with @Transactional) within a test case. So after reloading the entity it worked:
@Entity
class Entity {
@ElementCollection
@BatchSize(size = 10)
private List<String> strings;
}
@Component
@Transactional
class MyTestComponent {
public String method(Entity detachedEntity) {
// reloading the entity fixed it
Entity entity = entityRepository.findById(detachedEntity.getId());
// detachedEntity.getStrings(); caused the problem
entity.getStrings();
}
}
Upvotes: 0
Reputation: 7337
I got this error after a second execution of a method to generate a JWT token.
The line user.getUsersRole().stream().forEachOrdered((ur) -> roles.add(ur.getRoleId())); generated the error.
// MyUserDetails.java
@Service
public class MyUserDetails implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) {
/* ERROR
/* org.hibernate.LazyInitializationException: failed to
/* lazily initialize a collection of role:
/* com.organizator.backend.model.User.usersRole,
/* could not initialize proxy - no Session */
user.getUsersRole().stream().forEachOrdered((ur) ->
roles.add(ur.getRoleId()));
In my case the @Transactional
annotation solved it,
// MyUserDetails.java
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyUserDetails implements UserDetailsService {
@Override
@Transactional // <-- added
public UserDetails loadUserByUsername(String email) {
/* No Error */
user.getUsersRole().stream().forEachOrdered((ur) ->
roles.add(ur.getRoleId()));
Upvotes: 14
Reputation: 153770
The best way to handle the LazyInitializationException
is to join fetch upon query time, like this:
select t
from Topic t
left join fetch t.comments
You should ALWAYS avoid the following anti-patterns:
FetchType.EAGER
hibernate.enable_lazy_load_no_trans
Hibernate configuration propertyTherefore, make sure that your FetchType.LAZY
associations are initialized at query time or within the original @Transactional
scope using Hibernate.initialize
for secondary collections.
Upvotes: 42
Reputation: 157
In my Spring-Boot project spring.jpa.open-in-view
was set to false in application.properties. Setting it to true solved the problem.
Upvotes: 1
Reputation: 691
This is an old question but the below information may help people looking for an answer to this.
@VladMihalcea 's answer is useful. You must not rely on FetchType.EAGER
, instead you should load the comments into the Topic
entity when required.
If you are not explicitly defining your queries so that you could specify a join fetch
, then using @NamedEntityGraph
and @EntityGraph
you could override the FetchType.LAZY
(@OneToMany
associations use LAZY by default) at runtime and load the comments at the same time as the Topic
only when required. Which means that you restrict loading the comments to only those methods (queries) which really require that. An entity graph as JPA defines it:
An entity graph can be used with the find method or as a query hint to override or augment FetchType semantics.
You could use it based on the JPA example here. Alternatively, if you use Spring Data JPA, then you could use it based on the example provided by Spring.
Upvotes: 5
Reputation: 309
There are multiple solution for this Lazy Initialisation issue -
1) Change the association Fetch type from LAZY to EAGER but this is not a good practice because this will degrade the performance.
2) Use FetchType.LAZY on associated Object and also use Transactional annotation in your service layer method so that session will remain open and when you will call topicById.getComments(), child object(comments) will get loaded.
3) Also, please try to use DTO object instead of entity in controller layer. In your case, session is closed at controller layer. SO better to convert entity to DTO in service layer.
Upvotes: 9
Reputation: 1565
To get rid of lazy initialization exception you should not call for lazy collection when you operate with detached object.
From my opinion, best approach is to use DTO, and not entity. In this case you can explicitly set fields which you want to use. As usual it's enough. No need to worry that something like jackson ObjectMapper
, or hashCode
generated by Lombok will call your methods implicitly.
For some specific cases you can use @EntityGrpaph
annotation, which allow you to make eager
load even if you have fetchType=lazy
in your entity.
Upvotes: 4
Reputation: 696
Not the best solution, but for those who are facing LazyInitializationException
especially on Serialization
this will help. Here you will check lazily initialized properties and setting null
to those. For that create the below class
public class RepositoryUtil {
public static final boolean isCollectionInitialized(Collection<?> collection) {
if (collection instanceof PersistentCollection)
return ((PersistentCollection) collection).wasInitialized();
else
return true;
}
}
Inside your Entity class which you are having lazily initialized properties add a method like shown below. Add all your lazily loading properties inside this method.
public void checkLazyIntialzation() {
if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
yourlazyproperty= null;
}
Call this checkLazyIntialzation()
method after on all the places where you are loading data.
YourEntity obj= entityManager.find(YourEntity.class,1L);
obj.checkLazyIntialzation();
Upvotes: 2
Reputation: 476
The problem is caused because the code is accessing a lazy JPA relation when the "connection" to the database is closed (persistence context is the correct name in terms of Hibernate/JPA).
A simple way of solving it in Spring Boot is by defining a service layer and using the @Transactional
annotation. This annotation in a method creates a transaction that propagates into the repository layer and keeps open the persistence context until the method finish. If you access the collection inside the transactional method Hibernate/JPA will fetch the data from the database.
In your case, you just need to annotate with @Transactional
the method findTopicByID(id)
in your TopicService
and force the fetch of the collection in that method (for instance, by asking its size):
@Transactional(readOnly = true)
public Topic findTopicById(Long id) {
Topic topic = TopicRepository.findById(id).orElse(null);
topic.getComments().size();
return topic;
}
Upvotes: 7
Reputation: 547
Two things you should have for fetch = FetchType.LAZY
.
@Transactional
and
Hibernate.initialize(topicById.getComments());
Upvotes: 11
Reputation: 14126
In my case, I had the mapping b/w A
and B
like
A
has
@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;
in the DAO
layer, the method needs to be annotated with @Transactional
if you haven't annotated the mapping with Fetch Type - Eager
Upvotes: 2
Reputation: 9177
Yet another way to do the thing, you can use TransactionTemplate to wrap around the lazy fetch. Like
Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());
Upvotes: 0
Reputation: 187
One of the best solutions is to add the following in your application.properties file: spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Upvotes: 17
Reputation: 4669
By using the hibernate @Transactional
annotation, if you get an object from the database with lazy fetched attributes, you can simply get these by fetching these attributes like this :
@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
savedTicketOpt.ifPresent(ticket -> {
Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
assertThat(saleOpt).isPresent();
});
}
Here, in an Hibernate proxy-managed transaction, the fact of calling ticket.getSales()
do another query to fetch sales because you explicitly asked it.
Upvotes: 4
Reputation: 29
Hi All posting quite late hope it helps others, Thanking in advance to @GMK for this post Hibernate.initialize(object)
when Lazy="true"
Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();
now if i access 'set' after closing session it throws exception.
My solution :
Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();
now i can access 'set' even after closing Hibernate Session.
Upvotes: 0
Reputation: 30089
The collection comments
in your model class Topic
is lazily loaded, which is the default behaviour if you don't annotate it with fetch = FetchType.EAGER
specifically.
It is mostly likely that your findTopicByID
service is using a stateless Hibernate session. A stateless session does not have the first level cache, i.e., no persistence context. Later on when you try to iterate comments
, Hibernate will throw an exception.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed
The solution can be:
Annotate comments
with fetch = FetchType.EAGER
@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
If you still would like comments to be lazily loaded, use Hibernate's stateful sessions, so that you'll be able to fetch comments later on demand.
Upvotes: 1
Reputation: 440
The reason is you are trying to get the commentList on your controller after closing the session inside the service.
topicById.getComments();
Above will load the commentList only if your hibernate session is active, which I guess you closed in your service.
So, you have to get the commentList before closing the session.
Upvotes: 1
Reputation: 3927
your list is lazy loading, so the list wasn't loaded.
call to get on the list is not enough.
use in Hibernate.initialize in order to init the list.
If dosnt work run on the list element and call Hibernate.initialize for each .
this need to be before you return from the transaction scope.
look at this post.
search for -
Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session
Upvotes: 6
Reputation: 3405
The reason is that when you use lazy load, the session is closed.
There are two solutions.
Don't use lazy load.
Set lazy=false
in XML or Set @OneToMany(fetch = FetchType.EAGER)
In annotation.
Use lazy load.
Set lazy=true
in XML or Set @OneToMany(fetch = FetchType.LAZY)
In annotation.
and add OpenSessionInViewFilter filter
in your web.xml
Detail See my POST.
Upvotes: 29
Reputation: 558
The problem is caused by accessing an attribute with the hibernate session closed. You have not a hibernate transaction in the controller.
Possible solutions:
Do all this logic, in the service layer, (with the @Transactional), not in the controller. There should be the right place to do this, it is part of the logic of the app, not in the controller (in this case, an interface to load the model). All the operations in the service layer should be transactional. i.e.: Move this line to the TopicService.findTopicByID method:
Collection commentList = topicById.getComments();
Use 'eager' instead of 'lazy'. Now you are not using 'lazy' .. it is not a real solution, if you want to use lazy, works like a temporary (very temporary) workaround.
In general, the best solution is the 1.
Upvotes: 37
Reputation: 4100
In my case following code was a problem:
entityManager.detach(topicById);
topicById.getComments() // exception thrown
Because it detached from the database and Hibernate no longer retrieved list from the field when it was needed. So I initialize it before detaching:
Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm
Upvotes: 3
Reputation: 1916
I know it's an old question but I want to help. You can put the transactional annotation on the service method you need, in this case findTopicByID(id) should have
@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
more info about this annotation can be found here
About the other solutions:
fetch = FetchType.EAGER
is not a good practice, it should be used ONLY if necessary.
Hibernate.initialize(topics.getComments());
The hibernate initializer binds your classes to the hibernate technology. If you are aiming to be flexible is not a good way to go.
Hope it helps
Upvotes: 133
Reputation: 386
@Transactional annotation on controller is missing
@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}
Upvotes: 9
Reputation: 71
To solve the problem in my case it was just missing this line
<tx:annotation-driven transaction-manager="myTxManager" />
in the application-context file.
The @Transactional
annotation over a method was not taken into account.
Hope the answer will help someone
Upvotes: 7
Reputation: 10025
For those working with Criteria, I found that
criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);
did everything I needed had done.
Initial fetch mode for collections is set to FetchMode.LAZY to provide performance, but when I need the data, I just add that line and enjoy the fully populated objects.
Upvotes: 2
Reputation: 127
I found out that declaring @PersistenceContext
as EXTENDED
also solves this problem:
@PersistenceContext(type = PersistenceContextType.EXTENDED)
Upvotes: 7
Reputation: 244
If you are trying to have a relation between a entity and a Collection or a List of java objects (for example Long type), it would like something like this:
@ElementCollection(fetch = FetchType.EAGER)
public List<Long> ids;
Upvotes: 12