¿Cuál es el "lado propietario" en un mapeo ORM?
¿ Qué significa exactamente el lado propietario ? ¿ Cuál es una explicación con algunos ejemplos de mapeo ( uno a muchos, uno a uno, muchos a uno )?
El siguiente texto es un extracto de la descripción de @OneToOne en la documentación de Java EE 6. Puedes ver el concepto del lado propietario en él.
Define una asociación de un solo valor con otra entidad que tiene multiplicidad uno a uno. Normalmente no es necesario especificar explícitamente la entidad de destino asociada, ya que normalmente se puede inferir del tipo de objeto al que se hace referencia. Si la relación es bidireccional, la parte no propietaria debe utilizar el elemento mappedBy de la anotación OneToOne para especificar el campo de relación o la propiedad de la parte propietaria.
¿Por qué es necesaria la noción de lado propietario?
La idea de un lado propietario de una relación bidireccional proviene del hecho de que en las bases de datos relacionales no existen relaciones bidireccionales como en el caso de los objetos. En las bases de datos sólo tenemos relaciones unidireccionales: claves foráneas.
¿Cuál es el motivo del nombre "lado propietario"?
El lado propietario de la relación rastreada por Hibernate es el lado de la relación que posee la clave externa en la base de datos.
¿Cuál es el problema que resuelve la noción de lado propietario?
Tomemos un ejemplo de dos entidades asignadas sin declarar un lado propietario:
@Entity
@Table(name="PERSONS")
public class Person {
@OneToMany
private List<IdDocument> idDocuments;
}
@Entity
@Table(name="ID_DOCUMENTS")
public class IdDocument {
@ManyToOne
private Person person;
}
Desde un punto de vista OO, este mapeo define no una relación bidireccional, sino dos relaciones unidireccionales separadas.
El mapeo crearía no solo tablas PERSONS
y ID_DOCUMENTS
, sino que también crearía una tercera tabla de asociación PERSONS_ID_DOCUMENTS
:
CREATE TABLE PERSONS_ID_DOCUMENTS
(
persons_id bigint NOT NULL,
id_documents_id bigint NOT NULL,
CONSTRAINT fk_persons FOREIGN KEY (persons_id) REFERENCES persons (id),
CONSTRAINT fk_docs FOREIGN KEY (id_documents_id) REFERENCES id_documents (id),
CONSTRAINT pk UNIQUE (id_documents_id)
)
Observe la clave principal pk
activada ID_DOCUMENTS
únicamente. En este caso, Hibernate rastrea ambos lados de la relación de forma independiente: si agrega un documento a la relación Person.idDocuments
, inserta un registro en la tabla de asociación PERSON_ID_DOCUMENTS
.
Por otro lado, si llamamos idDocument.setPerson(person)
, cambiamos la clave externa person_id en la tabla ID_DOCUMENTS
. Hibernate está creando dos relaciones unidireccionales (clave externa) en la base de datos, para implementar una relación de objeto bidireccional.
Cómo la noción de lado propietario resuelve el problema:
Muchas veces lo que queremos es sólo una clave externa en la tabla ID_DOCUMENTS
y PERSONS
no la tabla de asociación adicional.
Para resolver esto necesitamos configurar Hibernate para que deje de rastrear las modificaciones en la relación Person.idDocuments
. Hibernate sólo debería rastrear el otro lado de la relación IdDocument.person
, y para hacerlo agregamos mappedBy :
@OneToMany(mappedBy="person")
private List<IdDocument> idDocuments;
¿Qué significa mapeado por?
Esto significa algo como: "las modificaciones en este lado de la relación ya están asignadas por el otro lado de la relación IdDocument.person, por lo que no es necesario realizar un seguimiento aquí por separado en una tabla adicional".
¿Hay alguna consecuencia de GOTCHA?
Usando mappedBy , si solo llamamos person.getDocuments().add(document)
, la clave externa NOID_DOCUMENTS
se vinculará al nuevo documento, porque este no es el lado propietario/seguido de la relación.
Para vincular el documento a la nueva persona, debe llamar explícitamente a document.setPerson(person)
, porque ese es el lado propietario de la relación.
Cuando se utiliza mappedBy , es responsabilidad del desarrollador saber cuál es el lado propietario y actualizar el lado correcto de la relación para activar la persistencia de la nueva relación en la base de datos.
Puedes imaginar que el lado propietario es la entidad que tiene la referencia al otro. En su extracto, tiene una relación uno a uno. Dado que es una relación simétrica , terminarás teniendo que si el objeto A está en relación con el objeto B, entonces también es cierto lo contrario.
Esto significa que guardar en el objeto A una referencia al objeto B y guardar en el objeto B una referencia al objeto A será redundante: es por eso que eliges qué objeto es "dueño" del otro que tiene la referencia.
Cuando tiene una relación de uno a muchos, los objetos relacionados con la parte "muchos" serán el lado propietario; de lo contrario, tendría que almacenar muchas referencias de un solo objeto a una multitud. Para evitar eso, cada objeto de la segunda clase tendrá un puntero al único al que se refieren (por lo que son el lado propietario).
Para una relación de muchos a muchos, dado que de todos modos necesitará una tabla de mapeo separada, no habrá ningún lado propietario.
En conclusión la parte propietaria es la entidad que tiene la referencia a la otra.
Explicaré esto muy brevemente. "Propietario" significa que lleva la columna de clave externa en sí misma. En otras palabras, es dueño de la relación. Mucha gente malinterpreta la palabra poseer. Piensan que el partido propietario es el partido principal. Pero cuando lo miramos, la tabla con la columna de clave externa es el lado vinculado. Por ejemplo: pensemos en la Persona y la Dirección y la relación entre ellas OneToOne.
@Data
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "adress_id")
private Adress adress;
}
@Data
@Entity
public class Adress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "adress")
private Person person;
}
en este caso, la persona tiene la columna adress_id fk que vincula la dirección de la columna de clave principal.
En general, podrías estar pensando ¿por qué necesito el lado propietario? Para comprender esto, necesita conocer las reglas de la base de datos. Por ejemplo, digamos que no desea utilizar una parte propietaria y todos los almacenes de datos en una tabla y eso significa implementar una mala práctica de base de datos. Supongo que ahora comprende que cada entidad debe almacenar sus propios datos en su propia tabla. Ahora te piden que muestres la persona y su dirección en la sección de vista. ¿Cómo lo harías si no hubiera ningún lado propietario? Probablemente tendrías que crear 2 consultas para cada entidad, ¿verdad? Aquí también has creado un DML incorrecto. En lugar de escribir 2 consultas, puede escribir 1 consulta usando JOIN
. JOIN funciona con el lado propietario que definimos, sin ellos, no podemos seleccionar los datos que pertenecen a una tabla de la otra tabla. ORM también te ofrece una forma más cómoda al utilizar códigos java básicos a través de entidades sin utilizar bases de datos. Eso es todo.
Las relaciones bidireccionales deben seguir estas reglas.
El lado inverso de una relación bidireccional debe hacer referencia a su lado propietario mediante el elemento mappedBy de la anotación @OneToOne, @OneToMany o @ManyToMany. El elemento mappedBy designa la propiedad o campo de la entidad propietaria de la relación.
El lado múltiple de las relaciones bidireccionales de muchos a uno no debe definir el elemento mappedBy. El lado múltiple es siempre el lado propietario de la relación. (Según los documentos de Oracle: https://docs.oracle.com/cd/E19798-01/821-1841/bnbqi/index.html )
Para relaciones bidireccionales uno a uno, el lado propietario corresponde al lado que contiene la clave externa correspondiente.
Para relaciones bidireccionales de muchos a muchos, cualquiera de las partes puede ser la propietaria.