【问题标题】:Spring @Transactional - using Collections after persist/mergeSpring @Transactional - 在持久/合并后使用集合
【发布时间】:2010-11-19 23:08:11
【问题描述】:

我在我的应用程序中使用 Spring 和 JPA,但最近我对使用 DomainObjects 中的集合有点困惑。

例如:

假设我们有两个表:Parent 和 Child。一个父级可以有多个子级 (@OneToMany),一个子级只能有一个父级 (@ManyToOne)。我使用 Spring 在我的 ServiceLayer(不是 Dao - 工作单元模式)中处理我的事务上下文,它由 aop:config 在 context-application.xml 中配置。子集合被标记为 LazyLoading 集合。到目前为止一切正常。

现在的问题如下:

我的服务中需要一个方法,它创建一个新的 Parent 并在同一个事务中调用另一个方法来对新创建的 Parent 进行一些计算。在这些计算中,我必须调用 parent.getChildren()。显然这应该返回一个空集,因为新的父级在创建时不能有任何子级,但由于某种原因,我返回“null”而不是一个空集,这会导致 NullpointerException

为了创建父级,我尝试了以下 EntityManager 方法:

  1. 使用 entityManager.persist( parent ) - 没有成功,创建后集合为空。
  2. 使用 entityManager.merge( parent ) 并返回 parent 的新托管实例 - 结果相同。

两者都导致了一个空集。但我很确定,这些调用在同一个事务中,因为在事务之外我会收到一个 LazyLoadingException(而不是 NullPointer),此时(afaik)是正确的。另一方面,将已经存在的父级与 entityManager.findById() 一起使用会产生一个现有的集合(这意味着它可以像预期的那样工作)。为了解决这个问题,我尝试了几种方法,直到找到一个可行的方法:

entityManager.refresh( parent ) 合并/持久化后,因为调用刷新时集合被初始化。

但在教程中,我从未见过有人使用过这种方法。在 EntityManager 的 JavaDoc 中说过,merge 返回新的托管 bean,而 persist 使引用本身受到管理。我猜托管意味着我已经可以在事务中使用这些集合。为什么集合在创建时没有被初始化?例如,我是否必须在 Parent 的构造函数中自己初始化它们?

我想知道,如果我做错了什么,或者在我创建了一个新的 Parent 实例之后调用 refresh() 方法是否正确?如果是这样猜我只需要像下面的文章中推荐的那样在持久化和更新之间分开:JPA Implemenentations 并在持久化后刷新新创建的对象?还是应该在我真的需要使用 Collection 时只刷新 Object?

希望您能关注我,如果您有任何建议或解决方案让我清楚(呃)这一点,我将不胜感激。

非常感谢,
梅内

【问题讨论】:

    标签: java spring collections transactions


    【解决方案1】:

    您是否初始化您的集合字段?

    @OneToMany
    private Set<Foo> foos = new HashSet<Foo>();
    

    而不仅仅是

    @OneToMany
    private Set<Foo> foos;
    

    因为后者当然会抛出 NPE。

    【讨论】:

    • 我通常会进一步采用这种方法,将所有集合实例声明为“最终”,这样我就不会忘记初始化它们。
    • 这不会阻止 JPA 提供者注入他们的自定义集合(PersistentBag 等)吗?
    • 我猜想 JPA EntityManager 必须负责初始化这些集合字段,所以我没有直接初始化集合字段。但这将是我可以忍受的解决方案。感谢您的提示。周一去试试,看看有没有效果。
    • 它可能确实初始化了它们,但在你的情况下,由于懒惰,它还没有完成。
    • 似乎工作正常,谢谢。我只是认为懒惰意味着这些集合在我真正在事务中使用它们的那一刻就被初始化了。
    【解决方案2】:

    尝试在父类中使用 CascadeType.ALL。

    @OneToMany(cascade = CascadeType.ALL)
    public List<Child> getChildren() {
      return children;
    }
    

    第二次使用 OpenSessionInViewFilter。这将确保您甚至可以从视图中调用 parent.getChildren() 并且休眠会话保持打开状态,直到发回响应。下面是如何在 web.xml 中配置它

    <filter>
     <filter-name>OpenSessionInViewFilter</filter-name>
     <filter-class>com.isavera.hibernate.OpenSessionFilter</filter-class>
       <init-param>
         <param-name>singleSession</param-name>
     <param-value>false</param-value>
       </init-param>
     </filter>
    
    <filter-mapping>
     <filter-name>OpenSessionInViewFilter</filter-name>
     <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    希望对你有帮助

    【讨论】:

    • 这是 LazyLoadException 等的答案,但不是 NullPointerException。
    猜你喜欢
    • 2010-12-07
    • 1970-01-01
    • 2020-06-05
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 2018-08-26
    • 1970-01-01
    相关资源
    最近更新 更多