Contenido del curso

Spring Data Repositories

Relaciones JPA con OneToOne y ManyToOne

Resumen

Mapear relaciones en JPA es el paso que conecta tu modelo entidad-relación con el código Java real. Aquí aprenderás a traducir relaciones de base de datos a entities usando las anotaciones @OneToOne, @ManyToOne y @OneToMany, aplicadas a un caso práctico con cuatro tablas: Pizza, OrderItem, PizzaOrder y Customer.

Este contenido te sirve si estás construyendo una API con Spring Data JPA y necesitas que tus entidades reflejen correctamente las llaves foráneas, las claves primarias compuestas y la dirección de cada relación.

Cómo se traducen las relaciones del diagrama entidad-relación a JPA

Antes de anotar nada, conviene tener clarísimo el mapa de relaciones. En el modelo del proyecto, cada tabla cumple un rol específico y la dirección de la relación define qué anotación usar [01:00].

  • Pizza y OrderItem: relación uno a uno, un ítem tiene una pizza.
  • PizzaOrder y OrderItem: relación uno a muchos, una orden puede tener muchos ítems.
  • PizzaOrder y Customer: relación uno a uno, una orden pertenece a un solo cliente.

Esa lectura del diagrama es la que te permite decidir, en cada entity, si toca @OneToOne, @ManyToOne o @OneToMany.

Cómo definir una clave primaria compuesta con IdClass

En OrderItemEntity hay un detalle distinto al resto: dos atributos llevan la anotación @Id porque la clave primaria es compuesta [02:30]. Para que JPA acepte esto, debes marcar la clase con @IdClass apuntando a una clase auxiliar que contenga esos mismos atributos.

Esa clase auxiliar es un POJO normal: con getters, setters, constructor vacío, constructor con todos los argumentos y los campos idOrder e idItem de tipo Integer. Además debe implementar Serializable y sobreescribir equals y hashCode, porque JPA la usará para identificar registros únicos.

¿Cuándo uso IdClass y cuándo EmbeddedId? Las dos sirven para claves compuestas. @IdClass te deja cada columna como atributo independiente del entity, mientras que @EmbeddedId agrupa todo bajo un solo objeto. Si quieres acceder a las columnas por separado, @IdClass es más cómodo.

Un punto crítico: los tipos de datos de las columnas que participan en relaciones deben ser idénticos en todos los entities. Si en una tabla declaras Integer y en otra Long, las relaciones fallan.

Cómo se anota una relación uno a uno con OneToOne y JoinColumn

La primera relación que conecta OrderItemEntity con PizzaEntity se modela con @OneToOne, porque un ítem solo puede tener una pizza [04:30]. Por convención, las relaciones se declaran al final de la clase.

java @OneToOne @JoinColumn(name = "id_pizza", referencedColumnName = "id_pizza", insertable = false, updatable = false) private PizzaEntity pizza;

La anotación @JoinColumn indica a través de qué columna ocurre el join. El parámetro name es la columna en la tabla actual y referencedColumnName es la columna en la tabla referenciada.

¿Por qué insertable = false y updatable = false? Para respetar el principio de responsabilidad única: a través de esa relación no quieres insertar ni actualizar pizzas, solo leerlas. Las pizzas se gestionan desde su propio entity.

Cómo modelar relaciones uno a muchos con ManyToOne y OneToMany

La relación entre OrderItemEntity y OrderEntity cambia de dirección. Desde el lado del ítem, muchos ítems pertenecen a una orden, así que la anotación es @ManyToOne [06:15].

java @ManyToOne @JoinColumn(name = "id_order", referencedColumnName = "id_order", insertable = false, updatable = false) private OrderEntity order;

Del otro lado, en OrderEntity, la relación inversa se declara como una lista con @OneToMany, porque una orden puede tener muchos ítems.

java @OneToMany(mappedBy = "order") private List<OrderItemEntity> items;

El atributo mappedBy recibe el nombre del campo que tiene el @ManyToOne en la clase hija. Ese valor es el que cierra la relación bidireccional y evita que JPA cree una tabla intermedia innecesaria.

¿Qué hace mappedBy en OneToMany? Le dice a JPA que esta es la parte inversa de la relación y que la llave foránea ya está definida en la otra entidad. Sin mappedBy, JPA podría duplicar el mapeo.

Por qué no debes mapear todas las relaciones posibles

En el ejercicio se decide no crear la relación inversa entre Pizza y OrderItem, ni entre Customer y Order [09:45]. La razón es de performance.

Cada relación mapeada implica que, al consultar un entity, Hibernate puede traer también los objetos relacionados. Eso encarece las consultas, especialmente cuando hay listas grandes detrás.

  • Crea relaciones solo cuando las necesites para tu lógica de negocio.
  • Si necesitas datos cruzados puntualmente, usa method queries o consultas personalizadas con Spring Data.
  • Evita relaciones bidireccionales por defecto, agrégalas solo si te ahorran consultas.

¿Por qué insertable=false y updatable=false en relaciones? Para que la relación funcione solo en lectura desde ese entity. Las inserciones y actualizaciones de la tabla relacionada se manejan desde su propio entity, evitando duplicidad de responsabilidades.

Cuando ejecutas la aplicación, Hibernate altera las tablas e incluye los constraints correspondientes: OrderItem recibe la llave foránea hacia Order y hacia Pizza, y PizzaOrder recibe la llave hacia Customer. Ese log de la consola es la confirmación de que tu mapeo quedó bien hecho.

¿Tú prefieres @IdClass o @EmbeddedId para tus claves compuestas? Cuéntame en los comentarios qué solución te resulta más limpia.