Spring/Hibernate @Transactional - Session closing before it should
I can't figure out why, but the Hibernate session is closing before it should, so I can't fetch lazily loaded lists.
In the logs, it's showing that the session closes immediately after hibernateTemplate.findByNamedParam() inside the DAO.
When I run my web 开发者_JS百科app, I get the following error:
Mar 29, 2011 3:13:21 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [Spring MVC Dispatcher Servlet] in context with path [/apps] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.communitydriven.apps.entities.Project.tags, no session or session was closed] with root cause
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.communitydriven.apps.entities.Project.tags, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:365)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:108)
at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:272)
at com.communitydriven.apps.managers.ProjectManager.getProject(ProjectManager.java:98)
at com.communitydriven.apps.controllers.ProjectController.getViewProject(ProjectController.java:86)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:498)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:562)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:394)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:166)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
My DAO:
@Repository
public class ProjectDao implements IProjectDao {
private HibernateTemplate hibernateTemplate;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
...
/**
* {@inheritDoc}
*/
@Override
public Project getProject(Project project) {
// Validate required parameters
if ( (project.getId() == null) ) {
throw new NullPointerException("Missing required parameter: " +
project.toString());
}
// Parameters
List<String> paramNames = new ArrayList<String>();
List<Object> values = new ArrayList<Object>();
// Construct HQL
StringBuffer hql = new StringBuffer();
hql.append("from Project p ");
boolean whereUsed = false; // track of "where" clause has been used
// Filter by Id
if (project.getId() != null) {
whereUsed = DaoUtils.appendFilter(whereUsed, hql);
hql.append("p.id = :id ");
paramNames.add("id");
values.add(project.getId());
}
// Get list of matching projects
@SuppressWarnings("unchecked")
List<Project> projects =
hibernateTemplate.findByNamedParam(
hql.toString(),
paramNames.toArray(new String[paramNames.size()]),
values.toArray());
// Get unique result
Project projectResult = DataAccessUtils.uniqueResult(projects);
return projectResult;
}
}
My Manager:
@Component
public class ProjectManager implements IProjectManager {
@Autowired
private IProjectDao projectDao;
...
/**
* {@inheritDoc}
*/
@Transactional
@Override
public ProjectMO getProject(Long projectId) {
Project project = new Project();
project.setId(projectId);
project = projectDao.getProject(project);
ProjectMO projectMO = new ProjectMO();
projectMO.setId(project.getId());
projectMO.setName(project.getName());
projectMO.setDescription(project.getDescription());
StringBuffer tags = new StringBuffer();
final String DELIMITER = ", ";
for (Tag tag : project.getTags()) {
tags.append(tag.getName() + DELIMITER);
}
projectMO.setTags(tags.toString());
return projectMO;
}
}
The Entity:
@Entity
@Table
public class Project {
private Long id;
private String name;
private String description;
private User submittedBy;
private List<Tag> tags;
public String toString() {
final String DELIMITER = ", ";
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName() + ": [");
sb.append("id: " + id).append(DELIMITER);
sb.append("name: " + name).append(DELIMITER);
sb.append("description: " + description).append("]");
return sb.toString();
}
// GETTERS AND SETTERS //
@Id
@Column
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@OneToOne
@JoinColumn
public User getSubmittedBy() {
return submittedBy;
}
public void setSubmittedBy(User submittedBy) {
this.submittedBy = submittedBy;
}
@ManyToMany
@JoinTable(name="Projects_Tags",
joinColumns={@JoinColumn(name="project_id")},
inverseJoinColumns={@JoinColumn(name="tag_id")})
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
}
My Database context:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto=update
</value>
</property>
<property name="packagesToScan" value="com.communitydriven.apps.entities" />
</bean>
<tx:annotation-driven />
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</beans>
Have you set up OpenSessionInViewFilter?
Associations are lazy-fetched by default. So once you exit the scope of the @Transactional annotation, you can't get the collection.
While OpenSessionInViewFilter is an option, I agree with you that that's not a great approach. In general you want to exercise tight control over transactional boundaries.
If you know you always want to do a fetch join, just include that in your HQL or set fetch = FetchType.EAGER in your @ManyToMany.
If you want to include tags in some cases but not in others, you can do Hibernate.initialize(project.getTags()) for the case where you want the tags, which will do a separate query for the tags, but it's not an n+1. Alternatively you can have two separate HQL queries (one that includes the fetch join and one that doesn't). Only thing is that if you have multiple collections you can't do fetch joins on more than one. So Hibernate.initialize() is pretty useful in such contexts.
Have you tried explicitly defining the transaction manager implementation, as well as using aspectj for the weaving (since your annotation is on a concrete class)?
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" />
What is the transaction manager in use - it should be HibernateTransactionManager or JTATransactionManager.
I just had same problem, I didnt want to change my component to service also I wanted to keep my @ManyToMany
as FetchType.Lazy
because it has been called many times and join entity details doesnt needed. So my dirty workaround is calling a new entity instead of using existing one. Refer to your example I have used: (lets assume you have implemented getProjectById method in your DAO as well )
List<Tags> tags = projectDAO.getProjectById(project.getId()).getTags();
if(tags.size()>0)
for (Tag tag : ) {
tags.append(tag.getName() + DELIMITER);
}
)
instead of:
for (Tag tag : project.getTags()) {
tags.append(tag.getName() + DELIMITER);
}
)
The Problem could be due to mapping provided in OpenViewSessionFilter defined in web.xml where each http session is binded by a hibernate session.
I had the same problem. My problem was is my servlet-context.xml
and root-context.xml
(my application context)
if you have component-scan specified in both contexts you can correct it by changing like this 1. In servlet-context specify component-scan for controller package only 2. In root-context specify component-scan for other packages (Do not include controller package here)
I got solution from this answer. You can also refer to the link and understand why @Transactional
is not working
http://forum.spring.io/forum/spring-projects/data/65235-using-jpa-with-transactional-and-i-get-a-session-is-closed-exception
Annotate your ProjectManager class as @Service and not @Component.
精彩评论