8. Acceso a Datos con Spring Data JPA
📚 8.1 Introducción a Spring Data
Section titled “📚 8.1 Introducción a Spring Data”¿Qué es Spring Data?
Section titled “¿Qué es Spring Data?”Spring Data es un proyecto que simplifica el acceso a datos en aplicaciones Spring. Proporciona una abstracción sobre diferentes tecnologías de persistencia.
Proyectos de Spring Data
Section titled “Proyectos de Spring Data”| Proyecto | Base de datos |
|---|---|
| Spring Data JPA | Bases relacionales (MySQL, PostgreSQL) |
| Spring Data MongoDB | MongoDB |
| Spring Data Redis | Redis |
| Spring Data Elasticsearch | Elasticsearch |
| Spring Data JDBC | JDBC simple |
Dependencia
Section titled “Dependencia”<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
<!-- Driver de base de datos --><dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope></dependency>
<!-- O H2 para desarrollo --><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope></dependency>⚙️ 8.2 Configuración de Base de Datos
Section titled “⚙️ 8.2 Configuración de Base de Datos”# application.propertiesspring.datasource.url=jdbc:mysql://localhost:3306/miappspring.datasource.username=rootspring.datasource.password=secretspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernatespring.jpa.hibernate.ddl-auto=updatespring.jpa.show-sql=truespring.jpa.properties.hibernate.format_sql=truespring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialectPostgreSQL
Section titled “PostgreSQL”spring.datasource.url=jdbc:postgresql://localhost:5432/miappspring.datasource.username=postgresspring.datasource.password=secretspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialectH2 (desarrollo)
Section titled “H2 (desarrollo)”spring.datasource.url=jdbc:h2:mem:testdbspring.datasource.username=saspring.datasource.password=spring.h2.console.enabled=truespring.h2.console.path=/h2-consoleOpciones de ddl-auto
Section titled “Opciones de ddl-auto”| Valor | Descripción |
|---|---|
none | No hace nada |
validate | Valida el esquema, no lo modifica |
update | Actualiza el esquema (desarrollo) |
create | Crea el esquema, destruye datos previos |
create-drop | Crea al iniciar, elimina al cerrar |
🏛️ 8.3 Creación de Entidades
Section titled “🏛️ 8.3 Creación de Entidades”Entidad básica
Section titled “Entidad básica”import jakarta.persistence.*;import lombok.*;
@Entity@Table(name = "usuarios")@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic class Usuario {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(nullable = false, length = 100) private String nombre;
@Column(unique = true, nullable = false) private String email;
@Column(nullable = false) private String password;
@Column(name = "fecha_registro") private LocalDateTime fechaRegistro;
@Enumerated(EnumType.STRING) private Rol rol;
@PrePersist protected void onCreate() { fechaRegistro = LocalDateTime.now(); }}
public enum Rol { ADMIN, USER, MODERATOR}Anotaciones principales
Section titled “Anotaciones principales”| Anotación | Descripción |
|---|---|
@Entity | Marca la clase como entidad JPA |
@Table | Nombre de la tabla |
@Id | Clave primaria |
@GeneratedValue | Estrategia de generación de ID |
@Column | Configuración de columna |
@Enumerated | Mapeo de enums |
@Temporal | Tipo de fecha (legacy) |
@Transient | Campo no persistido |
📦 8.4 Uso de Repositorios
Section titled “📦 8.4 Uso de Repositorios”Crear un repositorio
Section titled “Crear un repositorio”import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;
@Repositorypublic interface UsuarioRepository extends JpaRepository<Usuario, Long> { // Spring Data implementa automáticamente los métodos CRUD}Métodos heredados de JpaRepository
Section titled “Métodos heredados de JpaRepository”@Servicepublic class UsuarioService {
private final UsuarioRepository repository;
public UsuarioService(UsuarioRepository repository) { this.repository = repository; }
// CRUD básico public Usuario guardar(Usuario usuario) { return repository.save(usuario); // INSERT o UPDATE }
public Optional<Usuario> buscarPorId(Long id) { return repository.findById(id); }
public List<Usuario> listarTodos() { return repository.findAll(); }
public void eliminar(Long id) { repository.deleteById(id); }
public long contar() { return repository.count(); }
public boolean existe(Long id) { return repository.existsById(id); }
// Guardar varios public List<Usuario> guardarTodos(List<Usuario> usuarios) { return repository.saveAll(usuarios); }}🔍 8.5 Métodos Derivados
Section titled “🔍 8.5 Métodos Derivados”Query Methods
Section titled “Query Methods”Spring Data genera consultas automáticamente basándose en el nombre del método:
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// SELECT * FROM usuarios WHERE email = ? Optional<Usuario> findByEmail(String email);
// SELECT * FROM usuarios WHERE nombre = ? List<Usuario> findByNombre(String nombre);
// SELECT * FROM usuarios WHERE nombre LIKE %?% List<Usuario> findByNombreContaining(String nombre);
// SELECT * FROM usuarios WHERE nombre LIKE ?% List<Usuario> findByNombreStartingWith(String prefijo);
// SELECT * FROM usuarios WHERE rol = ? List<Usuario> findByRol(Rol rol);
// SELECT * FROM usuarios WHERE rol = ? AND activo = true List<Usuario> findByRolAndActivoTrue(Rol rol);
// SELECT * FROM usuarios WHERE fecha_registro > ? List<Usuario> findByFechaRegistroAfter(LocalDateTime fecha);
// SELECT * FROM usuarios WHERE fecha_registro BETWEEN ? AND ? List<Usuario> findByFechaRegistroBetween(LocalDateTime inicio, LocalDateTime fin);
// SELECT * FROM usuarios WHERE email = ? OR nombre = ? List<Usuario> findByEmailOrNombre(String email, String nombre);
// SELECT * FROM usuarios ORDER BY nombre ASC List<Usuario> findAllByOrderByNombreAsc();
// SELECT * FROM usuarios WHERE rol = ? ORDER BY fecha_registro DESC List<Usuario> findByRolOrderByFechaRegistroDesc(Rol rol);
// Verificar existencia boolean existsByEmail(String email);
// Contar long countByRol(Rol rol);
// Eliminar void deleteByEmail(String email);
// Primero/Top Optional<Usuario> findFirstByRolOrderByFechaRegistroDesc(Rol rol); List<Usuario> findTop5ByRolOrderByFechaRegistroDesc(Rol rol);}Palabras clave soportadas
Section titled “Palabras clave soportadas”| Palabra clave | Ejemplo | SQL |
|---|---|---|
And | findByNombreAndEmail | WHERE nombre = ? AND email = ? |
Or | findByNombreOrEmail | WHERE nombre = ? OR email = ? |
Between | findByEdadBetween | WHERE edad BETWEEN ? AND ? |
LessThan | findByEdadLessThan | WHERE edad < ? |
GreaterThan | findByEdadGreaterThan | WHERE edad > ? |
Like | findByNombreLike | WHERE nombre LIKE ? |
Containing | findByNombreContaining | WHERE nombre LIKE %?% |
In | findByRolIn | WHERE rol IN (?) |
NotNull | findByEmailNotNull | WHERE email IS NOT NULL |
OrderBy | findByRolOrderByNombre | ORDER BY nombre |
📝 8.6 Consultas Personalizadas
Section titled “📝 8.6 Consultas Personalizadas”@Query con JPQL
Section titled “@Query con JPQL”public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// JPQL (orientado a objetos) @Query("SELECT u FROM Usuario u WHERE u.email = :email") Optional<Usuario> buscarPorEmail(@Param("email") String email);
// Con múltiples parámetros @Query("SELECT u FROM Usuario u WHERE u.rol = :rol AND u.activo = true") List<Usuario> buscarActivosPorRol(@Param("rol") Rol rol);
// Con LIKE @Query("SELECT u FROM Usuario u WHERE u.nombre LIKE %:nombre%") List<Usuario> buscarPorNombreContiene(@Param("nombre") String nombre);
// Proyección (solo algunos campos) @Query("SELECT u.nombre, u.email FROM Usuario u WHERE u.rol = :rol") List<Object[]> obtenerNombreYEmailPorRol(@Param("rol") Rol rol);
// Con JOIN @Query("SELECT u FROM Usuario u JOIN u.pedidos p WHERE p.total > :monto") List<Usuario> buscarConPedidosMayoresA(@Param("monto") BigDecimal monto);
// UPDATE @Modifying @Query("UPDATE Usuario u SET u.activo = false WHERE u.id = :id") int desactivar(@Param("id") Long id);
// DELETE @Modifying @Query("DELETE FROM Usuario u WHERE u.fechaRegistro < :fecha") int eliminarAntiguos(@Param("fecha") LocalDateTime fecha);}@Query con SQL nativo
Section titled “@Query con SQL nativo”public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// SQL nativo @Query(value = "SELECT * FROM usuarios WHERE email = ?1", nativeQuery = true) Optional<Usuario> buscarPorEmailNativo(String email);
// Con parámetros nombrados @Query(value = "SELECT * FROM usuarios WHERE rol = :rol", nativeQuery = true) List<Usuario> buscarPorRolNativo(@Param("rol") String rol);
// Consulta compleja @Query(value = """ SELECT u.*, COUNT(p.id) as total_pedidos FROM usuarios u LEFT JOIN pedidos p ON u.id = p.usuario_id GROUP BY u.id HAVING COUNT(p.id) > :minPedidos """, nativeQuery = true) List<Usuario> buscarConMasDePedidos(@Param("minPedidos") int minPedidos);}🔗 8.7 Relaciones entre Entidades
Section titled “🔗 8.7 Relaciones entre Entidades”@OneToMany / @ManyToOne
Section titled “@OneToMany / @ManyToOne”@Entity@Table(name = "usuarios")public class Usuario { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String nombre;
// Un usuario tiene muchos pedidos @OneToMany(mappedBy = "usuario", cascade = CascadeType.ALL) private List<Pedido> pedidos = new ArrayList<>();}
@Entity@Table(name = "pedidos")public class Pedido { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private BigDecimal total;
// Muchos pedidos pertenecen a un usuario @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "usuario_id", nullable = false) private Usuario usuario;}@ManyToMany
Section titled “@ManyToMany”@Entitypublic class Producto { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String nombre;
@ManyToMany @JoinTable( name = "producto_categoria", joinColumns = @JoinColumn(name = "producto_id"), inverseJoinColumns = @JoinColumn(name = "categoria_id") ) private Set<Categoria> categorias = new HashSet<>();}
@Entitypublic class Categoria { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String nombre;
@ManyToMany(mappedBy = "categorias") private Set<Producto> productos = new HashSet<>();}@OneToOne
Section titled “@OneToOne”@Entitypublic class Usuario { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "perfil_id", referencedColumnName = "id") private Perfil perfil;}
@Entitypublic class Perfil { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String bio; private String avatar;
@OneToOne(mappedBy = "perfil") private Usuario usuario;}🗄️ 8.8 Integración con Base de Datos
Section titled “🗄️ 8.8 Integración con Base de Datos”Paginación y ordenamiento
Section titled “Paginación y ordenamiento”public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Paginación automática Page<Usuario> findByRol(Rol rol, Pageable pageable);
// Solo lista con paginación List<Usuario> findByActivo(boolean activo, Pageable pageable);}
// Uso en servicio@Servicepublic class UsuarioService {
public Page<Usuario> listarPaginado(int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("nombre").ascending()); return repository.findAll(pageable); }
public Page<Usuario> buscarPorRol(Rol rol, int page, int size) { Pageable pageable = PageRequest.of(page, size); return repository.findByRol(rol, pageable); }}
// Uso en controller@GetMappingpublic Page<Usuario> listar( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "nombre") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy)); return repository.findAll(pageable);}Specifications (consultas dinámicas)
Section titled “Specifications (consultas dinámicas)”// Habilitar Specificationspublic interface UsuarioRepository extends JpaRepository<Usuario, Long>, JpaSpecificationExecutor<Usuario> {}
// Crear Specificationspublic class UsuarioSpecifications {
public static Specification<Usuario> tieneRol(Rol rol) { return (root, query, cb) -> cb.equal(root.get("rol"), rol); }
public static Specification<Usuario> nombreContiene(String nombre) { return (root, query, cb) -> cb.like(cb.lower(root.get("nombre")), "%" + nombre.toLowerCase() + "%"); }
public static Specification<Usuario> estaActivo() { return (root, query, cb) -> cb.isTrue(root.get("activo")); }}
// Uso@Servicepublic class UsuarioService {
public List<Usuario> buscarDinamico(String nombre, Rol rol, Boolean activo) { Specification<Usuario> spec = Specification.where(null);
if (nombre != null) { spec = spec.and(UsuarioSpecifications.nombreContiene(nombre)); } if (rol != null) { spec = spec.and(UsuarioSpecifications.tieneRol(rol)); } if (activo != null && activo) { spec = spec.and(UsuarioSpecifications.estaActivo()); }
return repository.findAll(spec); }}
🐝