JPA en Java: Qué Es y Cómo Funciona (Ejemplo Completo desde Cero)
Descubre qué es JPA, en qué se diferencia de Hibernate y Spring Data JPA, y crea tu primer CRUD paso a paso con código real.
JPA (Java Persistence API) es una especificación de Java que define cómo mapear objetos Java a tablas de una base de datos relacional, sin necesidad de escribir SQL manualmente para las operaciones básicas. JPA en sí misma no es una implementación: es un conjunto de interfaces y anotaciones que otras librerías, como Hibernate, implementan realmente.
¿Qué es JPA?
JPA define anotaciones como @Entity, @Id o @Column y una interfaz de gestión (EntityManager) que permite guardar, actualizar, eliminar y consultar objetos Java como si fueran filas de una tabla, delegando la traducción a SQL en la implementación subyacente.
JPA vs Hibernate vs Spring Data JPA: la confusión más común
| JPA | Hibernate | Spring Data JPA | |
|---|---|---|---|
| Qué es | Especificación (interfaces y anotaciones) | Implementación concreta de JPA | Capa de abstracción de Spring sobre JPA |
| ¿Se puede usar solo? | No, necesita una implementación | Sí, de forma independiente | No, necesita JPA + una implementación (normalmente Hibernate) |
| Qué aporta | El "contrato" estándar | El motor real que traduce a SQL | Repositorios listos para usar sin escribir consultas básicas |
| Relación entre ellos | Es la base | Implementa JPA | Se apoya en JPA + Hibernate |
En un proyecto Spring Boot típico, usas Spring Data JPA (la capa cómoda), que internamente usa JPA (el estándar) implementado por Hibernate (el motor real).
¿Cómo funciona JPA internamente?
Cuando guardas una entidad, JPA (a través de su implementación) traduce esa operación a las sentencias SQL correspondientes, gestiona una caché de primer nivel para evitar consultas repetidas dentro de una misma transacción, y controla el ciclo de vida del objeto (nuevo, gestionado, desconectado, eliminado).
Tutorial: tu primera entidad JPA con Spring Boot
Configurar la dependencia y la base de datos
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
spring.datasource.url=jdbc:postgresql://localhost:5432/tu_base
spring.datasource.username=usuario
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Crear la entidad con anotaciones
@Entity
public class Curso {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String titulo;
private Integer duracionHoras;
// getters y setters
}
Crear el repositorio
public interface CursoRepository extends JpaRepository<Curso, Long> {
List<Curso> findByTituloContaining(String texto);
}
Con solo extender JpaRepository, obtienes automáticamente métodos como save(), findById(), findAll() y deleteById(), sin escribir ninguna implementación.
Probar el CRUD
@Service
public class CursoService {
private final CursoRepository cursoRepository;
public CursoService(CursoRepository cursoRepository) {
this.cursoRepository = cursoRepository;
}
public Curso crearCurso(String titulo, int horas) {
Curso curso = new Curso();
curso.setTitulo(titulo);
curso.setDuracionHoras(horas);
return cursoRepository.save(curso);
}
}
Relaciones entre entidades (OneToMany, ManyToOne)
@Entity
public class Curso {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String titulo;
@OneToMany(mappedBy = "curso", cascade = CascadeType.ALL)
private List<Modulo> modulos = new ArrayList<>();
}
@Entity
public class Modulo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
@ManyToOne
@JoinColumn(name = "curso_id")
private Curso curso;
}
@OneToMany se declara en el lado "padre" (un Curso tiene muchos Módulos) y @ManyToOne en el lado "hijo", que es quien realmente contiene la clave foránea en la base de datos.
Errores comunes con JPA
- Usar
ddl-auto=updateen producción, lo que puede provocar cambios de esquema no controlados; en producción se recomienda gestionar el esquema con migraciones (Flyway o Liquibase). - Confundir la anotación en el lado incorrecto de una relación
@OneToMany/@ManyToOne, generando tablas intermedias no deseadas. - No entender la diferencia entre carga
LAZYyEAGER, provocando el problema conocido como N+1 queries. - Olvidar anotar el
idcon@GeneratedValue, obligando a asignar el identificador manualmente.
Preguntas frecuentes
¿JPA y Hibernate son lo mismo? No. JPA es el estándar (interfaces y anotaciones); Hibernate es la implementación más usada de ese estándar.
¿Necesito Hibernate si uso Spring Data JPA? Sí, por debajo necesitas una implementación de JPA, y Spring Boot usa Hibernate por defecto.
¿Qué es el problema N+1 en JPA?
Ocurre cuando, al cargar una lista de entidades con relaciones LAZY, se ejecuta una consulta adicional por cada elemento para cargar su relación, en lugar de una única consulta optimizada.
Siguiente paso
Con las bases de JPA claras, el siguiente paso natural es aprender a definir correctamente tus entidades como Java Beans y a estructurar la capa de persistencia dentro de una arquitectura Spring Boot completa.
Newsletter Semanal de Java
Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.
🔒 Sin spam. Cancela cuando quieras.