2012-06-14 12 views
17

ho un rapporto bidirezionale uno-a-molti con le seguenti classi di entità:JPA con JTA: persist entità e unire entità figlio cascata

0 o 1 client < -> 0 o più ordini dei prodotti

Quando persiste l'entità client, desidero che anche le entità ordine prodotto associate siano mantenute (poiché la loro chiave esterna per il client "principale" potrebbe essere stata aggiornata).

Naturalmente tutte le opzioni CASCADE richieste sono impostate sul lato client. Ma non funziona se un cliente appena creato viene mantenuto per la prima volta, mentre fa riferimento a un ordine prodotto esistente come in questo scenario:

  1. ordine prodotto '1' è stato creato e persistente. Funziona bene.
  2. Il client '2' viene creato e l'ordine del prodotto '1' viene aggiunto all'elenco dei suoi ordini di prodotti. Quindi è persistito. Non funziona.

Ho provato diversi apporache, ma nessuno di loro ha mostrato il risultato previsto. Vedi quei risultati qui sotto. Ho letto tutte le domande correlate qui, ma non mi hanno aiutato. Uso EclipseLink 2.3.0, pure JPA 2.0 Annotations e JTA come tipo di transazione su un DB in memoria di Apache Derby (JavaDB) su GlassFish 3.1.2. Le relazioni di entità sono gestite da una GUI JSF. La gestione delle relazioni a livello di oggetto funziona (a parte il persistere), l'ho testato con i test JUnit.

Approccio 1) "Default" (sulla base di modelli di NetBeans classe)

Cliente:

@Entity 
public class Client implements Serializable, ParentEntity { 
    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 

    @OneToMany(mappedBy = "client", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, 
      fetch= FetchType.LAZY) 
    private List<ProductOrder> orders = new ArrayList<>(); 

    // other fields, getters and setters 
} 

ProductOrder:

@Entity 
public class ProductOrder implements Serializable, ChildEntity { 
    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 

    @ManyToOne // owning side 
    private Client client; 

    // other fields, getters and setters 
} 

Generico Persistenza Facciata:

// Called when pressing "save" on the "create new..." JSF page 
public void create(T entity) { 
    getEntityManager().persist(entity); 
} 

// Called when pressing "save" on the "edit..." JSF page 
public void edit(T entity) { 
    getEntityManager().merge(entity); 
} 

Risultato:

creare() genera questa eccezione immediatamente:

Attenzione: un'eccezione di sistema si è verificato nel corso di una chiamata su EJB metodo ClientFacade public void javaee6test.beans.AbstractFacade.create (java.lang .Object) javax.ejb.EJBException: Operazione interrotta ...

causato da: javax.transaction.RollbackException: transazione contrassegnata per il rollback. ...

Causato da: eccezione [EclipseLink-4002] (Eclipse Persistenza Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException interne Eccezione: java.sql. SQLIntegrityConstraintViolationException: lo stato è stato interrotto perché avrebbe causato un valore chiave duplicato in un vincolo di chiave univoco o primario o indice univoco identificato da "SQL120513133540930" definito su "PRODUCTORDER".Codice di errore: -1 Chiama: INSERISCI IN PRODUCTORDER (ID, CLIENT_ID) VALUES (?,?) Bind => [2 parametri associati] Query: InsertObjectQuery (javaee6test.model.ProductOrder [id = 1]) ...

causato da: java.sql.SQLIntegrityConstraintViolationException: La dichiarazione è stata interrotta perché avrebbe causato un valore di chiave duplicata in un unico o primaria vincolo di chiave o indice univoco identificato da 'SQL120513133540930' definito in 'PRO- DUCTORDER'. ...

causato da: org.apache.derby.client.am.SqlException: La dichiarazione è stata interrotta essere le cause che avrebbe causato un valore chiave duplicata in un unico o primario con-Straint tasto o indice univoco identificato da 'SQL120513133540930' definito su 'PRODUCTOR-DER'.

Non capisco questa eccezione. edit() funziona bene. Ma vorrei aggiungere ordini di prodotti a un cliente al momento della sua creazione, quindi questo è insufficiente.

Approccio 2) merge() solo

Modifiche a Generic Persistenza Facciata:

// Called when pressing "save" on the "create new..." JSF page 
public void create(T entity) { 
    getEntityManager().merge(entity); 
} 

// Called when pressing "save" on the "edit..." JSF page 
public void edit(T entity) { 
    getEntityManager().merge(entity); 
} 

Risultato:

su Create(), l'uscita di registrazione EclipseLink dice:

Fine: INSERIRE I VALORI DEL CLIENT (ID, NAME, ADDRESS_ID) (?, ?,?) bind => [3 parametri legati]

ma NO "UPDATE" sul tavolo ordine del prodotto. Quindi, la relazione non è stabilita. Di nuovo, edit(), d'altra parte, funziona bene.

suddetto approccio 3) Id GenerationType.IDENTITY su entrambi i tipi di entità

modifiche sia del cliente e l'ordine prodotto di classe:

... 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id; 
... 

Risultato:

su Create(), l'EclipseLink Uscita di registrazione dice:

Fine: INSERT I NTO CLIENT (nome, address_id) VALORI legano => [2 parametri legati]

Belle (,??): Valori IDENTITY_VAL_LOCAL()

fine: INSERT INTO PRODUCTORDER (Data ordine, client_id) VALORI (? ,?) si legano => [2 parametri legati]

Belle: valori IDENTITY_VAL_LOCAL()

così invece di estabilshing una relazione con l'ordine del prodotto aggiunto alla lista del cliente, viene creata una nuova entità ordine prodcut e persistente (!) e viene stabilita una relazione con tale entità. Lo stesso qui, edit() funziona bene.

suddetto approccio 4) Approccio (2) e (3) combinato

Risultato: come Approach (2).

La mia domanda è: Esiste un modo per realizzare lo scenario sopra descritto? Come può essere ottenuto? Vorrei restare con JPA (nessuna soluzione specifica del fornitore).

risposta

0

Utilizzare l'annotazione @Joincolumn nell'entità ProductOrder, vedere di seguito.

@Entity 
    public class ProductOrder implements Serializable, ChildEntity { 
    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 

    @ManyToOne // owning side 
    @JoinColumn(name = "PRODUCTORDER_ID", referencedColumnName = "ID") 
    //write your database column names into name and referencedColumnName attributes. 
    private Client client; 

    // other fields, getters and setters 
    } 
+1

No, questo non funziona. In realtà non cambia nulla. Ho provato con '@JoinColumn (name =" CLIENT_ID ", referencedColumnName =" ID ") ', ma non ha funzionato. – SputNick

0

Assicurarsi che si sta configurando entrambi i lati della relazione, non si può semplicemente aggiungere l'ordine al cliente, è anche necessario impostare il client dell'ordine. Inoltre è necessario unire entrambi gli oggetti che sono stati modificati. Se unisci semplicemente il client, il client dell'ordine non verrà unito (anche se la tua cascata ne provocherà la fusione).

persist non funziona, poiché persist richiede che l'oggetto che viene mantenuto sia corretto per il contesto di persistenza, ovvero non riferimento agli oggetti distaccati, deve fare riferimento agli oggetti gestiti.

Il tuo problema deriva dal distacco degli oggetti. Se non hai scollegato gli oggetti, non avresti gli stessi problemi. Normalmente in JPA creerai un EntityManager, troverai/interroghi i tuoi oggetti, li modifichi/li mantenga, call commit. Non è richiesta alcuna fusione.

+6

Ho trovato una soluzione simile ai tuoi consigli: ho rimosso le opzioni 'cascade' e ho persistito/unito" dipendenti "manualmente, ovvero mantenendo un elenco di entità" dipendenti "e unendole quando il" genitore "è persistente/fuse. MA Non accetterò questa risposta in quanto questo potrebbe non essere mai l'applet del "JPA ufficiale". Inoltre, non scollego esplicitamente alcuna entità e la registrazione di EclipseLink non menziona alcuna operazione di scollegamento. Anche io non capisco cosa siano queste opzioni "a cascata", poiché ovviamente non funzionano. – SputNick

+0

4 anni dopo e ho dovuto fare la stessa identica cosa. All'inizio, ho seguito l'approccio del libro usando le annotazioni a cascata JPA-only, ma alla fine ho dovuto rimuovere le annotazioni e persistere le mie entità padre, aggiungerle di nuovo ad ogni entità child e persistendole usando l'unione. –

7

Hi Ho avuto lo stesso problema di oggi, chiedo alla mailing list OpenJPA con questa email:

Hi. Ho un problema con l'inserimento e l'aggiornamento di un riferimento nella stessa entità.

Sto cercando di inserire un nuovo oggetto (Esame) che ha un riferimento a un altro oggetto (Persona) e allo stesso tempo voglio aggiornare un attributo (data di nascita) dell'oggetto Person. L'aggiornamento non avviene mai, anche se ho impostato CascadeType su ALL. L'unico modo in cui funziona è un persistere e successivamente un'operazione di unione. È normale? Devo cambiare qualcosa ??

Non mi piace l'idea di un "aggiornamento manuale" utilizzando l'unione nell'oggetto Persona perché non conosco il numero di oggetti (oggetto figlio dell'esame) che l'utente desidera aggiornare.

Entità:

public class Exam{ 
    @ManyToOne(cascade= CascadeType.ALL) 
    @JoinColumn(name = "person_id") 
    public Person person; 
...... 
} 

public class Person{ 
    private Date birthDate; 
    @OneToMany(mappedBy = "person") 
    private List<Exam> exams 
....... 
} 

public class SomeClass{ 
    public void someMethod(){ 
     exam = new Exam() 
     person.setBirthDate(new Date()); 
     exam.setPerson(person); 
     someEJB.saveExam(exam); 
    } 
} 

public class someEJB(){ 

    public void saveExam(Exam exam){ 
     ejbContext.getUserTransaction().begin(); 
     em.persist(exam); 
     //THIS WORKS 
     em.merge(exam.getPerson()); 
     ejbContext.getUserTransaction().commit();  
    } 

} 

devo utilizzare il metodo di unione per ogni oggetto bambino?

E la risposta è stata questa:

Sembra che il tuo problema è che l'esame è nuovo, ma la persona è esistente e l'Entità esistente viene ignorato quando a cascata l'persistono operazione. Credo che funzioni come previsto.

Fintanto che le relazioni sono impostate su CascadeType.ALL, è sempre possibile modificare em.persist (esame); em.merge (esame) ;. Questo avrebbe risolto persistendo il nuovo esame e avrebbe anche collegato in cascata la chiamata di unione alla persona .

Grazie, Rick


spero che questo ti può aiutare.

Problemi correlati