2

我有两个具有单向多对一关系的实体:

  • 许多人Bar都有一个Foo

当我尝试通过将托管Bar实例更改Foo为现有的分离Foo实例来更新托管实例时,刷新到数据库失败。

我使用外观 ( FooFacade& BarFacade) 来创建和修改实体。这是我的测试代码:

public void test() {
    FooFacade fooFacade = (FooFacade) lookupEJB(FooFacade.class);
    BarFacade barFacade = (BarFacade) lookupEJB(BarFacade.class);

    Foo originalFoo = fooFacade.createFoo();    // returned entity is detatched
    Foo newFoo = fooFacade.createFoo();         // returned entity is detatched

    Bar bar = barFacade.createBar(originalFoo); // returned entity is detatched

    barFacade.changeFoo(bar.getId(), newFoo);
}

此代码将导致以下 SQL 错误:

该语句被中止,因为它会导致在“FOO”上定义的“SQL130321134048610”标识的唯一或主键约束或唯一索引中出现重复键值。

newFooEclipselink在刷新 的更改时尝试插入bar,即使它已经存在!

但是,实例已分离的事实newFoo应该没有区别 - 请参阅下面粗体字的EJB 持久性规范(第 3.2.3 节,第 50 页)的相关部分:

应用于实体 X 的刷新操作的语义如下:

  • 如果 X 是托管实体,则将其同步到数据库。
    • 对于由来自 X 的关系引用的所有实体 Y,如果与 Y 的关系已使用级联元素值 cascade=PERSIST 或 cascade=ALL 进行注释,则将持久操作应用于 Y。
    • 对于由来自 X 的关系引用的任何实体 Y,其中与 Y 的关系尚未使用级联元素值 cascade=PERSIST 或 cascade=ALL 进行注释:
      • 如果 Y 是新的或删除的,则刷新操作(并且事务回滚)将抛出 IllegalStateException,否则事务提交将失败。
      • 如果 Y 是分离的,则语义取决于关系的所有权。如果 X 拥有该关系,则对该关系的任何更改都会与数据库同步;否则,如果 Y 拥有关系,则行为未定义。
  • 如果 X 是已删除的实体,则将其从数据库中删除。没有级联选项是相关的。

任何人都可以帮助解释为什么这似乎不起作用?我找不到现有的 eclipselink 错误报告。我在下面有一些代码,技术是:

  • EclipseLink(使用嵌入式 Glassfish EJB 容器进行测试)
  • Derby(用于测试这个问题 - 在生产中我使用 MS SQL,但它也失败了)

谢谢!

请注意:我知道我可以通过

  1. 将分离的合并Foo到更新时使用的相同持久性上下文中bar,然后
  2. foo在 . 上设置合并的实例bar

但是,这不是我希望在我的应用程序中采用的方法。

更新 1:我已经包含了 JPA 规范的一部分,这表明这应该可以工作

更新 2:简化了示例场景

实体

@Entity
@TableGenerator(name="test_generator", table="SEQUENCE", pkColumnName="SEQ_NAME", valueColumnName="SEQ_VALUE", pkColumnValue="TEST_SEQUENCE")
public class Foo {

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
    private int id;

    public int getId() {return this.id;}
    public void setId(int id) {this.id = id;}

}

@Entity
public class Bar {

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
    private int id;

    @ManyToOne
    private Foo foo;

    public int getId() {return this.id;}
    public void setId(int id) {this.id = id;}

    public Foo getFoo() {return this.foo;}
    public void setFoo(Foo foo) {this.foo = foo;}

}

外墙

@Singleton
@LocalBean
public class FooFacade {

    @PersistenceContext(unitName="CurriculumManagementSystem")
    private EntityManager em;

    public Foo createFoo() {
        Foo newFoo = new Foo();

        em.persist(newFoo);

        return newFoo;
    }
}

@Singleton
@LocalBean
public class BarFacade {

    @PersistenceContext(unitName="CurriculumManagementSystem")
    private EntityManager em;

    public Bar createBar(Foo parent) {
        Bar newBar = new Bar();

        newBar.setFoo(parent);

        em.persist(newBar);

        return newBar;
    }

    public void changeFoo(int barID, Foo detachedFoo) {
        Bar bar = (Bar) em.find(Bar.class, barID);

        bar.setFoo(detachedFoo);

        // when this method exits, the transaction completes
        // and any changes are flushed to the DB.

        // instead of only updating the Bar table by changing the
        // foreign key reference to the detachedFoo's ID, elipselink
        // tries to insert the detachedFoo as a new row in the
        // Foo table!
    }
4

2 回答 2

1

这个问题是由于 EclipseLink 中的一个错误造成的:

任何引用分离对象的更改都将导致插入分离对象

这已在版本 2.4.0 中修复。在版本 1.1.3 中报告。

在撰写本文时,与最新版本 Glassfish (3.1.2) 一起分发的 EclipseLink 版本是 2.3.2,因此我要么必须等待 Glassfish 的未来版本,要么在我的应用程序中提供更新的 EclipseLink 库。

目前,我将等待并使用我在原始帖子中描述的合并解决方法。感谢所有试图帮助我解决这个问题的人!:)

于 2013-04-04T03:20:46.180 回答
0

听起来您正在通过混合托管对象和分离对象来破坏对象。您不应该将两者混为一谈。

如果要更改对象的引用,您应该在同一持久性上下文(EntityManager/transaction)中为新引用执行 find() 或 getReference()。

您还需要正确维护双向关系。

于 2013-03-20T13:46:49.053 回答