java example - JPA / Hibernate: entidad separada pasada para persistir





ejemplos tutorial (9)


Este es un problema de consistencia bidireccional típico. Está bien discutido en este enlace , así como en este enlace.

De acuerdo con los artículos en los 2 enlaces anteriores, debe arreglar sus setters en ambos lados de la relación bidireccional. Un setter de ejemplo para One side está en este enlace.

Un setter de ejemplo para el lado Muchos está en este enlace.

Después de corregir a los instaladores, quiere declarar que el tipo de acceso a la Entidad es "Propiedad". La mejor práctica para declarar el tipo de acceso "Propiedad" es mover TODAS las anotaciones desde las propiedades del miembro a los getters correspondientes. Una gran advertencia es no mezclar los tipos de acceso "Campo" y "Propiedad" dentro de la clase de entidad, de lo contrario el comportamiento no está definido por las especificaciones JSR-317.

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?




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.




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.



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



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. :)




No pase id (pk) para persistir el método o pruebe el método save () en lugar de persist ().




Si nada es útil y aún obtiene esta excepción, revise sus métodos equals() y no incluya la colección de elementos secundarios en ella. Especialmente si tiene una estructura profunda de colecciones incrustadas (por ejemplo, A contiene Bs, B contiene Cs, etc.).

En el ejemplo de Account -> Transactions :

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

En el ejemplo anterior, elimine las transacciones de las verificaciones equals() . Esto se debe a que hibernar implicará que no está tratando de actualizar un objeto antiguo, pero le pasará un objeto nuevo para que persista siempre que cambie el elemento en la colección secundaria.
Por supuesto, estas soluciones no se adaptarán a todas las aplicaciones y debe diseñar cuidadosamente lo que desea incluir en los métodos equals y hashCode .




Probablemente en este caso haya obtenido su objeto de account utilizando la lógica de fusión, y persist se usa para persistir nuevos objetos y se quejará si la jerarquía tiene un objeto ya existente. Deberías usar saveOrUpdate en tales casos, en lugar de persist .




JPA es solo una especificación. En el mercado hay muchos proveedores que implementan JPA. Diferentes tipos de proveedores implementan JPA de manera diferente. por lo tanto, los diferentes tipos de proveedores brindan una funcionalidad diferente, así que elija un proveedor adecuado en función de sus requisitos.

Si está utilizando Hibernate o cualquier otro proveedor en lugar de JPA, no puede moverlo fácilmente para hibernar a EclipseLink o OpenJPA a Hibernate.Pero si usa JPA, entonces solo tiene que cambiar la provisión en el archivo XML de persistencia. JPA.





java hibernate jpa