可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have this problem:
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>
Exception is rised, when viewing jsp. In the line with c:forEach loop
回答1:
If you know that you\'ll want to see all Comment
s every time you retrieve a Topic
then change your field mapping for comments
to:
@OneToMany(fetch = FetchType.EAGER, mappedBy = \"topic\", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
Collections are lazy-loaded by default, take a look at this if you want to know more.
回答2:
From my experience, I have the following methods to solved 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 some how like EAGER fetching.
(3) Use OpenSessionInViewFilter
LazyInitializationException often occur in view layer. If you use Spring framework, you can use OpenSessionInViewFilter. However, I do not suggest you to do so. It may leads to performance issue if not use correctly.
回答3:
The origin of your problem:
By default hibernate lazily loads the collections (relationships) which means whenver you use the collection
in your code(here comments
field
in Topic
class)
the hibernate gets that from database, now the problem is that you are getting the collection in your controller (where the
JPA session is closed).This is the line of code that causes the exception
(where you are loading the comments
collection):
Collection<Comment> commentList = topicById.getComments();
You are getting \"comments\" collection (topic.getComments()) in your controller(where JPA session
has ended) and that causes the exception. Also if you had got
the comments
collection in your jsp file like this(instead of getting it in your controller):
<c:forEach items=\"topic.comments\" var=\"item\">
//some code
</c:forEach>
You would still have the same exception for the same reason.
Solving the problem:
Because you just can have only two collections with the FetchType.Eager
(eagerly fetched collection) in an Entity class and because lazy loading is more
efficient than eagerly loading, I think this way of solving your problem is better than just changing the FetchType
to eager:
If you want to have collection lazy initialized, and also make this work,
it is better to add this snippet of code to your web.xml
:
<filter>
<filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
What this code does is that it will increase the length of your JPA session
or as the documentation says, it is used \"to allow for lazy loading in web views despite the original transactions already being completed.\"
so
this way the JPA session will be open a bit longer and because of that
you can lazily load collections in your jsp files and controller classes.
回答4:
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
回答5:
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.
回答6:
In order to lazy load a collection there must be an active session. In a web app there are two ways to do this. You can use the Open Session In View pattern, where you use an interceptor to open the session at the beginning of the request and close it at the end. The risk there is that you have to have solid exception handling or you could bind up all your sessions and your app could hang.
The other way to handle this is to collect all the data you need in your controller, close your session, and then stuff the data into your model. I personally prefer this approach, as it seems a little closer to the spirit of the MVC pattern. Also if you get an error from the database this way you can handle it a lot better than if it happens in your view renderer. Your friend in this scenario is Hibernate.initialize(myTopic.getComments()). You will also have to reattach the object to the session, since you\'re creating a new transaction with every request. Use session.lock(myTopic,LockMode.NONE) for that.
回答7:
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.
- use @Transactional in the Controller. It should not be used here, you are mixing service layer with presentation, it is not a good design.
- use OpenSessionInViewFilter, many disadvantages reported, possible instability.
In general, the best solution is the 1.
回答8:
@Controller
@RequestMapping(value = \"/topic\")
@Transactional
i solve this problem by adding @Transactional
,i think this can make session open
回答9:
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;
回答10:
I found out that declaring @PersistenceContext
as EXTENDED
also solves this problem:
@PersistenceContext(type = PersistenceContextType.EXTENDED)
回答11:
it was the problem i recently faced which i solved with using
<f:attribute name=\"collectionType\" value=\"java.util.ArrayList\" />
more detailed decription here and this saved my day.
回答12:
@Transactional annotation on controller is missing
@Controller
@RequestMapping(\"/\")
@Transactional
public class UserController {
}
回答13:
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
回答14:
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
回答15:
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.
回答16:
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
回答17:
As I explained in this article, the best way to handle the LazyInitializationException
is to fetch it upon query time, like this:
select t
from Topic t
left join fetch t.comments
You should ALWAYS avoid the following anti-patterns:
- making the associations EAGER
- using OSIV (Open Session in View)
- enabling the
hibernate.enable_lazy_load_no_trans
Hibernate configuration property
Therefore, make sure that your FetchType.LAZY
associations are initialized at query time or within the original @Transactional
scope using Hibernate.initialize
for secondary collections.
回答18:
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.
回答19:
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.
回答20:
In my cae, 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
回答21:
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.
回答22:
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.
回答23:
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
回答24:
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());
回答25:
i resolved using List instead of Set:
private List<Categories> children = new ArrayList<Categories>();