2013-05-15 10 views
24

La domanda è fondamentalmente lo stesso come di seguito uno:Primavera-dati JPA: salvare nuova entità riferimento a quello esistente

JPA cascade persist and references to detached entities throws PersistentObjectException. Why?

Sto creando una nuova entità che fa riferimento a uno esistente, uno indipendente. Ora, quando salvo questa entità nel mio repository di dati molla viene generata un'eccezione:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist 

se guardiamo il metodo save() nel codice sorgente di JPA dati di primavera vediamo:

public <S extends T> S save(S entity) { 

    if (entityInformation.isNew(entity)) { 
     em.persist(entity); 
     return entity; 
    } else { 
     return em.merge(entity); 
    } 
} 

e se guardiamo isNew() in AbstractEntityInformation

public boolean isNew(T entity) { 

    return getId(entity) == null; 
} 

Quindi, in pratica, se i save() una nuova entità (id == null), dati di primavera saranno sempre chiamare persistere e, quindi, questo scenario alw ays fallire.

Questo sembra essere un caso di utilizzo molto tipico quando si aggiungono nuovi elementi alle raccolte.

Come posso risolvere questo?

EDIT 1:

NOTA:

Questo problema non è direttamente correlata alla How to save a new entity that refers existing entity in Spring JPA?. Per elaborare supponiamo di ottenere la richiesta di creare la nuova entità su http. Quindi estrarre le informazioni dalla richiesta e creare la propria entità e quella esistente di riferimento. Quindi saranno sempre distaccati.

+1

Bloccato con lo stesso scenario ... qualche soluzione? – raksja

+0

No, non proprio ... Ovviamente è possibile gestire le eccezioni e quindi prima creare (persistere) il nuovo oggetto e aggiungere il riferimento dopo aver mantenuto il nuovo. Ma ciò non funziona in tutti i casi ... –

+0

Un'altra opzione è determinare se siamo in questa situazione: 'entity.getId() == null && entity.getReferencedEntity(). GetId()! = Null' e se true carica l'entità referenziata dal database. –

risposta

8

Il migliore mi è venuta è

public final T save(T containable) { 
    // if entity containable.getCompound already exists, it 
    // must first be reattached to the entity manager or else 
    // an exception will occur (issue in Spring Data JPA -> 
    // save() method internal calls persists instead of merge) 
    if (containable.getId() == null 
      && containable.getCompound().getId() != null){ 
     Compound compound = getCompoundService() 
       .getById(containable.getCompound().getId()); 
     containable.setCompound(compound); 
    } 
    containable = getRepository().save(containable); 
    return containable; 
} 

Verifichiamo se ci troviamo nella situazione problematica e se sì, solo ricaricare il soggetto esistente dal database dal suo ID e impostare il campo della nuova entità a questa istanza appena caricata. Sarà quindi allegato.

Ciò richiede che il servizio per la nuova entità contenga un riferimento al servizio dell'entità di riferimento. Questo non dovrebbe essere un problema dal momento che si sta utilizzando comunque la molla in modo che il servizio possa essere aggiunto come nuovo campo @Autowired.

Un altro problema tuttavia (nel mio caso questo comportamento è effettivamente desiderato) che non è possibile modificare l'entità esistente referenziata contemporaneamente durante il salvataggio di quella nuova. Tutte quelle modifiche saranno ignorate.

NOTA IMPORTANTE:

In molti, e probabilmente i vostri casi questo può essere molto più semplice. È possibile aggiungere un riferimento di gestore di entità al tuo servizio:

@PersistenceContext 
private EntityManager entityManager; 

ed al precedente if(){} blocco utilizzare

containable = entityManager.merge(containable); 

invece del mio codice (non testato se funziona).

Nel mio caso le classi sono astratte e targetEntity in @ManyToOne è quindi anche astratto.La chiamata di entityManager.merge (containable) direttamente porta quindi a un'eccezione. Tuttavia, se le tue lezioni sono tutte concrete, questo dovrebbe funzionare.

9

Ho avuto un problema simile in cui stavo cercando di salvare un nuovo oggetto entità con un oggetto entità già salvato all'interno.

Quello che ho fatto è stato implementato Persistable < T> e implementato isNew() di conseguenza.

public class MyEntity implements Persistable<Long> { 

    public boolean isNew() { 
     return null == getId() && 
      subEntity.getId() == null; 
    } 

Oppure si potrebbe usare AbstractPersistable e sovrascrivere l'isNew lì naturalmente.

Non so se questo sarà considerato un buon modo di gestire questo problema, ma ha funzionato abbastanza bene per me e inoltre si sente molto naturale da fare.

+0

Cosa succede se subEntity sembra essere Elenco <...> con la mappatura @OneToMany –

1

Ho lo stesso problema con un @EmbeddedId e dati aziendali come parte dell'ID.
L'unico modo per sapere se l'entità è nuovo esegue un (em.find(entity)==null)?em.persist(entity):em.merge(entity)

ma la primavera-dati fornisce solo save() metodo e non v'è alcun modo per riempire il metodo Persistable.isNew() con un metodo find().

Problemi correlati