2012-09-11 15 views
7

Tengo un programa JPA donde EclipseLink es el proveedor de Persistencia. Cuando fusiono una entidad de usuario, cambio su ID y trato de fusionar la misma instancia de usuario nuevamente, se produce un error. Reescribo mi código para ilustrar mi problema de la manera más simple.Fusionar una entidad, cambiar su ID, fusionar de nuevo, causar "asignada a una columna de clave principal en la base de datos. No se permiten actualizaciones" error

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

El código anterior no está en el contexto de una transacción. userManager es un bean de sesión sin estado con un EntityManager inyectado. Cuando se ejecuta, la consola imprime:

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. 

La excepción se produce en la segunda invocación merge().

Si creo un nuevo usuario, establece su ID y fusionarlo, funciona:

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); 

Entonces, ¿cuál es la diferencia entre el primer y el segundo escenario de uno? De acuerdo con la especificación JPA, siempre que la entidad esté en estado separado, la fusión debería tener éxito, ¿no? (suponiendo que exista la entidad con ID = 2)

¿Por qué al proveedor de EclipseLink parece molestarle el hecho de que la entidad de usuario se haya fusionado anteriormente?

Actualización: Parece ser un error de EclipseLink. He sustituido el proveedor de persistencia de EclipseLink a Hibernate:

puedo cambiar

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

a

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

Sin error ha sido lanzado.

+0

es tu ID un valor generado? – kostja

+0

Sí, es un valor generado –

Respuesta

2

Parece ser un error de EclipseLink. He cambiado el proveedor de persistencia de EclipseLink a Hibernate:

de

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

a

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

Sin error ha sido lanzado.

La versión de EclipseLink es 2.3.2. (que se envía con el último servidor de aplicaciones de Glassfish 3.1.2).

La versión de hibernate es, a partir de ahora, la más reciente, 4.1.7.

+1

Todavía ocurre con la versión 2.6.3 –

3

La razón es que el Id se puede insertar/definir, como lo hace en su segundo ejemplo, pero no se ha modificado/actualizado, como lo intenta en su primer ejemplo. El proveedor de JPA intenta reflejar el cambio en la base de datos y falla.

JPA 2 § 2.4 especificación dice

La aplicación no debe cambiar el valor de la clave primaria. El comportamiento no está definido si esto ocurre.

+0

Pero la entidad ya está separada. Cuando cambia el Id de una entidad separada, el cambio no se reflejará en la base de datos. –

+0

@MingtaoSun sí, si la entidad no se gestiona, el error debería ocurrir en la llamada 'merge', no en' setId'. ¿Dónde ocurre? Algo extraño es que la entidad devuelta por el EM se dice que no está administrada. El EM solo puede devolver objetos contenidos en el contexto de presistencia correspondiente y, por lo tanto, solo los gestionados. Si el error ocurre en 'setId', intente desconectar la entidad de forma explícita llamando' detach' o 'clear' en el EM. – kostja

+0

El error ocurrió en merge() en lugar de setId(). De acuerdo con la especificación de JPA, cuando la entidad está separada, se puede fusionar(). –

0

Esta respuesta es 4 años de retraso, pero de todos modos.

Puede actualizarlo mediante la ejecución de actualización periódica consultas SQL o usandoJPQL o los criterios del API. Encuentro que el último es el mejor.

Aquí hay un ejemplo de código que puede hacer el truco. Lo probé en una situación similar y funciona bien con 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(); 

En lugar de User_.id puede pasar el nombre del campo como una cadena, por ejemplo, "id".

Otro ejemplo http://www.thoughts-on-java.org/criteria-updatedelete-easy-way-to/

Cuestiones relacionadas