¿Cómo uso las anotaciones para definir diferentes tipos de relaciones en Hibernate 4 y Spring?

Tengo dos clases, Foo y Bar , de la siguiente manera:

 public class Foo { private Long fooId; private Bar bar; //Yes, this doesn't actually make any sense, //having both a list and a single object here, its an example. private List bars; } public class Bar { private Long barId; private Foo foo; } 

¿Cómo implemento una relación (unidireccional / bidireccional) de uno a muchos, muchos a uno o muchos a muchos usando anotaciones para Hibernate 4 para estas clases?

Además, ¿cómo configuro mi one-to-many para la eliminación huérfana, la carga diferida y qué causa una LazyInitialiaizationException al tratar con colecciones y cómo resolver el problema?

Creando relaciones con anotaciones

Supongamos que todas las clases tienen anotaciones con @Entity y @Table

Relación Uno-Uno unidireccional

 public class Foo{ private UUID fooId; @OneToOne private Bar bar; } public class Bar{ private UUID barId; //No corresponding mapping to Foo.class } 

Relación uno a uno bidireccional administrado por Foo.class

 public class Foo{ private UUID fooId; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "barId") private Bar bar; } public class Bar{ private UUID barId; @OneToOne(mappedBy = "bar") private Foo foo; } 

Uni-Directional One to Many Relationship utilizando la tabla de unión administrada por el usuario

 public class Foo{ private UUID fooId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="fooId"), inverseJoinColumns = @JoinColumn(name="barId")) private List bars; } public class Bar{ private UUID barId; //No Mapping specified here. } @Entity @Table(name="FOO_BAR") public class FooBar{ private UUID fooBarId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; @ManyToOne @JoinColumn(name = "barId") private Bar bar; //You can store other objects/fields on this table here. } 

Muy comúnmente utilizado con Spring Security al configurar un objeto de User que tiene una lista de Role que pueden realizar. Puede agregar y quitar roles a un usuario sin tener que preocuparse por las cascadas que eliminan el Role .

Bidireccional de una a muchas relaciones mediante el mapeo de claves extranjeras

 public class Foo{ private UUID fooId; @OneToMany(mappedBy = "bar") private List bars; } public class Bar{ private UUID barId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; } 

Bidireccional de muchos a muchos utilizando la tabla de combinación administrada de Hibernate

 public class Foo{ private UUID fooId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="fooId"), inverseJoinColumns = @JoinColumn(name="barId")) private List bars; } public class Bar{ private UUID barId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="barId"), inverseJoinColumns = @JoinColumn(name="fooId")) private List foos; } 

Bidireccional de muchos a muchos utilizando el objeto de tabla de unión administrada por el usuario

Comúnmente utilizado cuando desea almacenar información adicional sobre el objeto de unión, como la fecha en que se creó la relación.

 public class Foo{ private UUID fooId; @OneToMany(mappedBy = "bar") private List bars; } public class Bar{ private UUID barId; @OneToMany(mappedBy = "foo") private List foos; } @Entity @Table(name="FOO_BAR") public class FooBar{ private UUID fooBarId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; @ManyToOne @JoinColumn(name = "barId") private Bar bar; //You can store other objects/fields on this table here. } 

Determinar qué lado de la relación bidireccional ‘posee’ la relación:

Este es uno de los aspectos más complicados de trabajar en las relaciones de Hibernate porque Hibernate funcionará correctamente sin importar la forma de establecer la relación. Lo único que cambiará es en qué tabla está almacenada la clave externa. En general, el objeto que tiene una colección de poseerá la relación.

Ejemplo: un objeto User tiene una lista de Roles declarados en él. En la mayoría de las aplicaciones, el sistema manipulará las instancias del objeto User más a menudo que las instancias del objeto Roles . Por lo tanto, convertiría el objeto Role el lado propietario de la relación y manipularía los objetos Role través de la lista de Role en un User por cascada. Para un ejemplo práctico, vea el ejemplo bidireccional One to Many . Normalmente, se aplicarán todos los cambios en cascada en este escenario a menos que tenga un requisito específico para hacer lo contrario.

Cómo determinar tu fetchType

Las colecciones extraídas de forma imprevista han provocado más problemas en SO de los que me preocupa mirar, ya que de forma predeterminada, Hibernate cargará los objetos relacionados de forma perezosa. No importa si la relación es uno-a-uno o muchos-a-muchos según los documentos de Hibernate:

De forma predeterminada, Hibernate utiliza la recuperación de selección de recogida lenta para las colecciones y la recuperación de proxy diferida para las asociaciones de un solo valor. Estos valores predeterminados tienen sentido para la mayoría de las asociaciones en la mayoría de las aplicaciones.

Considere esto mis dos centavos sobre cuándo usar fetchType.LAZY vs fetchType.EAGER en sus objetos. Si sabes que el 50% de las veces no necesitarás acceder a la colección en tu objeto primario, estaría usando fetchType.LAZY .

Los beneficios de rendimiento de esto son enormes y solo crecen a medida que agrega más objetos a su colección. Esto se debe a que para una colección cargada con entusiasmo, Hibernate hace un montón de comprobaciones entre bastidores para asegurarse de que ninguno de sus datos esté desactualizado. Si bien fetchType.EAGER uso de Hibernate para colecciones, tenga en cuenta que existe una penalización de rendimiento ** por usar fetchType.EAGER . Sin embargo, tome nuestro ejemplo de objeto Person . Es bastante probable que cuando carguemos una Person , querremos saber qué Roles desempeñan. Normalmente marcaré esta colección como fetchType.EAGER . NO MARCA REFLEXIVAMENTE TU COLECCIÓN COMO fetchType.EAGER CONSIGUE fetchType.EAGER PARA OBTENER ALREDEDOR DE UNA LazyInitializationException . No solo es malo por razones de rendimiento, generalmente indica que tienes un problema de diseño. Pregúntese si esta colección realmente es una colección cargada con entusiasmo, o si estoy haciendo esto solo para acceder a la colección en este único método. Hibernate tiene formas de evitar esto, que no afecta tanto el rendimiento de sus operaciones. Puede usar el siguiente código en su capa de Service si desea inicializar una colección cargada de forma laxa solo para esta llamada.

 //Service Class @Override @Transactional public Person getPersonWithRoles(UUID personId){ Person person = personDAO.find(personId); Hibernate.initialize(person.getRoles()); return person; } 

La llamada a Hibernate.initialize fuerza la creación y carga del objeto de colección. Sin embargo, ten cuidado, si solo pasas la instancia de Person , obtendrás un proxy de tu Person . Consulte la documentación para más información. El único inconveniente de este método es que no tiene control sobre cómo Hibernate realmente obtendrá su colección de objetos. Si desea controlar esto, puede hacerlo en su DAO.

 //DAO @Override public Person findPersonWithRoles(UUID personId){ Criteria criteria = sessionFactory.getCurrentSession().createCritiera(Person.class); criteria.add(Restrictions.idEq(personId); criteria.setFetchMode("roles", FetchMode.SUBSELECT); } 

El rendimiento aquí depende de qué FetchMode especifique. He leído respuestas que dicen usar FetchMode.SUBSELECT por motivos de rendimiento. La respuesta vinculada entra en más detalles si realmente está interesado.

Si quieres leerme mientras me repito, no dudes en consultar mi otra respuesta aquí

Determinando la dirección en cascada

Hibernate puede realizar operaciones en cascada de una o dos formas en una relación bidireccional. Entonces, si tiene una Lista de Role en un User , puede realizar cambios en cascada en los Role en ambas direcciones. Si cambia el nombre de una Role en particular en un User Hibernate puede actualizar automáticamente la Role asociada en la tabla de Role .

Sin embargo, este no es siempre el comportamiento deseado. Si lo piensa, en este caso, hacer cambios en Role basado en cambios al User no tiene ningún sentido. Sin embargo, tiene sentido ir en la dirección opuesta. Cambie el nombre de un Role en el objeto Role sí mismo, y ese cambio se puede realizar en cascada en todos los objetos del User que tengan ese Role en él.

En términos de eficiencia, tiene sentido crear / actualizar objetos Role guardando el objeto User que pertenecen. Esto significa que marcarías tu anotación @OneToMany como la cascada. Daré un ejemplo:

 public User saveOrUpdate(User user){ getCurrentSession.saveOrUpdate(user); return user; } 

En el ejemplo anterior, Hibernate generará una consulta INSERT para el objeto User , y luego creará una cascada de la creación de la Role una vez que el User haya sido insertado en la base de datos. Estas instrucciones de inserción luego podrán usar el PK del User como su clave externa, por lo que terminaría con N + 1 instrucciones de inserción, donde N es la cantidad de objetos de Role en la lista de usuarios.

Por el contrario, si quisiera guardar los objetos de Role individuales en cascada en el objeto User , podría hacerse:

 //Assume that user has no roles in the list, but has been saved to the //database at a cost of 1 insert. public void saveOrUpdateRoles(User user, List listOfRoles){ for(Role role : listOfRoles){ role.setUser(user); getCurrentSession.saveOrUpdate(role); } } 

Esto da como resultado N + 1 inserciones donde N es el número de Role ‘s en el listOfRoles , pero también N se generan sentencias de actualización como Hibernate cascades, la adición de cada Role a la tabla de User . Este método DAO tiene una complejidad de tiempo mayor que nuestro método anterior, O (n) en oposición a O (1) porque tiene que recorrer la lista de roles. Evita esto si es posible.

En la práctica, sin embargo, por lo general, el lado propietario de la relación será donde marques tus cascadas, y por lo general lo conectarás todo en cascada.

Eliminación huérfana

Hibernate puede resolverlo si elimina todas las asociaciones de un objeto. Supongamos que tiene un User que tiene una lista de Role y en esta lista hay enlaces a 5 roles diferentes. Digamos que eliminas un Role llamado ROLE_EXAMPLE y sucede que el ROLE_EXAMPLE no existe en ningún otro objeto de User . Si tiene orphanRemoval = true establecido en la anotación @OneToMany , Hibernate eliminará el objeto Role ahora ‘huérfano’ de la base de datos por cascada.

La eliminación huérfana no debe habilitarse en todos los casos. De hecho, tener orphanRemoval en nuestro ejemplo anterior no tiene sentido. El hecho de que ningún User pueda realizar cualquier acción que represente el objeto ROLE_EXAMPLE, eso no significa que ningún User futuro nunca podrá realizar la acción.

Este Q & A está destinado a complementar la documentación oficial de Hibernate, que tiene una gran cantidad de configuración XML para estas relaciones.

Estos ejemplos no están destinados a copiarse en el código de producción. Son ejemplos generics de cómo crear y administrar varios objetos y sus relaciones utilizando anotaciones JPA para configurar Hibernate 4 dentro del Spring Framework. Los ejemplos suponen que todas las clases tienen campos ID declarados en el siguiente formato: fooId . El tipo de este campo ID no es relevante.

** Recientemente tuvimos que abandonar el uso de Hibernate para un trabajo de inserción en el que insertaríamos <80,000+ objetos en la base de datos a través de una colección. Hibernate se comió toda la memoria del montón al comprobar la colección y se colgó el sistema.

DESCARGO DE RESPONSABILIDAD: NO SÉ SI ESTOS EJEMPLOS FUNCIONARÁN CON HIBERNATE INDEPENDIENTE

No estoy de ninguna manera afiliada con Hibernate o el equipo de desarrollo de Hibernate. Proporciono estos ejemplos, así que tengo una referencia para señalar cuándo estoy respondiendo preguntas en la etiqueta de Hibernate. Estos ejemplos y discusiones se basan en mi propia opinión y en cómo desarrollo mis aplicaciones usando Hibernate. Estos ejemplos no son exhaustivos. Los baso en las situaciones comunes en las que he usado Hibernate en el pasado.

Si encuentra problemas al intentar implementar estos ejemplos, no haga ningún comentario y espere que solucione su problema. Parte del aprendizaje Hibernate es aprender lo que está dentro y fuera de su API. Si hay un error con los ejemplos, siéntase libre de editarlos.