Skip to content

Top 50 Preguntas Frecuentes de Spring

Las 50 Preguntas Más Frecuentes de Spring

Section titled “Las 50 Preguntas Más Frecuentes de Spring”

Guía completa con las preguntas que aparecen en la mayoría de entrevistas técnicas.


¿Qué es? Contenedor IoC (Inversión de Control) central de Spring que gestiona el ciclo de vida de los beans.

¿Para qué sirve?

  • Crear e inyectar beans automáticamente
  • Gestionar dependencias entre componentes
  • Publicar y escuchar eventos
  • Acceder a recursos y propiedades

Tipos principales:

  • AnnotationConfigApplicationContext: Para configuración con anotaciones
  • AnnotationConfigWebApplicationContext: Para aplicaciones web
  • ClassPathXmlApplicationContext: Para configuración XML (legacy)

Ejemplo práctico:

// Tipos de ApplicationContext
// Para aplicaciones standalone
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// Para aplicaciones web
ApplicationContext ctx = new AnnotationConfigWebApplicationContext();
// Obtener beans
MiServicio servicio = ctx.getBean(MiServicio.class);
MiServicio servicio2 = ctx.getBean("miServicio", MiServicio.class);

2. ¿Cuál es la diferencia entre BeanFactory y ApplicationContext?

Section titled “2. ¿Cuál es la diferencia entre BeanFactory y ApplicationContext?”

¿Qué es? Dos interfaces para el contenedor IoC de Spring con diferentes capacidades.

Diferencias:

BeanFactoryApplicationContext
Lazy loadingEager loading
BásicoMás funcionalidades
Menos memoriaMás memoria
Sin eventosSoporte de eventos
Sin i18nSoporte i18n

Recomendación: Usar siempre ApplicationContext excepto en entornos con recursos muy limitados.

¿Qué es? Anotación que indica a Spring que debe inyectar una dependencia automáticamente.

Tipos de inyección:

  1. Constructor injection (recomendado): Dependencias inmutables, fácil testing
  2. Setter injection: Para dependencias opcionales
  3. Field injection (evitar): Difícil de testear, oculta dependencias

Nota: Desde Spring 4.3, @Autowired es opcional si hay un solo constructor.

Ejemplo práctico:

@Service
public class MiServicio {
// Field injection (evitar)
@Autowired
private Repositorio repo;
// Constructor injection (recomendado)
private final Repositorio repo;
@Autowired // Opcional si hay un solo constructor
public MiServicio(Repositorio repo) {
this.repo = repo;
}
// Setter injection
private Repositorio repo;
@Autowired
public void setRepo(Repositorio repo) {
this.repo = repo;
}
}

¿Qué es? Anotación que especifica cuál bean inyectar cuando hay múltiples candidatos del mismo tipo.

¿Cuándo usarlo? Cuando tienes varias implementaciones de una interfaz y necesitas elegir una específica.

Alternativa: @Primary marca un bean como preferido por defecto.

Ejemplo práctico:

// Cuando hay múltiples beans del mismo tipo
public interface NotificacionService {}
@Service("emailService")
public class EmailService implements NotificacionService {}
@Service("smsService")
public class SmsService implements NotificacionService {}
// Usar @Qualifier para especificar cuál
@Service
public class AlertaService {
private final NotificacionService notificacion;
public AlertaService(@Qualifier("emailService") NotificacionService notificacion) {
this.notificacion = notificacion;
}
}

¿Qué es? Anotación que marca un bean como preferido cuando hay múltiples candidatos del mismo tipo.

Diferencia con @Qualifier:

  • @Primary: Define el bean por defecto (en la definición)
  • @Qualifier: Elige un bean específico (en el punto de inyección)

Ejemplo práctico:

// @Primary marca el bean por defecto cuando hay múltiples
@Service
@Primary
public class EmailService implements NotificacionService {}
@Service
public class SmsService implements NotificacionService {}
// Se inyecta EmailService por defecto
@Service
public class AlertaService {
private final NotificacionService notificacion; // EmailService
}

¿Qué es? Anotación para inyectar valores de configuración directamente en campos.

Fuentes de valores:

  • Properties (${propiedad})
  • Variables de entorno
  • Expresiones SpEL (#{expresion})
  • Valores por defecto (${prop:default})

Limitación: Para configuraciones complejas, preferir @ConfigurationProperties.

Ejemplo práctico:

@Service
public class MiServicio {
// Inyectar valor de properties
@Value("${app.nombre}")
private String appNombre;
// Valor por defecto
@Value("${app.timeout:30}")
private int timeout;
// SpEL (Spring Expression Language)
@Value("#{systemProperties['user.name']}")
private String usuario;
@Value("#{T(java.lang.Math).random()}")
private double random;
}

¿Qué es? Anotación que mapea propiedades de configuración a un objeto Java tipado.

Ventajas sobre @Value:

  • Tipado fuerte (errores en tiempo de compilación)
  • Agrupación lógica de propiedades
  • Validación con Bean Validation
  • Autocompletado en IDEs

Requisito: Habilitar con @EnableConfigurationProperties o @ConfigurationPropertiesScan.

Ejemplo práctico:

application.yml
# app:
# nombre: MiApp
# config:
# timeout: 30
# maxRetries: 3
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String nombre;
private Config config;
public static class Config {
private int timeout;
private int maxRetries;
// getters y setters
}
// getters y setters
}
// Uso
@Service
public class MiServicio {
private final AppProperties props;
public MiServicio(AppProperties props) {
this.props = props;
}
}

¿Qué es? Secuencia de fases por las que pasa un bean desde su creación hasta su destrucción.

Fases:

  1. Instanciación: Se llama al constructor
  2. Inyección: Se inyectan dependencias
  3. @PostConstruct: Inicialización personalizada
  4. Uso: Bean disponible para la aplicación
  5. @PreDestroy: Limpieza antes de destrucción
  6. Destrucción: Bean eliminado del contexto

Ejemplo práctico:

@Component
public class MiBean {
// 1. Constructor
public MiBean() {
System.out.println("1. Constructor");
}
// 2. Inyección de dependencias
@Autowired
public void setDependencia(Dependencia dep) {
System.out.println("2. Setter injection");
}
// 3. Post-construcción
@PostConstruct
public void init() {
System.out.println("3. PostConstruct");
}
// 4. Bean listo para usar
// 5. Pre-destrucción
@PreDestroy
public void cleanup() {
System.out.println("5. PreDestroy");
}
}
// Alternativa con @Bean
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MiBean miBean() {
return new MiBean();
}

¿Qué es? Anotación que retrasa la creación del bean hasta que se use por primera vez.

¿Para qué sirve?

  • Mejorar tiempo de arranque de la aplicación
  • Ahorrar recursos para beans que no siempre se usan
  • Resolver dependencias circulares

Ejemplo práctico:

// Bean se crea solo cuando se necesita
@Component
@Lazy
public class BeanPesado {
public BeanPesado() {
// Inicialización costosa
}
}
// En inyección
@Service
public class MiServicio {
@Autowired
@Lazy
private BeanPesado beanPesado; // Se crea al primer uso
}

¿Qué es? Anotación que activa beans solo en ciertos perfiles de ejecución.

¿Para qué sirve?

  • Configuración diferente por entorno (dev, test, prod)
  • Activar/desactivar features
  • Usar diferentes implementaciones según el entorno

Activar perfil: spring.profiles.active=desarrollo

Ejemplo práctico:

// Bean solo activo en ciertos perfiles
@Configuration
@Profile("desarrollo")
public class DesarrolloConfig {
@Bean
public DataSource dataSource() {
return new H2DataSource();
}
}
@Configuration
@Profile("produccion")
public class ProduccionConfig {
@Bean
public DataSource dataSource() {
return new PostgresDataSource();
}
}
// Activar perfil
// application.properties: spring.profiles.active=desarrollo
// O: java -jar app.jar --spring.profiles.active=produccion

¿Qué es? Anotación que crea beans condicionalmente según ciertas condiciones.

Condiciones comunes (Spring Boot):

  • @ConditionalOnProperty: Según valor de propiedad
  • @ConditionalOnClass: Si una clase está presente
  • @ConditionalOnMissingBean: Si no existe otro bean del tipo
  • @ConditionalOnBean: Si existe otro bean

Base de la auto-configuración de Spring Boot.

Ejemplo práctico:

// Bean condicional
@Bean
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
return new H2DataSource();
}
@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public DataSource mysqlDataSource() {
return new MysqlDataSource();
}

12. ¿Qué es AOP (Aspect-Oriented Programming)?

Section titled “12. ¿Qué es AOP (Aspect-Oriented Programming)?”

¿Qué es? Programación Orientada a Aspectos - paradigma que separa concerns transversales del código de negocio.

Conceptos clave:

  • Aspect: Módulo que encapsula un concern transversal
  • Pointcut: Dónde aplicar el aspecto
  • Advice: Qué hacer (Before, After, Around)
  • JoinPoint: Punto de ejecución donde se aplica

Usos comunes: Logging, seguridad, transacciones, caché.

Ejemplo práctico:

// Aspecto para logging
@Aspect
@Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
// Pointcut - dónde aplicar
@Pointcut("execution(* com.ejemplo.service.*.*(..))")
public void serviceMethods() {}
// Before advice
@Before("serviceMethods()")
public void logBefore(JoinPoint jp) {
log.info("Ejecutando: {}", jp.getSignature().getName());
}
// After advice
@After("serviceMethods()")
public void logAfter(JoinPoint jp) {
log.info("Finalizado: {}", jp.getSignature().getName());
}
// Around advice
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long inicio = System.currentTimeMillis();
Object resultado = pjp.proceed();
long duracion = System.currentTimeMillis() - inicio;
log.info("{} ejecutado en {} ms", pjp.getSignature().getName(), duracion);
return resultado;
}
// AfterReturning
@AfterReturning(pointcut = "serviceMethods()", returning = "resultado")
public void logReturn(Object resultado) {
log.info("Retornó: {}", resultado);
}
// AfterThrowing
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void logException(Exception ex) {
log.error("Excepción: {}", ex.getMessage());
}
}

¿Qué es? Anotación que habilita el soporte de AOP con AspectJ en Spring.

Tipos de proxy:

  • JDK Proxy (default): Para interfaces
  • CGLIB Proxy (proxyTargetClass=true): Para clases concretas

Ejemplo práctico:

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// Habilita AOP con proxies
}
// proxyTargetClass = true para usar CGLIB en vez de JDK proxies
@EnableAspectJAutoProxy(proxyTargetClass = true)

¿Qué es? Sistema de publicación/suscripción para comunicación desacoplada entre componentes.

Componentes:

  • ApplicationEvent: Clase base para eventos
  • ApplicationEventPublisher: Para publicar eventos
  • @EventListener: Para escuchar eventos

Ventaja: Desacoplamiento - el publicador no conoce a los suscriptores.

Ejemplo práctico:

// Evento personalizado
public class UsuarioRegistradoEvent extends ApplicationEvent {
private final Usuario usuario;
public UsuarioRegistradoEvent(Object source, Usuario usuario) {
super(source);
this.usuario = usuario;
}
public Usuario getUsuario() {
return usuario;
}
}
// Publicar evento
@Service
public class UsuarioService {
@Autowired
private ApplicationEventPublisher publisher;
public void registrar(Usuario usuario) {
// Guardar usuario
publisher.publishEvent(new UsuarioRegistradoEvent(this, usuario));
}
}
// Escuchar evento
@Component
public class EmailListener {
@EventListener
public void onUsuarioRegistrado(UsuarioRegistradoEvent event) {
enviarEmailBienvenida(event.getUsuario());
}
@Async
@EventListener
public void onUsuarioRegistradoAsync(UsuarioRegistradoEvent event) {
// Procesamiento asíncrono
}
}

¿Qué es? Anotación para ejecutar métodos periódicamente.

Opciones:

  • fixedRate: Cada X ms (desde inicio de la anterior)
  • fixedDelay: X ms después de terminar la anterior
  • cron: Expresión cron para horarios específicos

Requisito: Habilitar con @EnableScheduling.

Ejemplo práctico:

@Configuration
@EnableScheduling
public class SchedulingConfig {}
@Component
public class TareasProgramadas {
// Cada 5 segundos
@Scheduled(fixedRate = 5000)
public void cadaCincoSegundos() {
System.out.println("Ejecutando...");
}
// 5 segundos después de terminar la anterior
@Scheduled(fixedDelay = 5000)
public void conDelay() {
System.out.println("Con delay...");
}
// Expresión cron (cada día a las 2am)
@Scheduled(cron = "0 0 2 * * ?")
public void cadaDia() {
System.out.println("Tarea diaria");
}
}

16. ¿Cuál es el flujo de una petición en Spring MVC?

Section titled “16. ¿Cuál es el flujo de una petición en Spring MVC?”

¿Qué es? Secuencia de pasos que sigue una petición HTTP en Spring MVC.

Flujo:

  1. DispatcherServlet recibe la petición (Front Controller)
  2. HandlerMapping encuentra el controlador apropiado
  3. HandlerAdapter ejecuta el método del controlador
  4. Controlador retorna ModelAndView o datos
  5. ViewResolver resuelve la vista (si aplica)
  6. Vista renderiza la respuesta al cliente

¿Qué es? Anotación que mapea URLs a métodos del controlador.

Shortcuts (más usados):

  • @GetMapping: Para GET
  • @PostMapping: Para POST
  • @PutMapping: Para PUT
  • @DeleteMapping: Para DELETE
  • @PatchMapping: Para PATCH

Ejemplo práctico:

@RestController
@RequestMapping("/api/v1")
public class MiController {
@RequestMapping(value = "/usuarios", method = RequestMethod.GET)
public List<Usuario> listar() { }
// Shortcuts
@GetMapping("/usuarios")
@PostMapping("/usuarios")
@PutMapping("/usuarios/{id}")
@DeleteMapping("/usuarios/{id}")
@PatchMapping("/usuarios/{id}")
}

18. ¿Qué es @PathVariable vs @RequestParam?

Section titled “18. ¿Qué es @PathVariable vs @RequestParam?”

¿Qué es? Dos formas de extraer datos de la URL.

Diferencias:

  • @PathVariable: Extrae de la ruta (/usuarios/{id}id)
  • @RequestParam: Extrae query params (?nombre=Juannombre)

Ejemplo práctico:

// @PathVariable - parte de la URL
// GET /usuarios/123
@GetMapping("/usuarios/{id}")
public Usuario buscar(@PathVariable Long id) { }
// @RequestParam - query parameters
// GET /usuarios?nombre=Juan&activo=true
@GetMapping("/usuarios")
public List<Usuario> buscar(
@RequestParam String nombre,
@RequestParam(required = false, defaultValue = "true") boolean activo
) { }
// Combinados
// GET /usuarios/123/pedidos?estado=pendiente
@GetMapping("/usuarios/{id}/pedidos")
public List<Pedido> pedidos(
@PathVariable Long id,
@RequestParam String estado
) { }

19. ¿Qué es @RequestBody y @ResponseBody?

Section titled “19. ¿Qué es @RequestBody y @ResponseBody?”

¿Qué es? Anotaciones para conversión automática entre JSON y objetos Java.

Diferencias:

  • @RequestBody: JSON del request → Objeto Java
  • @ResponseBody: Objeto Java → JSON en response

Nota: @RestController incluye @ResponseBody implícitamente.

Ejemplo práctico:

@RestController // Incluye @ResponseBody en todos los métodos
public class UsuarioController {
// @RequestBody - deserializa JSON a objeto
@PostMapping("/usuarios")
public Usuario crear(@RequestBody UsuarioDTO dto) {
return service.crear(dto);
}
// @ResponseBody - serializa objeto a JSON (implícito en @RestController)
@GetMapping("/usuarios/{id}")
public Usuario buscar(@PathVariable Long id) {
return service.buscar(id);
}
}

¿Qué es? Wrapper que representa una respuesta HTTP completa (status, headers, body).

¿Para qué sirve?

  • Controlar el código de estado HTTP
  • Añadir headers personalizados
  • Retornar respuestas vacías con status específico

Ejemplo práctico:

@GetMapping("/usuarios/{id}")
public ResponseEntity<Usuario> buscar(@PathVariable Long id) {
return service.findById(id)
.map(usuario -> ResponseEntity.ok(usuario))
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/usuarios")
public ResponseEntity<Usuario> crear(@RequestBody UsuarioDTO dto) {
Usuario usuario = service.crear(dto);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(usuario.getId())
.toUri();
return ResponseEntity.created(location).body(usuario);
}
// Otros métodos
ResponseEntity.ok(body);
ResponseEntity.noContent().build();
ResponseEntity.badRequest().body(error);
ResponseEntity.status(HttpStatus.CONFLICT).body(error);

¿Qué es? Mecanismo para validar objetos usando Bean Validation (JSR 380).

Anotaciones comunes:

  • @NotNull, @NotBlank, @NotEmpty
  • @Size(min, max), @Min, @Max
  • @Email, @Pattern
  • @Past, @Future (fechas)

Uso: Anotar parámetro con @Valid para activar validación.

Ejemplo práctico:

// DTO con validaciones
public class UsuarioDTO {
@NotBlank(message = "Nombre requerido")
@Size(min = 2, max = 50)
private String nombre;
@Email(message = "Email inválido")
@NotBlank
private String email;
@Min(18)
private int edad;
@Pattern(regexp = "^[0-9]{9}$", message = "Teléfono inválido")
private String telefono;
}
// Controlador
@PostMapping("/usuarios")
public Usuario crear(@Valid @RequestBody UsuarioDTO dto) {
// Si falla validación, lanza MethodArgumentNotValidException
return service.crear(dto);
}
// Validación manual
@Autowired
private Validator validator;
public void validar(Object objeto) {
Set<ConstraintViolation<Object>> violations = validator.validate(objeto);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}

¿Qué es? Componente que intercepta requests antes y después del controlador.

Métodos:

  • preHandle(): Antes del controlador
  • postHandle(): Después del controlador
  • afterCompletion(): Después de renderizar

Usos comunes: Logging, autenticación, métricas, modificar request/response.

Ejemplo práctico:

// Interceptor personalizado
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
System.out.println("Antes del controlador");
return true; // Continuar o detener
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
System.out.println("Después del controlador");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
System.out.println("Después de renderizar vista");
}
}
// Registrar interceptor
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}

¿Qué es? Dos mecanismos para interceptar requests con diferentes niveles de acceso.

Diferencias:

FilterInterceptor
Servlet APISpring MVC
Antes de DispatcherServletDespués de DispatcherServlet
Request/ResponseHandler/ModelAndView
Más bajo nivelMás alto nivel

Recomendación: Usar Interceptor para lógica de Spring MVC, Filter para operaciones de bajo nivel.

¿Qué es? Cross-Origin Resource Sharing - mecanismo de seguridad del navegador para requests entre dominios.

Configuración:

  • Por controlador: @CrossOrigin
  • Global: WebMvcConfigurer.addCorsMappings()

Ejemplo práctico:

// En controlador
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class MiController { }
// Global
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

¿Qué es? Mecanismo para retornar diferentes formatos según lo que el cliente solicite.

¿Cómo funciona? El cliente envía header Accept indicando el formato deseado (JSON, XML, etc.).

Ejemplo práctico:

// Retornar JSON o XML según Accept header
@GetMapping(
value = "/usuarios/{id}",
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
)
public Usuario buscar(@PathVariable Long id) {
return service.buscar(id);
}
// Configuración global
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.favorParameter(true)
.parameterName("format")
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}

26. ¿Qué métodos proporciona JpaRepository?

Section titled “26. ¿Qué métodos proporciona JpaRepository?”

¿Qué es? Interface de Spring Data JPA que proporciona operaciones CRUD y más.

Métodos principales:

  • save(), saveAll(): Guardar entidades
  • findById(), findAll(): Buscar
  • deleteById(), delete(): Eliminar
  • count(), existsById(): Utilidades
  • flush(), saveAndFlush(): Control de persistencia

Ejemplo práctico:

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Heredados de JpaRepository:
// save(entity), saveAll(entities)
// findById(id), findAll(), findAllById(ids)
// count(), existsById(id)
// deleteById(id), delete(entity), deleteAll()
// flush(), saveAndFlush(entity)
}

¿Qué es? Spring Data genera queries automáticamente a partir del nombre del método.

Palabras clave:

  • findBy, readBy, getBy: Buscar
  • And, Or: Combinar condiciones
  • GreaterThan, LessThan, Between: Comparaciones
  • Like, Containing, StartingWith: Texto
  • OrderBy: Ordenamiento
  • Top, First: Limitar resultados

Ejemplo práctico:

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Por propiedad
List<Usuario> findByNombre(String nombre);
Optional<Usuario> findByEmail(String email);
// Múltiples condiciones
List<Usuario> findByNombreAndEdad(String nombre, int edad);
List<Usuario> findByNombreOrEmail(String nombre, String email);
// Comparaciones
List<Usuario> findByEdadGreaterThan(int edad);
List<Usuario> findByEdadBetween(int min, int max);
List<Usuario> findByNombreLike(String patron);
List<Usuario> findByNombreContaining(String texto);
List<Usuario> findByNombreStartingWith(String prefijo);
// Ordenamiento
List<Usuario> findByActivoTrueOrderByNombreAsc();
// Limitar resultados
List<Usuario> findTop5ByOrderByFechaCreacionDesc();
Usuario findFirstByOrderByEdadDesc();
// Null checks
List<Usuario> findByEmailIsNull();
List<Usuario> findByEmailIsNotNull();
}

¿Qué es? Anotación para definir queries JPQL o SQL nativo personalizadas.

¿Cuándo usarlo?

  • Query methods no son suficientes
  • Queries complejas con JOINs
  • Operaciones de actualización/eliminación masiva

Tipos:

  • JPQL (default): Orientado a objetos
  • Nativo (nativeQuery=true): SQL puro

Ejemplo práctico:

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// JPQL
@Query("SELECT u FROM Usuario u WHERE u.activo = true")
List<Usuario> findActivos();
// Con parámetros nombrados
@Query("SELECT u FROM Usuario u WHERE u.nombre LIKE %:nombre%")
List<Usuario> buscarPorNombre(@Param("nombre") String nombre);
// Con parámetros posicionales
@Query("SELECT u FROM Usuario u WHERE u.edad > ?1 AND u.activo = ?2")
List<Usuario> buscar(int edad, boolean activo);
// SQL nativo
@Query(value = "SELECT * FROM usuarios WHERE activo = 1", nativeQuery = true)
List<Usuario> findActivosNativo();
// Modificación
@Modifying
@Query("UPDATE Usuario u SET u.activo = false WHERE u.ultimoAcceso < :fecha")
int desactivarInactivos(@Param("fecha") LocalDate fecha);
}

¿Qué es? API para construir queries dinámicas con criterios combinables.

¿Para qué sirve?

  • Filtros de búsqueda dinámicos
  • Combinar condiciones en runtime
  • Reutilizar criterios de búsqueda

Requisito: Extender JpaSpecificationExecutor.

Ejemplo práctico:

// Para queries dinámicas
public interface UsuarioRepository extends
JpaRepository<Usuario, Long>,
JpaSpecificationExecutor<Usuario> {
}
// Specifications
public class UsuarioSpecifications {
public static Specification<Usuario> nombreContiene(String nombre) {
return (root, query, cb) ->
cb.like(root.get("nombre"), "%" + nombre + "%");
}
public static Specification<Usuario> edadMayorQue(int edad) {
return (root, query, cb) ->
cb.greaterThan(root.get("edad"), edad);
}
public static Specification<Usuario> activo() {
return (root, query, cb) ->
cb.isTrue(root.get("activo"));
}
}
// Uso
List<Usuario> usuarios = repository.findAll(
Specification.where(nombreContiene("Juan"))
.and(edadMayorQue(18))
.and(activo())
);

¿Qué es? Mecanismos para dividir y ordenar resultados de queries.

Componentes:

  • Pageable: Define página y tamaño
  • Page: Resultado con metadatos de paginación
  • Sort: Define ordenamiento

¿Para qué sirve? Evitar cargar todos los registros en memoria.

Ejemplo práctico:

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
Page<Usuario> findByActivo(boolean activo, Pageable pageable);
List<Usuario> findByNombre(String nombre, Sort sort);
}
// Uso
Pageable pageable = PageRequest.of(0, 10, Sort.by("nombre").ascending());
Page<Usuario> pagina = repository.findByActivo(true, pageable);
pagina.getContent(); // Lista de usuarios
pagina.getTotalElements(); // Total de registros
pagina.getTotalPages(); // Total de páginas
pagina.getNumber(); // Página actual
pagina.hasNext(); // ¿Hay siguiente?
// Sorting
Sort sort = Sort.by(Sort.Direction.DESC, "fechaCreacion");
Sort multiSort = Sort.by("nombre").ascending()
.and(Sort.by("edad").descending());

¿Qué es? Anotación que define qué relaciones cargar junto con la entidad.

¿Para qué sirve?

  • Evitar el problema N+1 queries
  • Cargar relaciones LAZY en queries específicas
  • Sin cambiar FetchType global de la entidad

Ejemplo práctico:

// Evitar N+1 queries
@Entity
public class Usuario {
@Id
private Long id;
@OneToMany(mappedBy = "usuario", fetch = FetchType.LAZY)
private List<Pedido> pedidos;
}
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Cargar pedidos junto con usuario
@EntityGraph(attributePaths = {"pedidos"})
List<Usuario> findByActivo(boolean activo);
// Named EntityGraph
@EntityGraph(value = "Usuario.conPedidos")
Optional<Usuario> findById(Long id);
}
// En la entidad
@NamedEntityGraph(
name = "Usuario.conPedidos",
attributeNodes = @NamedAttributeNode("pedidos")
)

¿Qué es? Funcionalidad que registra automáticamente quién y cuándo creó/modificó entidades.

Anotaciones:

  • @CreatedDate: Fecha de creación
  • @LastModifiedDate: Fecha de modificación
  • @CreatedBy: Usuario que creó
  • @LastModifiedBy: Usuario que modificó

Requisito: Habilitar con @EnableJpaAuditing.

Ejemplo práctico:

@Configuration
@EnableJpaAuditing
public class JpaConfig {}
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Usuario {
@CreatedDate
private LocalDateTime fechaCreacion;
@LastModifiedDate
private LocalDateTime fechaModificacion;
@CreatedBy
private String creadoPor;
@LastModifiedBy
private String modificadoPor;
}
// Configurar auditor
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
}
}

¿Qué es? Mecanismo para retornar solo campos específicos en vez de la entidad completa.

Tipos:

  • Interface projection: Define getters para campos deseados
  • Class projection (DTO): Clase con constructor
  • Dynamic projection: Tipo genérico

Ventajas: Mejor performance, seguridad (no exponer campos sensibles).

Ejemplo práctico:

// Interface projection
public interface UsuarioResumen {
String getNombre();
String getEmail();
}
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
List<UsuarioResumen> findByActivo(boolean activo);
}
// Class projection (DTO)
public record UsuarioDTO(String nombre, String email) {}
@Query("SELECT new com.ejemplo.UsuarioDTO(u.nombre, u.email) FROM Usuario u")
List<UsuarioDTO> findAllDTO();
// Dynamic projection
<T> List<T> findByActivo(boolean activo, Class<T> type);
// Uso
List<UsuarioResumen> resumenes = repository.findByActivo(true, UsuarioResumen.class);

¿Qué es? Atributo que define cómo se comporta una transacción cuando ya existe otra.

Tipos principales:

  • REQUIRED (default): Usa existente o crea nueva
  • REQUIRES_NEW: Siempre crea nueva
  • MANDATORY: Debe existir, sino error
  • SUPPORTS: Usa si existe, sino sin transacción
  • NOT_SUPPORTED: Ejecuta sin transacción
  • NEVER: Error si existe transacción

Ejemplo práctico:

// Propagation types
@Transactional(propagation = Propagation.REQUIRED) // Default
// Usa transacción existente o crea nueva
@Transactional(propagation = Propagation.REQUIRES_NEW)
// Siempre crea nueva transacción
@Transactional(propagation = Propagation.MANDATORY)
// Debe existir transacción, sino error
@Transactional(propagation = Propagation.SUPPORTS)
// Usa transacción si existe, sino ejecuta sin transacción
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// Ejecuta sin transacción
@Transactional(propagation = Propagation.NEVER)
// Error si existe transacción

¿Qué es? Atributo que define el nivel de aislamiento entre transacciones concurrentes.

Niveles:

  • READ_UNCOMMITTED: Puede leer datos no confirmados
  • READ_COMMITTED: Solo lee datos confirmados
  • REPEATABLE_READ: Lecturas repetibles consistentes
  • SERIALIZABLE: Máximo aislamiento (más lento)

Problemas que previene: Dirty reads, non-repeatable reads, phantom reads.

Ejemplo práctico:

// Isolation levels
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Puede leer datos no confirmados (dirty reads)
@Transactional(isolation = Isolation.READ_COMMITTED)
// Solo lee datos confirmados
@Transactional(isolation = Isolation.REPEATABLE_READ)
// Lecturas repetibles dan mismo resultado
@Transactional(isolation = Isolation.SERIALIZABLE)
// Máximo aislamiento, más lento

36. ¿Cómo funciona la autenticación en Spring Security?

Section titled “36. ¿Cómo funciona la autenticación en Spring Security?”

¿Qué es? Proceso de verificación de identidad del usuario.

Flujo:

  1. Usuario envía credenciales
  2. AuthenticationFilter intercepta la petición
  3. AuthenticationManager procesa la autenticación
  4. AuthenticationProvider valida credenciales
  5. UserDetailsService carga datos del usuario
  6. Si válido, crea Authentication en SecurityContext

¿Qué es? Interface para cargar datos del usuario desde la base de datos.

Método a implementar: loadUserByUsername(String username)

¿Para qué sirve? Conectar Spring Security con tu sistema de usuarios personalizado.

Ejemplo práctico:

@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UsuarioRepository repository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
Usuario usuario = repository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));
return User.builder()
.username(usuario.getEmail())
.password(usuario.getPassword())
.roles(usuario.getRoles().toArray(new String[0]))
.build();
}
}

¿Qué es? JSON Web Token - estándar para autenticación stateless.

Flujo:

  1. Usuario envía credenciales
  2. Servidor genera JWT con datos del usuario
  3. Cliente envía JWT en header Authorization: Bearer <token>
  4. Servidor valida JWT en cada request

Componentes: JwtUtil (generar/validar) + JwtFilter (interceptar requests).

Ejemplo práctico:

// Generar token
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token, UserDetails userDetails) {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
}
// Filtro JWT
@Component
public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
String username = jwtUtil.extractUsername(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
chain.doFilter(request, response);
}
}

39. ¿Qué es @PreAuthorize y @PostAuthorize?

Section titled “39. ¿Qué es @PreAuthorize y @PostAuthorize?”

¿Qué es? Anotaciones para seguridad a nivel de método.

Diferencias:

  • @PreAuthorize: Verifica ANTES de ejecutar el método
  • @PostAuthorize: Verifica DESPUÉS (puede usar returnObject)
  • @PreFilter/@PostFilter: Filtra colecciones

Requisito: Habilitar con @EnableMethodSecurity.

Ejemplo práctico:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {}
@Service
public class UsuarioService {
@PreAuthorize("hasRole('ADMIN')")
public void eliminar(Long id) { }
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public void actualizar(Usuario usuario) { }
@PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
public Usuario buscar(Long id) { }
@PostAuthorize("returnObject.creador == authentication.name")
public Documento obtenerDocumento(Long id) { }
@PreFilter("filterObject.activo == true")
public void procesarUsuarios(List<Usuario> usuarios) { }
@PostFilter("filterObject.publico == true or filterObject.creador == authentication.name")
public List<Documento> listarDocumentos() { }
}

¿Qué es? Protocolo de autorización para login con proveedores externos.

Proveedores comunes: Google, GitHub, Facebook, Microsoft.

Ventajas:

  • No manejar passwords de usuarios
  • Experiencia de login simplificada
  • Acceso a datos del perfil del proveedor

Ejemplo práctico:

// Configuración OAuth2 Login
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
)
.build();
}
}
// application.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: email, profile

¿Qué es? Cross-Site Request Forgery - ataque donde un sitio malicioso hace requests en nombre del usuario.

En Spring Security:

  • Habilitado por defecto para formularios
  • Deshabilitar para APIs REST stateless (usan JWT)
  • Para SPAs: usar CookieCsrfTokenRepository

Ejemplo práctico:

// CSRF habilitado por defecto para formularios
// Deshabilitar para APIs REST stateless
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // Para APIs REST
.build();
}
// O configurar para SPAs
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)

42. ¿Cómo obtener el usuario autenticado?

Section titled “42. ¿Cómo obtener el usuario autenticado?”

¿Qué es? Formas de acceder a los datos del usuario actualmente autenticado.

Opciones:

  1. @AuthenticationPrincipal: En parámetro del controlador
  2. Principal: Interface estándar de Java
  3. SecurityContextHolder: Programáticamente desde cualquier lugar

Ejemplo práctico:

// En controlador
@GetMapping("/perfil")
public Usuario perfil(@AuthenticationPrincipal UserDetails userDetails) {
return service.buscarPorEmail(userDetails.getUsername());
}
// Con Principal
@GetMapping("/perfil")
public Usuario perfil(Principal principal) {
return service.buscarPorEmail(principal.getName());
}
// Programáticamente
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Collection<? extends GrantedAuthority> roles = auth.getAuthorities();

¿Qué es? Interface para encriptar y verificar passwords de forma segura.

Implementaciones:

  • BCryptPasswordEncoder: Estándar recomendado
  • Argon2PasswordEncoder: Más seguro, más lento
  • DelegatingPasswordEncoder: Soporta múltiples algoritmos

Nunca almacenar passwords en texto plano.

Ejemplo práctico:

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Uso
String encoded = passwordEncoder.encode("password123");
boolean matches = passwordEncoder.matches("password123", encoded);
// DelegatingPasswordEncoder (recomendado)
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// Soporta múltiples algoritmos: {bcrypt}hash, {scrypt}hash, etc.

¿Qué es? Configurar autorización basada en roles para diferentes endpoints.

Métodos comunes:

  • permitAll(): Acceso público
  • authenticated(): Requiere autenticación
  • hasRole("ADMIN"): Requiere rol específico
  • hasAnyRole("USER", "ADMIN"): Cualquiera de los roles

Ejemplo práctico:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.build();
}

¿Qué es? Objeto que almacena el Authentication del usuario actual.

Acceso: Vía SecurityContextHolder.getContext().

Estrategias de almacenamiento:

  • MODE_THREADLOCAL (default): Por hilo
  • MODE_INHERITABLETHREADLOCAL: Heredado a hilos hijos
  • MODE_GLOBAL: Compartido globalmente

Ejemplo práctico:

// SecurityContext almacena Authentication del usuario actual
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
// Estrategias de almacenamiento
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL); // Default
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);

¿Qué es? Tests de integración para la capa web.

Herramientas:

  • @WebMvcTest: Carga solo la capa web (rápido)
  • MockMvc: Simula requests HTTP
  • @MockBean: Mockea dependencias (servicios)

Ejemplo práctico:

@WebMvcTest(UsuarioController.class)
class UsuarioControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UsuarioService service;
@Test
void listarUsuarios() throws Exception {
when(service.findAll()).thenReturn(List.of(
new Usuario(1L, "Juan"),
new Usuario(2L, "Ana")
));
mockMvc.perform(get("/api/usuarios"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].nombre").value("Juan"));
}
@Test
void crearUsuario() throws Exception {
UsuarioDTO dto = new UsuarioDTO("Juan", "juan@test.com");
Usuario usuario = new Usuario(1L, "Juan");
when(service.crear(any())).thenReturn(usuario);
mockMvc.perform(post("/api/usuarios")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1));
}
}

¿Qué es? Tests de integración para la capa de persistencia.

Herramientas:

  • @DataJpaTest: Configura BD en memoria + repositorios
  • TestEntityManager: Preparar datos de prueba
  • Rollback automático después de cada test

Ejemplo práctico:

@DataJpaTest
class UsuarioRepositoryTest {
@Autowired
private UsuarioRepository repository;
@Autowired
private TestEntityManager entityManager;
@Test
void findByEmail() {
Usuario usuario = new Usuario("Juan", "juan@test.com");
entityManager.persistAndFlush(usuario);
Optional<Usuario> encontrado = repository.findByEmail("juan@test.com");
assertThat(encontrado).isPresent();
assertThat(encontrado.get().getNombre()).isEqualTo("Juan");
}
}

¿Qué es? Tests unitarios para la capa de lógica de negocio.

Herramientas:

  • @ExtendWith(MockitoExtension.class): Habilita Mockito
  • @Mock: Crea mocks de dependencias
  • @InjectMocks: Inyecta mocks en el servicio

Ejemplo práctico:

@ExtendWith(MockitoExtension.class)
class UsuarioServiceTest {
@Mock
private UsuarioRepository repository;
@InjectMocks
private UsuarioService service;
@Test
void buscarUsuario() {
Usuario usuario = new Usuario(1L, "Juan");
when(repository.findById(1L)).thenReturn(Optional.of(usuario));
Usuario resultado = service.buscar(1L);
assertThat(resultado.getNombre()).isEqualTo("Juan");
verify(repository).findById(1L);
}
}

¿Qué es? Anotación que carga el contexto completo de Spring para tests de integración.

Características:

  • Carga todos los beans de la aplicación
  • Más lento que tests unitarios
  • Prueba la integración real entre componentes

WebEnvironment:

  • MOCK (default): Sin servidor
  • RANDOM_PORT: Servidor en puerto aleatorio
  • DEFINED_PORT: Servidor en puerto configurado

Ejemplo práctico:

@SpringBootTest
class IntegrationTest {
@Autowired
private UsuarioService service;
@Test
void integrationTest() {
// Test con contexto completo de Spring
}
}
// Con servidor embebido
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int port;
@Test
void testEndpoint() {
String url = "http://localhost:" + port + "/api/usuarios";
ResponseEntity<List> response = restTemplate.getForEntity(url, List.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

¿Qué es? Librería para ejecutar contenedores Docker en tests.

¿Para qué sirve?

  • Probar con BD real (PostgreSQL, MySQL, MongoDB)
  • Probar con servicios externos (Redis, Kafka, RabbitMQ)
  • Tests más realistas que con BD en memoria

Requisito: Docker instalado y corriendo.

Ejemplo práctico:

@SpringBootTest
@Testcontainers
class DatabaseIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UsuarioRepository repository;
@Test
void testWithRealDatabase() {
Usuario usuario = repository.save(new Usuario("Juan"));
assertThat(usuario.getId()).isNotNull();
}
}

🐝