Esecuzione di un esempio molto semplice di relazione uno-a-molti (stato ->
Paese).Impedisci l'ibernazione dall'eliminazione di entità orfane durante la fusione di un'entità con associazioni di entità con orphanRemoval impostato su true
Paese (lato inverso):
@OneToMany(mappedBy = "country", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<StateTable> stateTableList=new ArrayList<StateTable>(0);
StateTable (lato possedere):
@JoinColumn(name = "country_id", referencedColumnName = "country_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Country country;
Il metodo si tenta di aggiornare una dotazione (monofamiliare) StateTable
entità all'interno di una transazione di database attivo (JTA o risorsa locale):
public StateTable update(StateTable stateTable) {
// Getting the original state entity from the database.
StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId());
// Get hold of the original country (with countryId = 67, for example).
Country oldCountry = oldState.getCountry();
// Getting a new country entity (with countryId = 68) supplied by the client application which is responsible for modifying the StateTable entity.
// Country has been changed from 67 to 68 in the StateTable entity using for example, a drop-down list.
Country newCountry = entityManager.find(Country.class, stateTable.getCountry().getCountryId());
// Attaching a managed instance to StateTable.
stateTable.setCountry(newCountry);
// Check whether the supplied country and the original country entities are equal.
// (Both not null and not equal - http://stackoverflow.com/a/31761967/1391249)
if (ObjectUtils.notEquals(newCountry, oldCountry)) {
// Remove the state entity from the inverse collection held by the original country entity.
oldCountry.remove(oldState);
// Add the state entity to the inverse collection held by the newly supplied country entity
newCountry.add(stateTable);
}
return entityManager.merge(stateTable);
}
Va notato che orphanRemoval
è impostato su true
. L'entità StateTable
viene fornita da un'applicazione client che è interessata a modificare l'associazione di entità Country
(countryId = 67
) in StateTable
in qualcos'altro (countryId = 68
) (quindi sul lato opposto in JPA, migrando un'entità figlio dalla sua genitrice (raccolta) a un'altra genitore (raccolta) che a sua volta orphanRemoval=true
si oppone).
Il provider di Hibernate emette un'istruzione DML DELETE
che provoca la rimozione della riga corrispondente all'entità StateTable
dalla tabella del database sottostante.
Nonostante il fatto che orphanRemoval
è impostato su true
, mi aspetto Hibernate ad emettere un regolare UPDATE
DML dichiarazione provocando l'effetto di orphanRemoval
essere sospesa nella sua interezza perché il collegamento rapporto è migrato (non eliminati semplicemente).
EclipseLink fa esattamente questo lavoro. Emette un'istruzione UPDATE
nello scenario indicato (con la stessa relazione con orphanRemoval
impostata su true
).
Quale si sta comportando in base alle specifiche? È possibile rendere il problema di Hibernate un'istruzione UPDATE
in questo caso diversa dalla rimozione di orphanRemoval
dal lato inverso?
Questo è solo un tentativo di fare una relazione bidirezionale più coerente su entrambi i lati.
Le modalità di gestione collegamento difensive cioè add()
e remove()
utilizzato nel frammento di sopra, se necessario, sono definiti nell'entità Country
come segue.
public void add(StateTable stateTable) {
List<StateTable> newStateTableList = getStateTableList();
if (!newStateTableList.contains(stateTable)) {
newStateTableList.add(stateTable);
}
if (stateTable.getCountry() != this) {
stateTable.setCountry(this);
}
}
public void remove(StateTable stateTable) {
List<StateTable> newStateTableList = getStateTableList();
if (newStateTableList.contains(stateTable)) {
newStateTableList.remove(stateTable);
}
}
Aggiornamento:
Hibernate può solo rilasciare un UPDATE
DML dichiarazione previsto, se il codice data si modifica nel modo seguente.
public StateTable update(StateTable stateTable) {
StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId());
Country oldCountry = oldState.getCountry();
// DELETE is issued, if getReference() is replaced by find().
Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId());
// The following line is never expected as Country is already retrieved
// and assigned to oldCountry above.
// Thus, oldState.getCountry() is no longer an uninitialized proxy.
oldState.getCountry().hashCode(); // DELETE is issued, if removed.
stateTable.setCountry(newCountry);
if (ObjectUtils.notEquals(newCountry, oldCountry)) {
oldCountry.remove(oldState);
newCountry.add(stateTable);
}
return entityManager.merge(stateTable);
}
Osservare le seguenti due righe nella versione più recente del codice.
// Previously it was EntityManager#find()
Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId());
// Previously it was absent.
oldState.getCountry().hashCode();
Se sia l'ultima riga è assente o EntityManager#getReference()
è sostituito dal EntityManager#find()
, poi una dichiarazione DELETE
DML è inaspettatamente rilasciato.
Quindi, cosa sta succedendo qui? Soprattutto, sottolineo la portabilità. Non trasferire questo tipo di funzionalità di base di base tra diversi provider JPA impedisce gravemente l'utilizzo dei framework ORM.
Capisco la differenza di base tra EntityManager#getReference()
e EntityManager#find()
.
Solo un pensiero ma fa alcuna differenza se si fa l'add prima della rimozione? –
Cambiare l'ordine di quelle due linee non fa alcuna differenza. Ho verificato in anticipo tali possibilità :) – Tiny
Sembra un bug Hibernate. Solo un'ipotesi, fondere esplicitamente il nuovo paese e quindi il vecchio paese prima dello stato. Se necessario, richiamare il colore intermedio. – BalusC