[Java] JPA / Hibernate: entidad separada pasada para persistir



Answers

La solución es simple, simplemente use CascadeType.MERGE lugar de CascadeType.PERSIST o CascadeType.ALL .

He tenido el mismo problema y CascadeType.MERGE ha funcionado.

Espero que estés ordenado

Question

Tengo un modelo de objetos persistido por JPA que contiene una relación muchos a uno: una cuenta tiene muchas transacciones. Una transacción tiene una cuenta.

Aquí hay un fragmento del código:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

Puedo crear un objeto Cuenta, agregarle transacciones y mantener el objeto Cuenta correctamente. Pero, cuando creo una transacción, usando una cuenta ya existente y persistiendo la transacción , recibo una excepción:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141) 

Entonces, puedo persistir una Cuenta que contiene transacciones, pero no una Transacción que tiene una Cuenta. Pensé que esto se debía a que la Cuenta podría no estar adjunta, pero este código todavía me da la misma excepción:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

¿Cómo puedo guardar correctamente una transacción asociada a un objeto de cuenta ya existente?




Tal vez es el error de OpenJPA, cuando se retrotrae restablece el campo @Version, pero el pcVersionInit se mantiene verdadero. Tengo un AbstraceEntity que declaró el campo @Version. Puedo solucionarlo reiniciando el campo pcVersionInit. Pero no es una buena idea. Creo que no funciona cuando la entidad persiste en cascada.

    private static Field PC_VERSION_INIT = null;
    static {
        try {
            PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
            PC_VERSION_INIT.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
        }
    }

    public T call(final EntityManager em) {
                if (PC_VERSION_INIT != null && isDetached(entity)) {
                    try {
                        PC_VERSION_INIT.set(entity, false);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                    }
                }
                em.persist(entity);
                return entity;
            }

            /**
             * @param entity
             * @param detached
             * @return
             */
            private boolean isDetached(final Object entity) {
                if (entity instanceof PersistenceCapable) {
                    PersistenceCapable pc = (PersistenceCapable) entity;
                    if (pc.pcIsDetached() == Boolean.TRUE) {
                        return true;
                    }
                }
                return false;
            }



Usar la combinación es arriesgado y complicado, por lo que es una solución sucia en tu caso. Debe recordar al menos que cuando pasa un objeto de entidad para fusionarse, deja de estar adjunto a la transacción y en su lugar se devuelve una nueva entidad ahora adjunta. Esto significa que si alguien tiene el antiguo objeto de la entidad en su poder, los cambios se ignoran silenciosamente y se descartan en commit.

No está mostrando el código completo aquí, por lo que no puedo verificar su patrón de transacción. Una forma de llegar a una situación como esta es si no tiene una transacción activa al ejecutar la fusión y persiste. En ese caso, se espera que el proveedor de persistencia abra una nueva transacción para cada operación JPA que realice e inmediatamente la comprometa y la cierre antes de que la llamada regrese. Si este es el caso, la fusión se ejecutará en una primera transacción y luego, una vez que el método de fusión retorna, la transacción se completa y se cierra, y la entidad devuelta se separa ahora. La persistencia debajo de ella abriría una segunda transacción e intentaría referirse a una entidad que está separada, dando una excepción. Siempre envuelva su código dentro de una transacción a menos que sepa muy bien lo que está haciendo.




En la definición de su entidad, no especifica el @JoinColumn para la Account unida a una Transaction . Querrás algo como esto:

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}

EDITAR: Bueno, supongo que sería útil si estuvieras usando la anotación @Table en tu clase. Je. :)




Necesita establecer Transacción para cada Cuenta.

foreach(Account account : accounts){
    account.setTransaction(transactionObj);
}

O bien, será suficiente (si corresponde) para establecer los identificadores nulos en muchos lados.

// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());

foreach(Account account : accounts){
    account.setId(null);
}

transactionObj.setAccounts(accounts);

// just persist transactionObj using EntityManager merge() method.





Links