合并实体,更改其ID,再次合并,导致“映射到数据库中的主键列。不允许更新”错误

时间:2012-09-11 06:39:24

标签: java hibernate java-ee jpa eclipselink

我有一个JPA程序,其中EclipseLink是持久性提供程序。当我合并用户实体,更改其ID,并尝试再次合并同一个用户实例时,会引发错误。我重写我的代码以最简单的方式说明我的问题。

User user = userManager.find(1);
userManager.merge(user);
System.out.println("User is managed? "+userManager.contains(user);
user.setId(2);
userManager.merge(user);

上述代码不在事务上下文中。 userManager是一个无状态会话bean,注入了EntityManager。执行时,控制台打印:

User is managed? false

Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.1.3.v20110304-r9073): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [demo.model.User] is mapped to a primary key column in the database. Updates are not allowed.

在第二次merge()调用时发生异常。

如果我创建一个新用户,设置其ID并合并它,它可以工作:

User user = userManager.find(1);
userManager.merge(user);
System.out.println("User is managed? "+userManager.contains(user);
User newUser = new User();
newUser.setId(2);
userManager.merge(newUser);

那么第一个场景和第二个场景之间有什么区别?根据JPA规范,只要实体处于分离状态,合并就应该成功,对吧? (假设存在ID = 2的实体)

为什么EclipseLink提供商似乎对以前合并用户实体的事实感到困扰?

更新:这似乎是EclipseLink的一个错误。我已经将EclipseLink中的持久性提供程序替换为Hibernate:

我改变了

<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<provider>org.hibernate.ejb.HibernatePersistence</provider>    

没有抛出任何错误。

4 个答案:

答案 0 :(得分:3)

原因是Id可能会插入/定义 - 正如您在第二个示例中所做的那样,但未更改/更新 - 正如您在第一个示例中所尝试的那样。 JPA提供程序尝试反映数据库中的更改并失败。

JPA 2spec§2.4说

  

应用程序不得更改主键的值。该   如果发生这种情况,行为是不确定的。

答案 1 :(得分:3)

这似乎是EclipseLink的一个错误。我已经将持久性提供程序从EclipseLink更改为Hibernate:

<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<provider>org.hibernate.ejb.HibernatePersistence</provider>  

没有抛出任何错误。

EclipseLink的版本是2.3.2。 (随最新的Glassfish应用服务器3.1.2一起提供)。

hibernate的版本,截至目前最新的4.1.7。

答案 2 :(得分:1)

在persistence.xml中尝试<property name="eclipselink.weaving.internal" value="false"/> http://blogs.nologin.es/rickyepoderi/index.php?/archives/95-Weaving-Problem-in-EclipseLink.html

答案 3 :(得分:1)

这个答案迟了4年但无论如何。

您可以使用SQL或 JPQL 标准API 执行常规更新查询来更新它。我发现最后一个是最好的。

这是一个可以解决问题的代码示例。我在类似的情况下尝试过它,它可以与EclipseLink一起使用。

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<User> cu = cb.createCriteriaUpdate(User.class);
Root<User> c = cu.from(User.class);
cu.set(User_.id, newId).where(    cb.equal(c.get(User_.id), oldId)    );
em.createQuery(cu).executeUpdate();

您可以将字段名称作为字符串传递,而不是 User_.id &#34; ID&#34;

另一个例子http://www.thoughts-on-java.org/criteria-updatedelete-easy-way-to/