2008-10-07 10 views
20

En mi aplicación que tienen estos tipos mapeados-Hibernate (caso general):¿Por qué Hibernate intenta eliminar cuando intento actualizar/insertar?

class RoleRule { 
    private Role role; 
    private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted 
    private PermissionType permissionType; // enum 

    @ManyToOne 
    @JoinColumn(name = "ROLE_ID") 
    public Role getRole() { 
    return role; 
    } 
    public void setRole(Role role) { 
    this.role = role; 
    } 

} 

class Role { 
    private Set<RoleRule> rules = new HashSet<RoleRule>(0); 

    @OneToMany(cascade=CascadeType.ALL) 
    @JoinColumn(name="ROLE_ID") 
    public Set<RoleRule> getRules() { 
    return rules; 
    } 
    public void setRules(Set<RoleRule> rules) { 
    this.rules = rules; 
    } 

} 

todas las clases han equals() & hashCode() anulaciones.

Mi aplicación permite ajustar los roles (solo con los administradores de sistemas, no se preocupe), y entre otros campos, permite la creación de nuevas reglas de roles. Cuando se crea una nueva regla, intento crear un nuevo objeto RoleRule e insertarlo en el campo del rol rules. Llamo al session.update(role) para aplicar los cambios a la base de datos.

Ahora viene la parte fea ... Hibernate decide hacer lo siguiente al cierre de la transacción y rubor:

  1. insertar la nueva regla en la base de datos. Excelente.
  2. Actualice los otros campos de función (no colecciones). Hasta aquí todo bien.
  3. Actualice las reglas existentes, incluso si nada ha cambiado en ellas. Puedo vivir con esto.
  4. Actualice las reglas existentes nuevamente. Aquí está una pasta a partir del registro, incluyendo el comentario automático:
/* delete one-to-many row Role.rules */ 
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

Por supuesto, todos los campos son no-nula, y esta operación fracasa espectacularmente.

¿Alguien puede tratar de explicar por qué Hibernate haría esto? Y, lo que es más importante, ¿cómo puedo evitar esto?

EDITAR: Estaba tan seguro de que era algo que ver con la asignación, y luego mi jefe, en un capricho, elimina el equals() y hashCode() en ambas clases, ellos reconstruido usando Eclipse, y misteriosamente esto solucionó el problema .

Todavía tengo mucha curiosidad sobre mi pregunta. ¿Alguien puede sugerir por qué Hibernate haría esto?

+0

¿Qué driver y dialecto estás usando? –

+0

la aplicación se ejecuta en Oracle 10g, pero eso no debería importar. – Yuval

+0

Lo más importante es que falta la implementación de los métodos 'equals' y' hashCode' en tu publicación. Construir sus métodos 'equals' y' hashCode' basados ​​en valores de identidad de la base de datos ** generados por hibernación o su base de datos ** es una mala idea, haciendo que dependan de cualquier campo que pueda actualizarse después de que el tiempo de construcción sea aún peor. –

Respuesta

3

no estoy seguro si esta es la solución, pero es posible que desee probar:

@OneToMany(mappedBy = "role") 

Y no tienen la anotación @JoinColumn? Creo que ambas entidades están tratando de 'poseer' la asociación, ¿por lo que el SQL podría estar en mal estado?

Además, si usted quiere asegurarse de que sólo se actualizan columnas afectadas, se puede utilizar una anotación específica de hibernación de la clase:

@Entity 
@org.hibernate.annotations.Entity(
    dynamicInsert = true, dynamicUpdate = true 
) 
7

he utilizado generalmente dos métodos de actualización de una colección (los muchos lado de uno-a-muchos) en Hibernate. La forma de fuerza bruta es borrar la colección, llamar a guardar en el padre y luego llamar al color. A continuación, agregue todos los miembros de la colección de nuevo y llame de nuevo al padre. Esto eliminará todo y luego insertará todo. El color en el medio es clave porque obliga a que la eliminación ocurra antes de la inserción. Probablemente sea mejor usar este método solo en colecciones pequeñas, ya que las vuelve a insertar todas.

La segunda forma es más difícil de codificar, pero es más eficiente.Recorre el nuevo conjunto de elementos secundarios y modifica manualmente los que aún están allí, elimine los que no lo son y luego agregue los nuevos. En pseudo-código que sería:

copy the list of existing records to a list_to_delete 
for each record from the form 
    remove it from the list_to_delete 
    if the record exists (based on equals()? key?) 
    change each field that the user can enter 
    else if the record doesn't exist 
    add it to the collection 
end for 
for each list_to_delete 
    remove it 
end for 
save 

He buscado en los foros de hibernar durante muchas horas tratando de encontrar el camino correcto para resolver este problema. Debería poder actualizar su colección para que sea precisa y luego guardarla, pero como ha descubierto, Hibernate intenta separar los elementos secundarios del elemento primario antes de eliminarlos, y si la clave externa no es nula, fallará.

+2

¿Sigue siendo esta la única solución? Pensé que el objetivo de Hibernate era evitar tener que codificar esto individualmente para cada objeto, y hacer que el código sea más fácil de mantener. ¿Hay alguna forma de hacerlo sin escribir un método para cada objeto, por ejemplo, una opción de hibernación que debe establecerse? – pete

4

Ver la respuesta de la pregunta 'Overriding equals and hashCode in Java'.

Explica cómo anular los métodos equals y hashCode, que parecía ser su problema, ya que funcionó después de haberlos reescrito.

Sobreescribirlos erróneamente puede hacer que hibernate elimine sus colecciones y las vuelva a insertar. (como la clave hash se utiliza como claves en los mapas)

+4

Soy muy consciente de la importancia de equals y hashcode, y cómo anularlos. El gran misterio aquí es que los antiguos iguales + código hash son idénticos a los recién generados ... y sin embargo, esto resolvió mi problema. ¿Puedes explicar eso? – Yuval

0

La respuesta de Brian Deterling me ayudó a superar la eliminación fantasma. Ojalá hubiera puesto un código real. Esto es lo que obtuve de su sugerencia 1. Publicar para que alguien lo use o para comentar mi código.

// snFile and task share many to many relationship 

@PersistenceContext 
private EntityManager em; 

public SnFile merge(SnFile snFile) { 
     log.debug("Request to merge SnFile : {}", snFile); 

     Set<Task> tasks = taskService.findBySnFilesId(snFile.getId()); 
     if(snFile.getTasks() != null) { 
      snFile.getTasks().clear(); 
     } 
     em.merge(snFile); 
     em.flush(); 
     if(tasks != null) { 
      if(snFile.getTasks() != null) 
       snFile.getTasks().addAll(tasks); 
      else 
       snFile.setTasks(tasks); 
     } 

     return em.merge(snFile); 
    } 
Cuestiones relacionadas