同一事务中多个 Eclipselink 合并需要 Flush() 吗?

发布于 2024-12-21 09:54:34 字数 1209 浏览 1 评论 0 原文

我在单个事务中调用多个 EntityManager.merge() 时遇到问题。这是使用 Oracle 数据库。这两个对象都不存在。实体:

public class A {
    @Id
    @Column("ID")
    public Long getID();

    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();
}

合并代码看起来像这样:

@Transactional
public void create(Object A, Object B) {
    Object A = entitymanager.merge(A);
    B.setId(A.getId());
    entitymanager.merge(B);
}

对象 A 的 ID 通过序列生成,并在 B 上正确设置。查看日志,在调用 B 上的合并之前调用 A 上的合并。有一个从 A 到 B 的 @OneToOne 映射。但是,在方法结束时,当它实际提交时,它会尝试在 B 上执行 INSERT,然后再对 A 执行 INSERT,这会抛出 IntegrityConstraintViolation 因为“找不到父键”。

如果我在第二次合并之前添加 entitymanager.flush() ,它就可以正常工作。

@Transactional
public void create(Object A, Object B) {
    Object A = entitymanager.merge(A);
    entitymanager.flush();
    B.setId(A.getId());
    entitymanager.merge(B);
}

然而,flush() 是一个昂贵的操作,没有必要。所有这些都应该发生在同一个事务中(@Transactional 的默认传播是 Propagation.REQUIRED)。

知道为什么没有flush()就行不通吗?为什么即使A上的合并发生在B上的合并之前,COMMIT上的实际INSERT却相反?

I'm having an issue with multiple EntityManager.merge() calls in a single transaction. This is using an Oracle database. Neither object exists yet. Entities:

public class A {
    @Id
    @Column("ID")
    public Long getID();

    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();
}

The merge code looks something like this:

@Transactional
public void create(Object A, Object B) {
    Object A = entitymanager.merge(A);
    B.setId(A.getId());
    entitymanager.merge(B);
}

Object A's ID is generated through a sequence and it gets correctly set on B. Looking at the log, merge on A is called before merge on B is called. There is a @OneToOne mapping from A to B. However, at the end of the method when it goes to actually commit, it tries to do an INSERT on B before it goes to do an INSERT on A, which throws an IntegrityConstraintViolation because the "parent key not found".

If I add entitymanager.flush() before the 2nd merge, it works fine.

@Transactional
public void create(Object A, Object B) {
    Object A = entitymanager.merge(A);
    entitymanager.flush();
    B.setId(A.getId());
    entitymanager.merge(B);
}

However, flush() is an expensive operation that shouldn't be necessary. All of this should be happening in the same transaction (default propagation of @Transactional is Propagation.REQUIRED).

Any idea why this doesn't work without flush(), and why even though the merge on A happens before the merge on B, the actual INSERT on COMMIT is reversed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黑凤梨 2024-12-28 09:54:34

如果实体 A 和 B 没有关系(即 @OneToOne、@OneToMany,...),则持久性提供程序无法计算正确的插入顺序。 IIRC EclipseLink 在将 SQL 语句发送到数据库时不使用对象创建顺序。

如果您不想使用flush(),只需将约束设置为延迟即可。

If entity A and B do not have a relationship (i.e. @OneToOne, @OneToMany, ...), then the persistence provider cannot calculate the correct insertion order. IIRC EclipseLink does not use the object-creation order when it comes to sending SQL statements to the database.

If you like to refrain from using flush(), simply set your constraints to be deferred.

离去的眼神 2024-12-28 09:54:34

正如 Frank 提到的,您显示的代码没有设置 A->B 关系,因此提供程序无法知道此 B 对象需要插入到 A 之前。其他关系可能会导致它认为一般情况下需要先插入A。

延迟约束可以在某些数据库上进行,指的是设置数据库将约束处理延迟到事务结束。如果推迟或删除约束,则可以查看正在生成的 SQL 是否正确,或者代码和遗漏的映射是否存在其他问题。

As Frank mentioned, the code you have shown does not set a A->B relationship, so there is no way for the provider to know that this B object needs to be inserted before the A. Other relationships may cause it to think that in general A needs to be inserted first.

Deferring constraints can be done on some databases, and refers to setting the database to defer constraint processing until the end of the transaction. If you defer or remove the constraints, you can then see if the SQL that is being generated is correct or if there is another problem with the code and mappings that is being missed.

甜是你 2024-12-28 09:54:34

除非存在双向 @OneToOne 注释,否则合并似乎是按字母顺序排列的(至少这是一种可能性)。

以前:

public class A {
    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();
}

现在:

public class A {
    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();

    @OneToOne(targetEntity = A.class)
    @JoinColumn("ID")
    public A getA();
}

对于我正在做的事情,B 有办法获得 A 并不重要,但我仍然不明白为什么 A 中的注释不够。

It appears that the merges are alphabetical (at least, that is one possibility) unless there are bidirectional @OneToOne annotations.

Previously:

public class A {
    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();
}

Now:

public class A {
    @OneToOne(targetEntity = B.class)
    @JoinColumn("ID")
    public B getB();
}

public class B {
    @Id
    @Column("ID")
    public Long getID();

    @OneToOne(targetEntity = A.class)
    @JoinColumn("ID")
    public A getA();
}

For what I'm doing it doesn't matter that B has a way to get A, but I still don't understand why the annotations in A aren't sufficient.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文