2012-09-11 10 views
7

Ho un programma JPA in cui EclipseLink è il provider di Persistenza. Quando unisco un'entità utente, ne cambio l'ID e provo a unire nuovamente la stessa istanza utente, viene generato un errore. Riscrivo il mio codice per illustrare il mio problema nel modo più semplice.Unisci un'entità, modifica il relativo ID, unisci di nuovo, causa "mappato a una colonna chiave primaria nel database. Aggiornamenti non consentiti" Errore

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

Il codice sopra riportato non è in un contesto di transazione. userManager è un bean di sessione stateless con un EntityManager iniettato. Quando viene eseguita, la console stampa:

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. 

L'eccezione si verifica al secondo richiamo di merge().

Se creo un nuovo utente, imposta il suo ID e si fondono, funziona:

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

Allora, qual è la differenza tra il primo scenario e la seconda? Secondo la specifica JPA, finché l'entità è in stato di distacco, l'unione dovrebbe avere successo, giusto? (Supponendo che l'entità con ID = 2 esista)

Perché il provider EclipseLink sembra essere infastidito dal fatto che l'entità utente è stata unita prima?

Aggiornamento: Sembra un bug di EclipseLink. Ho sostituito il provider di persistenza da EclipseLink a Ibernazione:

posso cambiare

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

a

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

Nessun errore è stato gettato.

+0

solamente dell'ID di un valore generato? – kostja

+0

Sì, è un valore generato –

risposta

2

Sembra essere un bug di EclipseLink. Ho cambiato il provider di persistenza da EclipseLink a Ibernazione:

da

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

a

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

Nessun errore è stato gettato.

La versione di EclipseLink è 2.3.2. (che viene fornito con l'ultimo server di applicazione Glassfish 3.1.2).

La versione di hibernate è, a partire da ora l'ultima, 4.1.7.

+1

Si verifica ancora con la versione 2.6.3 –

3

Il motivo è che il Id può essere inserito/definito, come si fa nel secondo esempio, ma non modificato/aggiornato, come si prova nel primo esempio. Il provider JPA tenta di riflettere la modifica nel database e non riesce.

JPA 2 spec §2.4 dice

L'applicazione non deve modificare il valore della chiave primaria. Il comportamento non è definito se si verifica ciò.

+0

Ma l'entità è già scollegata. Quando si modifica l'ID di un'entità distaccata, la modifica non si rifletterà nel database. –

+0

@MingtaoSun yes, se l'entità non è gestita, l'errore dovrebbe verificarsi sulla chiamata 'merge', non su' setId'. Dove si verifica? Una cosa strana è che l'entità restituita dal EM non è gestita. Il EM può restituire solo gli oggetti contenuti nel corrispondente contesto di presistenza e quindi solo quelli gestiti. Se l'errore si verifica in "setId", prova a separare esplicitamente l'entità chiamando 'detach' o' clear' sull'EM. – kostja

+0

L'errore si è verificato in unione() anziché in setId(). Secondo la specifica JPA, quando l'entità è staccata, può essere unita(). –

0

Questa risposta è di 4 anni di ritardo, ma comunque.

È possibile aggiornarlo eseguendo regolari aggiornamento interroga utilizzando SQL o JPQL o Criteri API. Trovo che l'ultimo sia il migliore.

Ecco un esempio di codice che può fare il trucco. L'ho provato in una situazione simile e funziona perfettamente 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(); 

Invece di User_.id è possibile passare il nome del campo come String per esempio "id".

Un altro esempio http://www.thoughts-on-java.org/criteria-updatedelete-easy-way-to/

Problemi correlati