Java Eclipselink插入ID为0的实体
我刚刚发现EclipseLink(2.3.0和2.4.1)有一个相当奇怪的问题,我只是想问问是否有人能确认这是一个bug,而不仅仅是我遗漏了一些明显的东西 基本上,我涉及到两个实体,让我们简单地称它们为A和B。A有一个渴望的(如果这有关系的话),对B的非级联多对一单向引用,在数据库中用连接列建模。DB表A包含列ID(PK)、B_ID(FK到B.ID)等,表B包含列ID等 现在,我有了一个As列表,应该用对新B实例的引用来更新它。在伪代码中,类似于:Java Eclipselink插入ID为0的实体,java,jpa,eclipselink,Java,Jpa,Eclipselink,我刚刚发现EclipseLink(2.3.0和2.4.1)有一个相当奇怪的问题,我只是想问问是否有人能确认这是一个bug,而不仅仅是我遗漏了一些明显的东西 基本上,我涉及到两个实体,让我们简单地称它们为A和B。A有一个渴望的(如果这有关系的话),对B的非级联多对一单向引用,在数据库中用连接列建模。DB表A包含列ID(PK)、B_ID(FK到B.ID)等,表B包含列ID等 现在,我有了一个As列表,应该用对新B实例的引用来更新它。在伪代码中,类似于: for(A a : list) { /
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。一切如期进行
然后,有人(我)将关系的级联类型从none更改为merge/persist,因为这在代码的其他地方是必需的。我希望旧代码可以工作,合并b并不是真的需要,IMHO不应该受到伤害吗?一个简短的测试确认它仍然有效,插入了新的B实体,并相应地更新了A
但是,只有当列表中只有一个实体时,它才起作用。第二次运行循环会导致EclipseLink自动刷新会话,因为可能存在bFromDatabase
执行了一个“SELECT..FROM B”,缓存了一个合并的B实体,它希望数据库表是最新的。使用代码中最精细的日志记录级别和断点,我可以验证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或零主键
虫子还是我的错
编辑:James要求提供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)代码>解决了我的问题。我不明白为什么调用merge会导致EclipseLink完全失败,试图插入一个没有填充id的B实例。奇怪的是,B的id是如何映射的
似乎合并后的数据库可能会以某种方式获得两个单独的B实例(因为没有标识它们是相同的,因为它们没有Id)。并不是说merge()通常不是必需的,它只对分离的对象是必需的,例如在使用序列化时(尝试使用persist)
为了避免刷新,您可以在EntityManager或持久性单元中将flushMode设置为COMMIT而不是AUTO。我已将id映射添加到原始问题中。在本例中,合并b实际上是必需的,因为它是一个新对象。若我将它持久化,它的id就不会立即更新,也就无法将它和现有的a实例连接起来。在这种情况下,自动刷新模式也是正确的,因为我可能会创建B的新实例,这可以在以后的循环迭代中通过perhapsGetExistingBFromDatabase
找到。