在什么情况下这种奇怪的jpa行为会发生?

时间:2015-12-17 13:36:06

标签: java jpa glassfish ejb

我有一个无状态的ejb bean,它有一个entitymanager(em)和这个函数。

public void what(Long commentId) {
    Comment c = em.find(Comment.class, commentId);
    em.refresh(c);
    CommentUpdate cu = new CommentUpdate(c, "new Text");
    em.persist(cu);
    c.getUpdates().add(0, cu);
    int i = c.getUpdates().size();
    em.flush();
    int j = c.getUpdates().size();
    if (i != j)
        System.err.println("What?");
}

CommentUpdate是一个非常简单的实体类。

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentUpdate extends AbstractEntity {

@ManyToOne
@Getter
@Setter
private Comment comment;

@Getter
@Setter
private String text;

public CommentUpdate(Comment comment, String text) {
    this.comment = comment;
    this.text = text;
}
}

评论稍微复杂一点,但关系定义如下:

@OneToMany(mappedBy = "comment")
@OrderBy("createdAt DESC")
@Getter
private List<CommentUpdate> updates = new ArrayList<>();

现在的事情是,有时&#34;什么?&#34;打印到日志中。 刷新后,新创建的CommentUpdate消失了。但又回到了下一次刷新。 这是一个更大的vaadin项目中的方法,我还不能在一个小/简单的项目中重现它。是否存在逻辑行为,或者这可能是glassfish(4.1.1)使用的eclipselink(2.6)中的错误?

2 个答案:

答案 0 :(得分:1)

第一个可疑的事情是CommentCommentUpdate

之间存在双向关系
@OneToMany(mappedBy = "comment")
private List<CommentUpdate> updates = new ArrayList<>();

但你只设置了它的一面:

c.getUpdates().add(0, cu);

对于双向关系,您必须自己设置两个引用,即。

c.getUpdates().add(0, cu);
cu.setComment(c);

不这样做会导致状态不一致,就像您刚刚遇到的状态一样。

JPA specs(章 2.9实体关系)说:

  

关系的拥有方确定数据库中关系的更新,如第3.2.4节所述

     

一对多/多对一双向关系的多方必须是拥有方

3.2.4与数据库的同步

部分中
  

托管实体之间的双向关系将根据关系所属方持有的引用进行保留。开发人员有责任将内存中的引用保留在拥有方,而保持在反方的引用在更改时保持一致。

     

特别重要的是要确保对关系的反面进行更改会在所属方面产生适当的更新,以确保更改在与数据库同步时不会丢失。

通常,如果你仔细遵循JPA规范,EM真的会负责整理所有内容。但如果你犯了一个小错误,事情会变得奇怪。所以底线是:在comment对象中设置commentUpdate,这是此关系中的拥有方。

注意:打电话后

Comment c = em.find(Comment.class, commentId);

应该不需要刷新c,因为它只是被提取并被管理。

答案 1 :(得分:0)

可能导致您获得此类结果的最可能原因是启用了缓存并且您为脏读取设置了数据源,这允许读取未提及的更新。使用缓存时,EntityManager将缓存查询以提高性能。在这种情况下,当您阅读关系时,它将从缓存集中读取它并且不会查询表。更新实体出现在第二次方法调用中的原因是因为CommentUpdate已经提交给数据库,因此缓存也会更新。

如果您使用Eclipselink作为提供商,则可以使用这些属性禁用或启用缓存

<property name="eclipselink.query-results-cache" value="false"/>
<property name="eclipselink.cache.shared.default" value="false"/>
<property name="eclipselink.cache.size.default" value="0"/>
<property name="eclipselink.cache.type.default" value="None"/>