Estoy trabajando en un proyecto en WebSphere 8.5.5 (OpenJPA 2.2.3) que necesita crear y fusionar en cascada a través de un gran modelo de entidad anotada JPA. Tenemos un problema muy específico al fusionar nietos, ya sea llamando a EntityManager.merge () en el abuelo o mediante el vaciado desencadenado en la confirmación de una transacción. Aquí están los detalles:

Parte relevante de las asignaciones de entidades:

  1. EntityA tiene un oneToMany a EntityB
  2. EntityB tiene un oneToMany a EntityC
  3. EntityC tiene un oneToMany a EntityD

Todos tienen asignaciones bidireccionales. Las entidades A y B tienen claves primarias de una sola columna. La entidad C tiene una clave principal compuesta que incluye una clave externa a la clave principal de la entidad B. La entidad D tiene una clave compuesta que incluye la clave compuesta de la entidad C. Consulte las asignaciones a continuación.

@Entity
@Table(name="TableA")
public class EntityA extends BaseEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
    @SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
    @Column(name="TABLE_A_ID")
    private Integer id;

    @OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
    private List<EntityB> entityBList;

    ...

}

@Entity
@Table(name="TableB")
public class EntityB extends BaseEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
    @SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
    @Column(name="TABLE_B_ID")
    private Integer id;

    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="TABLE_A_ID")
    private EntityA entityA;

    @OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
    private List<EntityC> entityCList;

    ...

}

@Entity
@Table(name="TableC")
public class EntityC extends BaseEntity {

    @EmbeddedId
    private EntityC_PK id = new EntityC_PK();

    @MapsId("entityB_Id")
    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="TABLE_B_ID")
    private EntityB entityB;

    @OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
    private List<EntityD> entityDList;

    ...

}

@Embeddable
public class EntityC_PK implements BaseComponent {

    @Column(name="TABLE_B_ID", nullable = false, updatable = false)
    private Integer entityB_Id;

    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
    @SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
    @Column(name="TABLE_C_ID")
    private Integer entityC_Id;

    ...

}

@Entity
@Table(name="TABLE_D")
public class EntityD extends BaseEntity {

    @EmbeddedId
    private EntityD_PK id = new EntityD_PK();

    @MapsId("entityC_Id")
    @JoinColumns({
        @JoinColumn(name = "TABLE_B_ID"),
        @JoinColumn(name = "TABLE_C_ID")})
    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    private EntityC entityC;

    ...

}

@Embeddable
public class EntityD_PK implements BaseComponent {

    @Embedded
    private EntityC_PK entityC_Id;

    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
    @SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
    @Column(name="TABLE_D_ID")
    private Integer entity_id;

    ...

}

Que funciona:

Puede llamar a un EntityManager.persist () en la Entidad A (con todos los hijos adjuntos) y el modelo en cascada persistirá correctamente.

Qué no funciona:

Si crea una instancia de la Entidad A y llama a EntityManager.persist (entityA) y ENTONCES agrega los hijos, nietos, etc.cuando EntityManager.merge (entityA) (o permite la fusión implícita al realizar la transacción), no podrá ejecutar el INSERTAR sentencias en el orden correcto. Para hacer las cosas más confusas, el orden de los INSERTOS no es consistente en las ejecuciones repetidas de las pruebas unitarias. Falla al intentar insertar la Entidad D antes que la Entidad C.

La pregunta:

¿Cómo corregimos las anotaciones JPA para hacer cumplir el orden de inserción correcto (y actualizar / eliminar) al fusionar?

EDICIÓN 1: El orden de inserción / eliminación es fundamental porque la base de datos impone las relaciones de clave externa con restricciones.

0
The Doctor 'X' 14 dic. 2016 a las 22:45
Vea mi respuesta aquí, que puede ser de utilidad: stackoverflow.com/questions/39024310/…
 – 
Alan Hay
14 dic. 2016 a las 23:31

1 respuesta

La mejor respuesta

Primero permíteme decir (y tal vez estoy diciendo lo obvio, lo siento) que deberías revisar la especificación de JPA para tus escenarios ... los elementos incrustables a veces tienen reglas diferentes sobre ellos. A continuación, indica 'EntityManager.create ()', pero creo que se refería a .persist. Más tarde hablas de fusionar, así que quizás te refieres a .merge. De cualquier manera, le sugiero que se quede con .persist si desea conservar nuevas entidades en lugar de una fusión. Si bien no es ilegal, la fusión suele ser para fusionar entidades independientes, etc.

Con eso fuera del camino, permítame llegar al meollo de su pregunta y darle una propiedad que podría ayudar con su pedido. No indicó en su texto si su ddl contiene una restricción de clave externa. Dado que le preocupa el orden, supongo que tiene tal limitación. Si lo hace, OpenJPA no sabe nada sobre esta restricción y, como tal, no sabrá ordenar las cosas de manera apropiada. De forma predeterminada, no puede depender del orden de SQL, y la aleatoriedad del orden es exactamente lo que espero. Sin embargo, si necesita que las cosas estén en orden de tal manera que admita una restricción FK, entonces debe permitir que OpenJPA 'aprenda' sobre su restricción. Para hacer eso, debe establecer esta propiedad en su archivo persistence.xml (o puede establecerla como una propiedad personalizada de JVM):

<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>  

Esta propiedad permite a OpenJPA inspeccionar su esquema y, al hacerlo, puede aprender sobre su restricción FK. Con ese conocimiento, OpenJPA puede ordenar SQL correctamente.

Finalmente, si no tiene una restricción FK, pero desea ordenar el SQL de cierta manera, es posible que deba usar esto:

<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>  

No lo haga, y repito, no use ambas propiedades juntas. Puede tener efectos secundarios extraños. Concéntrese primero en la propiedad SchemaFactory y luego, si no ayuda, pruebe UpdateManager. El orden de operación le dice a OpenJPA que ordene SQL en función de cómo persisten sus entidades, o en otras palabras, el orden de las operaciones. En realidad, esto podría no ser demasiado útil para su situación, ya que persiste A y espera que todo lo demás se conecte en cascada (es probable que OpenJPA persista A primero, pero cuando se trata de B y C, es un juego de azar el que irá primero). Sin embargo, si persistió A, luego C, luego B, el SQL debería ir en el orden de insertar A, C, luego B con el "orden de operación" establecido.

2
buræquete 31 oct. 2017 a las 10:14