Skip to content

8. Acceso a Datos con Spring Data JPA

Spring Data es un proyecto que simplifica el acceso a datos en aplicaciones Spring. Proporciona una abstracción sobre diferentes tecnologías de persistencia.

ProyectoBase de datos
Spring Data JPABases relacionales (MySQL, PostgreSQL)
Spring Data MongoDBMongoDB
Spring Data RedisRedis
Spring Data ElasticsearchElasticsearch
Spring Data JDBCJDBC simple
Dependencias
<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”
Configuración MySQL
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/miapp
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
Configuración PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/miapp
spring.datasource.username=postgres
spring.datasource.password=secret
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Configuración H2
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
ValorDescripción
noneNo hace nada
validateValida el esquema, no lo modifica
updateActualiza el esquema (desarrollo)
createCrea el esquema, destruye datos previos
create-dropCrea al iniciar, elimina al cerrar

Entidad Usuario
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "usuarios")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public 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
}
AnotaciónDescripción
@EntityMarca la clase como entidad JPA
@TableNombre de la tabla
@IdClave primaria
@GeneratedValueEstrategia de generación de ID
@ColumnConfiguración de columna
@EnumeratedMapeo de enums
@TemporalTipo de fecha (legacy)
@TransientCampo no persistido

Repositorio básico
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Spring Data implementa automáticamente los métodos CRUD
}
Uso de métodos CRUD
@Service
public 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);
}
}

Spring Data genera consultas automáticamente basándose en el nombre del método:

Métodos derivados
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);
}
Palabra claveEjemploSQL
AndfindByNombreAndEmailWHERE nombre = ? AND email = ?
OrfindByNombreOrEmailWHERE nombre = ? OR email = ?
BetweenfindByEdadBetweenWHERE edad BETWEEN ? AND ?
LessThanfindByEdadLessThanWHERE edad < ?
GreaterThanfindByEdadGreaterThanWHERE edad > ?
LikefindByNombreLikeWHERE nombre LIKE ?
ContainingfindByNombreContainingWHERE nombre LIKE %?%
InfindByRolInWHERE rol IN (?)
NotNullfindByEmailNotNullWHERE email IS NOT NULL
OrderByfindByRolOrderByNombreORDER BY nombre

Consultas 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);
}
Consultas SQL nativas
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);
}

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
@Entity
public 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<>();
}
@Entity
public class Categoria {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
@ManyToMany(mappedBy = "categorias")
private Set<Producto> productos = new HashSet<>();
}
OneToOne
@Entity
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "perfil_id", referencedColumnName = "id")
private Perfil perfil;
}
@Entity
public 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
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
@Service
public 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
@GetMapping
public 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 para consultas dinámicas
// Habilitar Specifications
public interface UsuarioRepository extends
JpaRepository<Usuario, Long>,
JpaSpecificationExecutor<Usuario> {
}
// Crear Specifications
public 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
@Service
public 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);
}
}
🐝