Ir al contenido

Persistencia con JPA

Una clase con @Entity se mapea a una tabla. @Id identifica su llave primaria y @GeneratedValue permite que la base de datos genere el identificador.

@Data
@Entity
public class EmployeeEntity {
@Id
private Long id;
private String name;
private Double salary;
}

@Data es una anotación de Lombok, no de JPA. Durante la compilación genera automáticamente métodos que normalmente habría que escribir a mano:

  • getters y setters para los atributos;
  • toString();
  • equals() y hashCode();
  • un constructor para los atributos que lo requieran.

@Data reduce código repetitivo en la clase Java, pero no crea tablas ni decide cómo se persiste la entidad. El mapeo con la base de datos comienza con @Entity, @Id y las demás anotaciones de JPA.

En este caso, los atributos se representan como columnas de EMPLOYEE_ENTITY. Un repository declara el tipo de entidad y el tipo de su identificador:

public interface EmployeeRepository
extends JpaRepository<EmployeeEntity, Long> {
}

Spring provee operaciones como save, findById, findAll y delete.

En una relación uno a muchos, el lado con @ManyToOne es propietario y contiene la llave foránea. El lado inverso usa mappedBy y no crea una columna adicional.

// DepartmentEntity: lado inverso
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
private List<EmployeeEntity> employees;
// EmployeeEntity: lado propietario
@ManyToOne
private DepartmentEntity department;

El atributo fetch determina cuándo se carga una relación desde la base de datos.

  • FetchType.LAZY: al consultar el departamento, JPA no necesita cargar inmediatamente todos sus empleados. La colección se solicita cuando el código intenta acceder a ella.
  • FetchType.EAGER: la relación debe quedar disponible junto con la entidad principal, aunque el código todavía no la haya utilizado.
DepartmentEntity department = departmentRepository.findById(id).get();
// Con LAZY, acceder a la colección puede provocar su consulta en este momento.
List<EmployeeEntity> employees = department.getEmployees();

LAZY evita cargar grafos completos de entidades cuando una operación solo necesita los datos del departamento. El acceso a la colección debe realizarse mientras el contexto de persistencia correspondiente siga disponible.

La tabla EMPLOYEE_ENTITY contiene DEPARTMENT_ID. El mismo patrón se aplica a CompanyEntity y DepartmentEntity: una compañía tiene muchos departamentos y cada departamento pertenece a una compañía.

MascotaEntity

id es generado; nombre es obligatorio; microchip es único. @ManyToOne con @JoinColumn(name="refugio_id") crea la llave foránea. La lista de adopciones usa mappedBy="mascota" y no crea columna en MASCOTA_ENTITY.

RefugioEntity

Tiene nombre obligatorio, dirección, teléfono y capacidad. Sus listas de mascotas y adopciones son relaciones @OneToMany inversas; las llaves foráneas viven en las otras tablas.

AdopcionEntity

Incluye fecha obligatoria, nombre y teléfono del adoptante. Dos relaciones @ManyToOne crean mascota_id y refugio_id en ADOPCION_ENTITY.

Los tipos Java se traducen a tipos SQL: String a VARCHAR, Integer a INTEGER, Long a BIGINT y LocalDate a DATE. JPA requiere un constructor vacío para las entidades.

Explorador Mascota–Refugio–Adopción

Selecciona una entidad y observa cómo sus atributos y relaciones aparecen en las tablas.

Clase JavaMascotaEntity@Entity
JPA
Tabla SQLMASCOTA_ENTITYLlaves y columnas
Mapeo de atributos
Relaciones
@ManyToOneREFUGIO_ENTITY

Muchas mascotas pertenecen a un refugio

REFUGIO_ID
@OneToManyADOPCION_ENTITY

Una mascota tiene un historial de adopciones

MASCOTA_ID vive en ADOPCION_ENTITY
Datos de ejemplo en MASCOTA_ENTITY
IDNOMBREEDADMICROCHIPREFUGIO_ID
1Luna32023-00011
2Max52023-00021
3Bella22023-00032

@ElementCollection persiste colecciones de tipos básicos o embebibles sin identidad independiente. Crea una tabla separada con una relación uno a muchos hacia la entidad propietaria.

@ElementCollection @OneToMany
Valores sin identificador propio Entidades con identificador
Solo tienen sentido dentro del padre Pueden existir y consultarse independientemente

Es apropiado para contactos, etiquetas, categorías, historiales numéricos y otros valores múltiples simples. No conviene para objetos con identidad, metadatos propios, grandes volúmenes o consultas independientes frecuentes.

@ElementCollection
@CollectionTable(
name = "producto_tags",
joinColumns = @JoinColumn(name = "producto_id")
)
@Column(name = "tag")
private Set<String> tags;

JPA crea PRODUCTO y PRODUCTO_TAGS; esta última contiene producto_id y tag.

@ElementCollection
@CollectionTable(name = "calificaciones")
@Column(name = "nota")
private List<Double> notas;

También se pueden declarar varias colecciones en una entidad, por ejemplo correos, teléfonos y habilidades de un usuario; cada colección tiene su tabla.

Las anotaciones relacionadas son:

  • @ElementCollection: declara la colección.
  • @CollectionTable: personaliza tabla y llave foránea.
  • @Column: personaliza la columna del valor.
  • @OrderColumn: conserva el orden de una lista.
  • @Enumerated: define cómo persistir enums.

Complete la relación compañía–departamentos:

// CompanyEntity
(mappedBy = "company")
private List<DepartmentEntity> departments;

// DepartmentEntity

private CompanyEntity company;

Complete una colección de etiquetas:


@CollectionTable(name = "producto_tags", joinColumns = @JoinColumn(name = "producto_id"))
(name = "tag")
private Set<String> tags;
1. ¿En qué lado se almacena la llave foránea de una relación Department–Employee?
2. ¿Qué significa mappedBy?
3. ¿Qué responsabilidad tiene @Data en una entidad?
4. ¿Qué indica FetchType.LAZY en la colección employees?
5. ¿Cuándo corresponde @ElementCollection?
6. ¿Qué anotación permite personalizar la tabla de una colección?