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 entidadfindById(ID id)
: Busca una entidad por su identificadorfindAll()
: Recupera todas las entidadesdeleteById(ID id)
: Elimina una entidad por su identificadorcount()
: 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 datossaveAndFlush(T entity)
: Guarda y sincroniza inmediatamentedeleteInBatch(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.
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