【问题标题】:Spring/Hibernate @Transactional - Session closing before it shouldSpring/Hibernate @Transactional - 在它应该之前关闭会话
【发布时间】:2011-07-25 16:09:14
【问题描述】:

我不知道为什么,但是 Hibernate 会话提前关闭了,所以我无法获取延迟加载的列表。

在日志中,显示会话在 DAO 内的 hibernateTemplate.findByNamedParam() 之后立即关闭。

当我运行我的网络应用程序时,我收到以下错误:

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)

我的 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;
    }
}

我的经理:

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

实体:

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

我的数据库上下文:

<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>

【问题讨论】:

  • 到目前为止,我只是传入一个布尔值来急切地获取惰性列表。但是,我真的很想知道为什么默认功能没有按预期工作。

标签: hibernate spring spring-mvc transactions


【解决方案1】:

你设置OpenSessionInViewFilter了吗?

【讨论】:

  • 我不想在视图或控制器中允许事务,我希望在服务层的@Transactional 中实现预期的行为。
  • 设置 OpenSessionInViewFilter 并不意味着事务由 View 或 Controller 控制。这只是意味着休眠会话保持活动状态,直到视图被处理。这允许您获取延迟加载的实体。 OpenSessionInViewFilter 对事务没有任何副作用。
  • 不幸的是,这会给我带来更多的痛苦。我将开始使用 JacksonMapping 将对象转换为 JSON。 JacksonMapping 急切地抓取对象中的所有字段,这意味着如果我不是很明确,我可能会不小心将整个数据库加载到内存中。我确实喜欢这些惰性异常来控制我,但我觉得这个特殊的错误不应该发生。
  • 另一个(可能有点笨拙)选项是手动将会话绑定到线程!这可以通过 AOP 建议来完成,从而使解决方案具有非侵入性。有效吗?
【解决方案2】:

默认情况下,关联是惰性获取的。所以一旦退出@Transactional注解的作用域,就无法获取集合了。

虽然 OpenSessionInViewFilter 是一个选项,但我同意你的观点,这不是一个好方法。一般来说,您希望对事务边界进行严格控制。

如果您知道自己总是想要进行 fetch 连接,只需将其包含在您的 HQL 中或在您的 @ManyToMany 中设置 fetch = FetchType.EAGER。

如果您想在某些情况下包含标签但在其他情况下不包含标签,您可以在需要标签的情况下执行 Hibernate.initialize(project.getTags()) ,这将对标签进行单独的查询,但是它不是n+1。或者,您可以有两个单独的 HQL 查询(一个包含 fetch 连接,一个不包含)。唯一的问题是,如果您有多个集合,则无法获取多个集合。所以 Hibernate.initialize() 在这种情况下非常有用。

【讨论】:

  • 是的,但是我的经理有@Transactional,并且经理内部发生了延迟提取错误。
  • 另外,Hibernate.initialize() 会抛出同样的惰性错误。
【解决方案3】:

您是否尝试过显式定义事务管理器实现,以及使用 aspectj 进行编织(因为您的注释位于具体类上)?

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" />

【讨论】:

    【解决方案4】:

    什么是正在使用的事务管理器 - 它应该是 HibernateTransactionManager 或 JTATransactionManager。

    【讨论】:

    • org.springframework.orm.hibernate3.HibernateTransactionManager
    【解决方案5】:

    我只是遇到了同样的问题,我不想将我的组件更改为服务,我也想将我的@ManyToMany 保留为FetchType.Lazy,因为它已被多次调用并且不需要加入实体详细信息。所以我肮脏的解决方法是调用一个新实体而不是使用现有实体。请参考我使用过的示例:(假设您在 DAO 中也实现了 getProjectById 方法)

    List<Tags> tags = projectDAO.getProjectById(project.getId()).getTags();
    if(tags.size()>0)
    for (Tag tag : ) {
                tags.append(tag.getName() + DELIMITER);
            }
    )
    

    代替:

    for (Tag tag : project.getTags()) {
                tags.append(tag.getName() + DELIMITER);
            }
    )
    

    【讨论】:

      【解决方案6】:

      问题可能是由于在 web.xml 中定义的 OpenViewSessionFilter 中提供的映射,其中每个 http 会话都由休眠会话绑定。

      【讨论】:

        【解决方案7】:

        我遇到了同样的问题。我的问题是我的servlet-context.xml 和root-context.xml(我的应用程序上下文)

        如果你在两种情况下都指定了组件扫描,你可以通过这样的改变来纠正它 1. 在 servlet-context 中仅指定控制器包的组件扫描 2.在root-context中指定其他包的组件扫描(这里不要包括控制器包)

        我从这个答案中得到了解决方案。也可以参考链接了解@Transactional为什么不起作用

        http://forum.spring.io/forum/spring-projects/data/65235-using-jpa-with-transactional-and-i-get-a-session-is-closed-exception

        【讨论】:

          【解决方案8】:

          将您的 ProjectManager 类注释为 @Service 而不是 @Component。

          【讨论】:

            猜你喜欢
            • 2016-11-16
            • 2017-12-10
            • 1970-01-01
            • 1970-01-01
            • 2016-10-19
            • 1970-01-01
            • 1970-01-01
            • 2014-01-28
            • 1970-01-01
            相关资源
            最近更新 更多