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?
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. }
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.
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í
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.
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.