Repositorios Spring Data

Avanzado
SpringBoot
SpringBoot
Actualizado: 13/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Jerarquía de repositorios de Spring Data

Spring Data JPA organiza sus repositorios en una jerarquía bien estructurada que proporciona diferentes niveles de funcionalidad según las necesidades de tu aplicación. Esta arquitectura permite elegir exactamente el nivel de abstracción que necesitas sin cargar funcionalidades innecesarias.

Repository - La interfaz base

En la base de la jerarquía encontramos Repository<T, ID>, que actúa como interfaz marcadora. Esta interfaz no define métodos concretos, sino que sirve para identificar que una interfaz es un repositorio de Spring Data.

public interface ProductoRepository extends Repository<Producto, Long> {
    // Interfaz vacía - solo marca que es un repositorio
    // Debes definir todos los métodos que necesites
}

Esta interfaz es útil cuando quieres control total sobre los métodos disponibles en tu repositorio, definiendo únicamente aquellos que realmente necesitas.

CrudRepository - Operaciones CRUD básicas

CrudRepository<T, ID> extiende Repository y añade las operaciones CRUD fundamentales. Proporciona métodos para crear, leer, actualizar y eliminar entidades de forma estándar.

public interface ProductoRepository extends CrudRepository<Producto, Long> {
    // Hereda automáticamente:
    // save(S entity)
    // findById(ID id)
    // findAll()
    // deleteById(ID id)
    // count()
    // existsById(ID id)
}

Los métodos más utilizados incluyen:

  • save(T entity): Guarda o actualiza una entidad
  • findById(ID id): Busca una entidad por su identificador
  • findAll(): Recupera todas las entidades
  • deleteById(ID id): Elimina una entidad por su identificador
  • count(): Cuenta el total de entidades

PagingAndSortingRepository - Paginación y ordenación

PagingAndSortingRepository<T, ID> extiende CrudRepository añadiendo capacidades de paginación y ordenación. Resulta esencial cuando trabajas con grandes volúmenes de datos.

public interface ProductoRepository extends PagingAndSortingRepository<Producto, Long> {
    // Hereda todos los métodos de CrudRepository más:
    // findAll(Sort sort)
    // findAll(Pageable pageable)
}

Ejemplo de uso con ordenación:

@Service
public class ProductoService {
    
    @Autowired
    private ProductoRepository repository;
    
    public List<Producto> obtenerProductosOrdenados() {
        Sort ordenacion = Sort.by("nombre").ascending();
        return (List<Producto>) repository.findAll(ordenacion);
    }
    
    public Page<Producto> obtenerProductosPaginados(int pagina, int tamaño) {
        Pageable paginacion = PageRequest.of(pagina, tamaño);
        return repository.findAll(paginacion);
    }
}

JpaRepository - La interfaz más completa

JpaRepository<T, ID> se sitúa en la cima de la jerarquía, extendiendo tanto PagingAndSortingRepository como QueryByExampleExecutor. Ofrece funcionalidades adicionales específicas de JPA.

public interface ProductoRepository extends JpaRepository<Producto, Long> {
    // Hereda todos los métodos anteriores más:
    // flush()
    // saveAndFlush(T entity)
    // deleteInBatch(Iterable<T> entities)
    // findAll(Example<T> example)
}

Los métodos adicionales más relevantes son:

  • flush(): Sincroniza el contexto de persistencia con la base de datos
  • saveAndFlush(T entity): Guarda y sincroniza inmediatamente
  • deleteInBatch(Iterable<T> entities): Elimina múltiples entidades en una sola operación

Elección de la interfaz adecuada

La selección de la interfaz depende de tus necesidades específicas:

  • Repository: Cuando necesitas control total sobre los métodos disponibles
  • CrudRepository: Para operaciones básicas sin paginación
  • PagingAndSortingRepository: Cuando requieres paginación pero no funcionalidades JPA específicas
  • JpaRepository: Para aplicaciones que necesitan todas las funcionalidades disponibles
// Para casos simples sin paginación
public interface CategoriaRepository extends CrudRepository<Categoria, Long> {
    List<Categoria> findByNombre(String nombre);
}

// Para casos con grandes volúmenes de datos
public interface ProductoRepository extends JpaRepository<Producto, Long> {
    Page<Producto> findByCategoria(Categoria categoria, Pageable pageable);
    
    @Query("SELECT p FROM Producto p WHERE p.precio > :precio")
    List<Producto> findProductosCaros(@Param("precio") BigDecimal precio);
}

Esta jerarquía permite que cada aplicación utilice exactamente el nivel de funcionalidad que necesita, manteniendo el código limpio y evitando la sobrecarga de métodos innecesarios.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

El repositorio JpaRepository

JpaRepository representa la interfaz más completa dentro de la jerarquía de repositorios de Spring Data JPA. Esta interfaz combina todas las funcionalidades de sus interfaces padre y añade características específicas de JPA que resultan especialmente útiles en aplicaciones empresariales.

Funcionalidades específicas de JPA

La principal ventaja de JpaRepository radica en sus métodos específicos de JPA que no están disponibles en las interfaces de nivel inferior. Estos métodos aprovechan las características avanzadas del EntityManager de JPA.

@Repository
public interface ProductoRepository extends JpaRepository<Producto, Long> {
    // Hereda automáticamente todos los métodos de la jerarquía
}

Operaciones de sincronización

Los métodos de sincronización permiten controlar cuándo se envían los cambios a la base de datos, algo crucial para el rendimiento y la consistencia de datos.

flush() sincroniza el contexto de persistencia con la base de datos sin confirmar la transacción:

@Service
@Transactional
public class ProductoService {
    
    @Autowired
    private ProductoRepository repository;
    
    public void actualizarInventario(List<Producto> productos) {
        for (Producto producto : productos) {
            producto.setStock(producto.getStock() - 1);
            repository.save(producto);
        }
        
        // Fuerza la sincronización antes de continuar
        repository.flush();
        
        // Continúa con otras operaciones...
    }
}

saveAndFlush(T entity) combina el guardado y la sincronización en una sola operación:

@Service
public class PedidoService {
    
    @Autowired
    private ProductoRepository productoRepository;
    
    public Producto crearProductoUrgente(Producto producto) {
        // Guarda y sincroniza inmediatamente
        Producto productoGuardado = productoRepository.saveAndFlush(producto);
        
        // El producto ya está disponible en la base de datos
        // para otras operaciones que puedan necesitarlo
        return productoGuardado;
    }
}

Operaciones en lote optimizadas

JpaRepository incluye métodos para operaciones en lote que mejoran significativamente el rendimiento cuando trabajas con múltiples entidades.

saveAll(Iterable<S> entities) guarda múltiples entidades de forma optimizada:

@Service
public class ImportacionService {
    
    @Autowired
    private ProductoRepository repository;
    
    public void importarProductos(List<Producto> productos) {
        // Más eficiente que múltiples llamadas a save()
        repository.saveAll(productos);
    }
}

deleteAllInBatch(Iterable<T> entities) elimina múltiples entidades en una sola consulta SQL:

@Service
public class LimpiezaService {
    
    @Autowired
    private ProductoRepository repository;
    
    public void eliminarProductosDescontinuados(List<Producto> productos) {
        // Genera una sola consulta DELETE con múltiples IDs
        repository.deleteAllInBatch(productos);
    }
}

Consultas por ejemplo con QueryByExample

JpaRepository implementa QueryByExampleExecutor, permitiendo consultas dinámicas basadas en objetos ejemplo sin escribir consultas explícitas.

@Service
public class BusquedaService {
    
    @Autowired
    private ProductoRepository repository;
    
    public List<Producto> buscarProductosSimilares(String nombre, String categoria) {
        // Crear objeto ejemplo
        Producto ejemplo = new Producto();
        ejemplo.setNombre(nombre);
        ejemplo.setCategoria(categoria);
        
        // Configurar matcher para búsqueda flexible
        ExampleMatcher matcher = ExampleMatcher.matching()
            .withIgnoreCase()
            .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
        
        Example<Producto> example = Example.of(ejemplo, matcher);
        
        return repository.findAll(example);
    }
}

Integración con especificaciones JPA

JpaRepository se integra perfectamente con JPA Criteria API a través de JpaSpecificationExecutor, permitiendo consultas complejas y dinámicas.

public interface ProductoRepository extends JpaRepository<Producto, Long>, 
                                          JpaSpecificationExecutor<Producto> {
    // Combina funcionalidades de JpaRepository con Specifications
}
@Service
public class FiltroService {
    
    @Autowired
    private ProductoRepository repository;
    
    public Page<Producto> buscarConFiltros(String nombre, BigDecimal precioMinimo, 
                                          Pageable pageable) {
        
        Specification<Producto> spec = Specification.where(null);
        
        if (nombre != null) {
            spec = spec.and((root, query, cb) -> 
                cb.like(cb.lower(root.get("nombre")), "%" + nombre.toLowerCase() + "%"));
        }
        
        if (precioMinimo != null) {
            spec = spec.and((root, query, cb) -> 
                cb.greaterThanOrEqualTo(root.get("precio"), precioMinimo));
        }
        
        return repository.findAll(spec, pageable);
    }
}

Cuándo utilizar JpaRepository

JpaRepository es la elección recomendada para la mayoría de aplicaciones Spring Boot modernas por varias razones:

  • Funcionalidad completa: Incluye todas las operaciones disponibles en la jerarquía
  • Optimizaciones JPA: Aprovecha las características específicas de JPA para mejor rendimiento
  • Flexibilidad: Permite tanto operaciones simples como consultas complejas
  • Compatibilidad futura: Garantiza acceso a nuevas funcionalidades que se añadan
@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
    
    // Métodos de consulta derivados
    List<Usuario> findByEmailContaining(String email);
    
    // Consultas personalizadas con @Query
    @Query("SELECT u FROM Usuario u WHERE u.activo = true AND u.ultimoAcceso > :fecha")
    List<Usuario> findUsuariosActivosRecientes(@Param("fecha") LocalDateTime fecha);
    
    // Consultas nativas cuando sea necesario
    @Query(value = "SELECT * FROM usuarios WHERE MATCH(nombre, email) AGAINST(?1)", 
           nativeQuery = true)
    List<Usuario> busquedaTextoCompleto(String termino);
}

La versatilidad de JpaRepository la convierte en la interfaz ideal para repositorios que necesitan crecer y adaptarse a los requisitos cambiantes de la aplicación, proporcionando una base sólida para el acceso a datos en Spring Boot.

Diferencia entre patrón Repositorio y patrón DAO

Aunque tanto el patrón Repositorio como el patrón DAO (Data Access Object) se utilizan para abstraer el acceso a datos, representan filosofías diferentes en el diseño de aplicaciones. Comprender estas diferencias te ayudará a entender por qué Spring Data JPA adopta el enfoque de repositorio.

El patrón DAO tradicional

El patrón DAO se centra en operaciones de base de datos y proporciona una interfaz que mapea directamente las operaciones CRUD sobre las tablas. Su enfoque es principalmente técnico, pensando en términos de persistencia.

// Enfoque DAO tradicional
public interface ProductoDAO {
    void insert(Producto producto);
    void update(Producto producto);
    void delete(Long id);
    Producto selectById(Long id);
    List<Producto> selectAll();
    List<Producto> selectByCategoria(String categoria);
}
@Component
public class ProductoDAOImpl implements ProductoDAO {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public void insert(Producto producto) {
        String sql = "INSERT INTO productos (nombre, precio, categoria) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, producto.getNombre(), producto.getPrecio(), producto.getCategoria());
    }
    
    @Override
    public Producto selectById(Long id) {
        String sql = "SELECT * FROM productos WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new ProductoRowMapper(), id);
    }
}

El patrón Repositorio en Spring Data

El patrón Repositorio adopta una perspectiva de dominio, modelando una colección de objetos en memoria. Se enfoca en el comportamiento del negocio más que en los detalles de persistencia.

// Enfoque Repositorio
public interface ProductoRepository extends JpaRepository<Producto, Long> {
    List<Producto> findByCategoria(String categoria);
    List<Producto> findByPrecioBetween(BigDecimal min, BigDecimal max);
    List<Producto> findByNombreContainingIgnoreCase(String nombre);
    
    @Query("SELECT p FROM Producto p WHERE p.stock < :minimo")
    List<Producto> findProductosConStockBajo(@Param("minimo") Integer minimo);
}

Diferencias fundamentales

Nivel de abstracción

El DAO opera a nivel de tabla, mientras que el Repositorio opera a nivel de entidad de dominio:

// DAO - piensa en tablas y columnas
public interface PedidoDAO {
    void insertPedido(Long clienteId, Date fecha, BigDecimal total);
    void updateEstadoPedido(Long pedidoId, String estado);
    ResultSet selectPedidosByCliente(Long clienteId);
}

// Repositorio - piensa en objetos de dominio
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
    List<Pedido> findByCliente(Cliente cliente);
    List<Pedido> findByEstado(EstadoPedido estado);
    List<Pedido> findByFechaBetween(LocalDate inicio, LocalDate fin);
}

Nomenclatura y semántica

Los métodos DAO utilizan terminología de base de datos (insert, update, select), mientras que los repositorios usan lenguaje de dominio:

// DAO - terminología técnica
usuarioDAO.insert(usuario);
usuarioDAO.selectByEmail(email);
usuarioDAO.updatePassword(id, password);

// Repositorio - terminología de negocio
usuarioRepository.save(usuario);
usuarioRepository.findByEmail(email);
usuario.cambiarPassword(password);
usuarioRepository.save(usuario);

Flexibilidad en consultas

Spring Data JPA permite derivar consultas automáticamente del nombre del método, algo que no existe en el patrón DAO tradicional:

public interface ClienteRepository extends JpaRepository<Cliente, Long> {
    // Spring genera automáticamente la implementación
    List<Cliente> findByNombreAndApellido(String nombre, String apellido);
    List<Cliente> findByEdadGreaterThan(Integer edad);
    List<Cliente> findByEmailEndingWith(String dominio);
    
    // Consultas complejas derivadas
    List<Cliente> findByActivoTrueAndUltimaCompraAfter(LocalDateTime fecha);
}

Gestión de transacciones

El patrón Repositorio se integra naturalmente con la gestión declarativa de transacciones de Spring:

@Service
@Transactional
public class PedidoService {
    
    @Autowired
    private PedidoRepository pedidoRepository;
    
    @Autowired
    private ProductoRepository productoRepository;
    
    public Pedido procesarPedido(Pedido pedido) {
        // Toda la operación en una transacción
        for (LineaPedido linea : pedido.getLineas()) {
            Producto producto = productoRepository.findById(linea.getProductoId())
                .orElseThrow(() -> new ProductoNoEncontradoException());
            
            producto.reducirStock(linea.getCantidad());
            productoRepository.save(producto);
        }
        
        return pedidoRepository.save(pedido);
    }
}

Testabilidad y mocking

Los repositorios de Spring Data son más fáciles de testear debido a su naturaleza de interfaz y la integración con el contexto de Spring:

@ExtendWith(MockitoExtension.class)
class ProductoServiceTest {
    
    @Mock
    private ProductoRepository productoRepository;
    
    @InjectMocks
    private ProductoService productoService;
    
    @Test
    void deberiaEncontrarProductosPorCategoria() {
        // Given
        List<Producto> productos = Arrays.asList(
            new Producto("Laptop", "Electrónicos"),
            new Producto("Mouse", "Electrónicos")
        );
        
        when(productoRepository.findByCategoria("Electrónicos"))
            .thenReturn(productos);
        
        // When
        List<Producto> resultado = productoService.buscarPorCategoria("Electrónicos");
        
        // Then
        assertThat(resultado).hasSize(2);
        verify(productoRepository).findByCategoria("Electrónicos");
    }
}

Cuándo usar cada patrón

Utiliza el patrón Repositorio (Spring Data JPA) cuando:

  • Desarrolles aplicaciones con Domain-Driven Design
  • Necesites consultas derivadas automáticas
  • Quieras aprovechar las funcionalidades de Spring Boot
  • Busques menor código boilerplate

Considera el patrón DAO cuando:

  • Requieras control total sobre las consultas SQL
  • Trabajes con bases de datos legacy complejas
  • Necesites optimizaciones específicas de rendimiento
  • Integres con sistemas que no usan JPA
// Ejemplo de migración de DAO a Repositorio
// Antes (DAO)
@Repository
public class ClienteDAOImpl implements ClienteDAO {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List<Cliente> selectClientesActivos() {
        String sql = "SELECT * FROM clientes WHERE activo = 1 AND ultima_compra > ?";
        return jdbcTemplate.query(sql, new ClienteRowMapper(), 
                                 Date.from(LocalDateTime.now().minusMonths(6)
                                          .atZone(ZoneId.systemDefault()).toInstant()));
    }
}

// Después (Repositorio)
public interface ClienteRepository extends JpaRepository<Cliente, Long> {
    List<Cliente> findByActivoTrueAndUltimaCompraAfter(LocalDateTime fecha);
}

El patrón Repositorio de Spring Data JPA representa una evolución natural del patrón DAO, proporcionando mayor expresividad, menos código repetitivo y mejor integración con el ecosistema Spring, mientras mantiene la flexibilidad necesaria para casos de uso complejos.

Aprendizajes de esta lección

  • Comprender la jerarquía de interfaces de repositorios en Spring Data JPA y sus niveles de funcionalidad.
  • Identificar las características y métodos clave de Repository, CrudRepository, PagingAndSortingRepository y JpaRepository.
  • Aprender a utilizar JpaRepository para operaciones avanzadas, incluyendo sincronización, operaciones en lote y consultas dinámicas.
  • Diferenciar entre el patrón Repositorio y el patrón DAO, entendiendo sus enfoques y aplicaciones.
  • Reconocer cuándo es adecuado usar cada patrón en el desarrollo de aplicaciones con Spring Boot.

Completa SpringBoot y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración