-1

我刚刚发现 EclipseLink(2.3.0 和 2.4.1)有一个相当奇怪的问题,只是想问是否有人可以确认这是一个错误,而不仅仅是我遗漏了一些明显的东西......

基本上,我涉及两个实体,让我们简单地称它们为 A 和 B。A 有一个急切的(如果重要的话),对 B 的非级联多对一单向引用,该引用在数据库中使用连接列建模。DB 表 A 包含列 ID (PK)、B_ID(FK 到 B.ID)等,表 B 包含列 ID 等。

现在,我有一个 As 列表,应该使用对新 B 实例的引用进行更新。在伪代码中类似于:

for(A a : list) {
    // using some criteria, check if perhaps a matching B
    // entity is already found in the database
    B b = perhapsGetExistingBFromDatabase();

    if(b == null) {
        // no existing B was found, create a new
        b = new B();
        // set values in B
        b.setFoo(4711),
        b.setBar(42);

        // save B
        em.merge(b);
    }

    // set reference from A to B ...
    a.setB(b);
    // ... and update A
    em.merge(a);
}

由于引用是非级联的,因此有必要合并 b 和 a。一切都按预期工作。

然后,某人(我)将关系的级联类型从无更改为合并/持久,因为代码中的其他地方需要这样做。我希望旧代码能够工作,合并 b 并不是真正需要的,恕我直言不应该受到伤害吗?一个简短的测试确认它仍然有效,插入了新的 B 实体并相应地更新了 A。

但是,它仅在列表中只有一个 A 实体时才有效。第二次运行循环会导致 EclipseLink 自动刷新会话,因为perhapsGetExistingBFromDatabase执行了“SELECT .. FROM B”,缓存了一个合并的 B 实体,它希望数据库表是最新的。使用代码中的 FINEST 日志记录级别和断点,我可以验证 EclipseLink 确定需要为新的 B 实体生成一个 id,它调用序列生成器并在实体的正确字段中设置 id。尽管如此,EclipseLink 仍会调用类似于以下的 SQL 语句:

INSERT INTO B (ID, ...) VALUES(0, ...);
UPDATE A SET B_ID = 0 WHERE ID = ...;

生成的 id 在某处丢失,EclipseLink 尝试创建一个 id=0 的新实体。数据确实是无效的,后来,EclipseLink 也抛出了 PersistenceException:在工作单元克隆中遇到 Null 或零主键。

错误还是我的错误?


编辑:詹姆斯要求 B.ID 的映射:

@Id
@GeneratedValue(generator = "sq_idgen", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "sq_idgen", sequenceName = "...", allocationSize = 100)
@Column(name = "id")
protected long id;

请注意,删除不必要的内容em.merge(b);可以解决我的问题。对我来说,为什么调用合并会导致 EclipseLink 完全失败,试图插入一个没有填充 id 的 B 实例,这对我来说并不明显。

4

1 回答 1

1

奇怪的是,B 的 Id 是如何映射的?

似乎合并后的 B 可能会以某种方式获得两个单独的实例(因为没有任何东西可以将它们识别为相同的,因为它们没有 ID)。并不是说 merge() 通常不需要,它仅对分离的对象需要,例如在使用序列化时(尝试使用 persist 代替)。

为避免刷新,您可以将 EntityManager 或持久性单元中的 flushMode 设置为 COMMIT 而不是 AUTO。

于 2013-03-05T15:29:59.947 回答