¡Bienvenido a uno de los temas más útiles y populares de Spring Data: el trabajo con repositorios! En esta lección veremos qué son los repositorios, para qué sirven, en qué se diferencian CrudRepository y JpaRepository, cómo nos facilitan la vida y, por supuesto, cómo integrarlos en nuestra aplicación. Todo esto te será útil no solo en tus proyectos de estudio, sino también en el trabajo real, porque los repositorios son la base para interactuar con la base de datos en Spring.
¿Qué es un repositorio?
Un repositorio (Repository) en este contexto es la capa de la aplicación que se encarga de la interacción con la base de datos. Abstrae las operaciones básicas como crear, leer, actualizar y eliminar datos (las llamadas operaciones CRUD).
Antes, interactuar con la base de datos requería escribir consultas SQL a mano; Spring Data simplifica este proceso muchísimo. Ya no hace falta "ensuciarse las manos" con SQL (aunque nadie te lo prohíbe si quieres). En su lugar, los repositorios te permiten centrarte en la lógica de negocio, confiando las tareas rutinarias a Spring.
CrudRepository y JpaRepository: diferencias
Spring Data ofrece varios interfaces para trabajar con la base de datos, pero dos de ellos se usan con más frecuencia:
CrudRepository
CrudRepository — es la interfaz básica para ejecutar operaciones CRUD. Estos son sus métodos principales:
public interface CrudRepository<T, ID> {
<S extends T> S save(S entity); // Guardar (o actualizar) la entidad
Optional<T> findById(ID id); // Buscar entidad por identificador
boolean existsById(ID id); // Comprobar si existe el registro
Iterable<T> findAll(); // Obtener todas las entidades
void deleteById(ID id); // Eliminar por identificador
void delete(T entity); // Eliminar la entidad pasada
}
Cuándo usarlo: si solo necesitas implementar las operaciones CRUD básicas.
JpaRepository
JpaRepository extiende CrudRepository y añade nuevas capacidades:
- Paginación (dividir una lista grande en páginas, como en Google: 10 resultados por página)
- Ordenación de datos
- Métodos específicos de JPA como
flush()para gestionar el estado de la base
Ejemplo de paginación:
// Obtener los primeros 20 usuarios, ordenados por nombre
Page<User> users = repository.findAll(PageRequest.of(0, 20, Sort.by("name")));
Ejemplos de métodos de JpaRepository:
public interface JpaRepository<T, ID> extends CrudRepository<T, ID> {
List<T> findAll(Sort sort); // Ordenar resultados
Page<T> findAll(Pageable pageable); // Paginación de resultados
void flush(); // Guardar los cambios en la base de datos
<S extends T> S saveAndFlush(S entity); // Guardar y hacer flush del contexto
}
Cuándo usarlo: si necesitas capacidades adicionales a nivel DAO, como ordenación o paginación.
Uso práctico de los repositorios
Ahora que la teoría está un poco más clara, ¡pasemos a la práctica! Trabajaremos con una aplicación que ya tiene la entidad User.
1. Entidad User
Nuestra clase User es una entidad muy simple que almacenará datos sobre el usuario:
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getters y setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
2. Creación del repositorio
Ahora vamos a crear la interfaz del repositorio. Spring Data hará todo lo demás por nosotros:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// Aquí puedes añadir métodos personalizados
}
¡Y eso es todo! El repositorio está listo. Ahora tenemos acceso a todas las operaciones CRUD que proporciona JpaRepository.
Realizar operaciones CRUD
Probemos usar nuestro repositorio para operaciones básicas.
Guardar usuario
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUser(String name, String email) {
User user = new User(name, email);
return userRepository.save(user);
}
}
Obtener usuario por ID
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
Actualizar datos del usuario
public User updateUser(Long id, String name, String email) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
user.setName(name);
user.setEmail(email);
return userRepository.save(user);
}
Eliminar usuario
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
Uso en un controlador REST Vamos a crear un controlador que use nuestro UserService:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.createUser(user.getName(), user.getEmail());
return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(user -> new ResponseEntity<>(user, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
User updatedUser = userService.updateUser(id, user.getName(), user.getEmail());
return new ResponseEntity<>(updatedUser, HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
Métodos personalizados en repositorios
A veces las operaciones estándar no son suficientes y necesitas escribir algo propio. Por ejemplo, buscar un usuario por nombre.
Implementación usando el nombre del método
List<User> findByName(String name);
Spring Data entiende "mágicamente" que quieres buscar usuarios por nombre, gracias a las convenciones de nombrado de métodos.
Implementación usando @Query
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
Paginación Si tienes muchos datos, puedes devolverlos en partes pequeñas:
Page<User> findAll(Pageable pageable);
Uso:
Pageable pageable = PageRequest.of(0, 10, Sort.by("name"));
Page<User> page = userRepository.findAll(pageable);
Posibles errores y cómo evitarlos
Aquí van algunos errores comunes que conviene recordar:
- Error "No EntityManager with actual transaction": no olvides añadir la anotación
@Transactionalen los métodos de servicio que modifican datos. - Falta de constructor sin parámetros en la entidad: asegúrate de que las clases-entidad tengan un constructor público sin parámetros.
- Problemas con la carga perezosa (LazyInitializationException): ten cuidado con lazy loading y trata de usar
FetchType.LAZYsolo donde sea necesario.
¿Para qué sirve todo esto?
Los repositorios son una forma estupenda de centrarte en la lógica de la aplicación sin perder tiempo en la rutina. Trabajar con repositorios es más rápido, más seguro y mucho más sencillo que escribir consultas SQL a mano.
En el trabajo real vas a usar repositorios constantemente, ya sea en una aplicación CRUD sencilla o en un sistema de microservicios complejo con decenas de bases de datos.
GO TO FULL VERSION