我有两个具有单向多对一关系的实体:
- 许多人
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”标识的唯一或主键约束或唯一索引中出现重复键值。
newFoo
Eclipselink在刷新 的更改时尝试插入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,但它也失败了)
谢谢!
请注意:我知道我可以通过
- 将分离的合并
Foo
到更新时使用的相同持久性上下文中bar
,然后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!
}