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.
Spring Core (1-15)
Section titled “Spring Core (1-15)”1. ¿Qué es el ApplicationContext?
Section titled “1. ¿Qué es el ApplicationContext?”¿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 anotacionesAnnotationConfigWebApplicationContext: Para aplicaciones webClassPathXmlApplicationContext: Para configuración XML (legacy)
Ejemplo práctico:
// Tipos de ApplicationContext// Para aplicaciones standaloneApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// Para aplicaciones webApplicationContext ctx = new AnnotationConfigWebApplicationContext();
// Obtener beansMiServicio 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:
| BeanFactory | ApplicationContext |
|---|---|
| Lazy loading | Eager loading |
| Básico | Más funcionalidades |
| Menos memoria | Más memoria |
| Sin eventos | Soporte de eventos |
| Sin i18n | Soporte i18n |
Recomendación: Usar siempre ApplicationContext excepto en entornos con recursos muy limitados.
3. ¿Qué es @Autowired?
Section titled “3. ¿Qué es @Autowired?”¿Qué es? Anotación que indica a Spring que debe inyectar una dependencia automáticamente.
Tipos de inyección:
- Constructor injection (recomendado): Dependencias inmutables, fácil testing
- Setter injection: Para dependencias opcionales
- 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:
@Servicepublic 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; }}4. ¿Qué es @Qualifier?
Section titled “4. ¿Qué es @Qualifier?”¿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 tipopublic interface NotificacionService {}
@Service("emailService")public class EmailService implements NotificacionService {}
@Service("smsService")public class SmsService implements NotificacionService {}
// Usar @Qualifier para especificar cuál@Servicepublic class AlertaService {
private final NotificacionService notificacion;
public AlertaService(@Qualifier("emailService") NotificacionService notificacion) { this.notificacion = notificacion; }}5. ¿Qué es @Primary?
Section titled “5. ¿Qué es @Primary?”¿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@Primarypublic class EmailService implements NotificacionService {}
@Servicepublic class SmsService implements NotificacionService {}
// Se inyecta EmailService por defecto@Servicepublic class AlertaService { private final NotificacionService notificacion; // EmailService}6. ¿Qué es @Value?
Section titled “6. ¿Qué es @Value?”¿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:
@Servicepublic 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;}7. ¿Qué es @ConfigurationProperties?
Section titled “7. ¿Qué es @ConfigurationProperties?”¿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:
# 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@Servicepublic class MiServicio { private final AppProperties props;
public MiServicio(AppProperties props) { this.props = props; }}8. ¿Qué es el ciclo de vida de un Bean?
Section titled “8. ¿Qué es el ciclo de vida de un Bean?”¿Qué es? Secuencia de fases por las que pasa un bean desde su creación hasta su destrucción.
Fases:
- Instanciación: Se llama al constructor
- Inyección: Se inyectan dependencias
- @PostConstruct: Inicialización personalizada
- Uso: Bean disponible para la aplicación
- @PreDestroy: Limpieza antes de destrucción
- Destrucción: Bean eliminado del contexto
Ejemplo práctico:
@Componentpublic 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();}9. ¿Qué es @Lazy?
Section titled “9. ¿Qué es @Lazy?”¿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@Lazypublic class BeanPesado { public BeanPesado() { // Inicialización costosa }}
// En inyección@Servicepublic class MiServicio {
@Autowired @Lazy private BeanPesado beanPesado; // Se crea al primer uso}10. ¿Qué es @Profile?
Section titled “10. ¿Qué es @Profile?”¿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=produccion11. ¿Qué es @Conditional?
Section titled “11. ¿Qué es @Conditional?”¿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@Componentpublic 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()); }}13. ¿Qué es @EnableAspectJAutoProxy?
Section titled “13. ¿Qué es @EnableAspectJAutoProxy?”¿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@EnableAspectJAutoProxypublic class AopConfig { // Habilita AOP con proxies}
// proxyTargetClass = true para usar CGLIB en vez de JDK proxies@EnableAspectJAutoProxy(proxyTargetClass = true)14. ¿Qué son los eventos en Spring?
Section titled “14. ¿Qué son los eventos en Spring?”¿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 personalizadopublic 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@Servicepublic class UsuarioService {
@Autowired private ApplicationEventPublisher publisher;
public void registrar(Usuario usuario) { // Guardar usuario publisher.publishEvent(new UsuarioRegistradoEvent(this, usuario)); }}
// Escuchar evento@Componentpublic class EmailListener {
@EventListener public void onUsuarioRegistrado(UsuarioRegistradoEvent event) { enviarEmailBienvenida(event.getUsuario()); }
@Async @EventListener public void onUsuarioRegistradoAsync(UsuarioRegistradoEvent event) { // Procesamiento asíncrono }}15. ¿Qué es @Scheduled?
Section titled “15. ¿Qué es @Scheduled?”¿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 anteriorcron: Expresión cron para horarios específicos
Requisito: Habilitar con @EnableScheduling.
Ejemplo práctico:
@Configuration@EnableSchedulingpublic class SchedulingConfig {}
@Componentpublic 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"); }}Spring MVC (16-25)
Section titled “Spring MVC (16-25)”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:
- DispatcherServlet recibe la petición (Front Controller)
- HandlerMapping encuentra el controlador apropiado
- HandlerAdapter ejecuta el método del controlador
- Controlador retorna ModelAndView o datos
- ViewResolver resuelve la vista (si aplica)
- Vista renderiza la respuesta al cliente
17. ¿Qué es @RequestMapping?
Section titled “17. ¿Qué es @RequestMapping?”¿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=Juan→nombre)
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étodospublic 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); }}20. ¿Qué es ResponseEntity?
Section titled “20. ¿Qué es ResponseEntity?”¿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étodosResponseEntity.ok(body);ResponseEntity.noContent().build();ResponseEntity.badRequest().body(error);ResponseEntity.status(HttpStatus.CONFLICT).body(error);21. ¿Qué es @Valid y validación?
Section titled “21. ¿Qué es @Valid y validación?”¿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 validacionespublic 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@Autowiredprivate Validator validator;
public void validar(Object objeto) { Set<ConstraintViolation<Object>> violations = validator.validate(objeto); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); }}22. ¿Qué es un Interceptor?
Section titled “22. ¿Qué es un Interceptor?”¿Qué es? Componente que intercepta requests antes y después del controlador.
Métodos:
preHandle(): Antes del controladorpostHandle(): Después del controladorafterCompletion(): Después de renderizar
Usos comunes: Logging, autenticación, métricas, modificar request/response.
Ejemplo práctico:
// Interceptor personalizado@Componentpublic 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@Configurationpublic class WebConfig implements WebMvcConfigurer {
@Autowired private LoggingInterceptor loggingInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loggingInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/public/**"); }}23. ¿Qué es un Filter vs Interceptor?
Section titled “23. ¿Qué es un Filter vs Interceptor?”¿Qué es? Dos mecanismos para interceptar requests con diferentes niveles de acceso.
Diferencias:
| Filter | Interceptor |
|---|---|
| Servlet API | Spring MVC |
| Antes de DispatcherServlet | Después de DispatcherServlet |
| Request/Response | Handler/ModelAndView |
| Más bajo nivel | Más alto nivel |
Recomendación: Usar Interceptor para lógica de Spring MVC, Filter para operaciones de bajo nivel.
24. ¿Cómo manejar CORS?
Section titled “24. ¿Cómo manejar CORS?”¿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")@RestControllerpublic class MiController { }
// Global@Configurationpublic 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); }}25. ¿Qué es content negotiation?
Section titled “25. ¿Qué es content negotiation?”¿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@Configurationpublic 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); }}Spring Data (26-35)
Section titled “Spring Data (26-35)”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 entidadesfindById(),findAll(): BuscardeleteById(),delete(): Eliminarcount(),existsById(): Utilidadesflush(),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)}27. ¿Cómo crear query methods?
Section titled “27. ¿Cómo crear query methods?”¿Qué es? Spring Data genera queries automáticamente a partir del nombre del método.
Palabras clave:
findBy,readBy,getBy: BuscarAnd,Or: Combinar condicionesGreaterThan,LessThan,Between: ComparacionesLike,Containing,StartingWith: TextoOrderBy: OrdenamientoTop,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();}28. ¿Qué es @Query?
Section titled “28. ¿Qué es @Query?”¿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);}29. ¿Qué es Specification?
Section titled “29. ¿Qué es Specification?”¿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ámicaspublic interface UsuarioRepository extends JpaRepository<Usuario, Long>, JpaSpecificationExecutor<Usuario> {}
// Specificationspublic 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")); }}
// UsoList<Usuario> usuarios = repository.findAll( Specification.where(nombreContiene("Juan")) .and(edadMayorQue(18)) .and(activo()));30. ¿Qué es Pagination y Sorting?
Section titled “30. ¿Qué es Pagination y Sorting?”¿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);}
// UsoPageable pageable = PageRequest.of(0, 10, Sort.by("nombre").ascending());Page<Usuario> pagina = repository.findByActivo(true, pageable);
pagina.getContent(); // Lista de usuariospagina.getTotalElements(); // Total de registrospagina.getTotalPages(); // Total de páginaspagina.getNumber(); // Página actualpagina.hasNext(); // ¿Hay siguiente?
// SortingSort sort = Sort.by(Sort.Direction.DESC, "fechaCreacion");Sort multiSort = Sort.by("nombre").ascending() .and(Sort.by("edad").descending());31. ¿Qué es @EntityGraph?
Section titled “31. ¿Qué es @EntityGraph?”¿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@Entitypublic 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"))32. ¿Qué es Auditing?
Section titled “32. ¿Qué es Auditing?”¿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@EnableJpaAuditingpublic 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@Componentpublic class AuditorAwareImpl implements AuditorAware<String> {
@Override public Optional<String> getCurrentAuditor() { return Optional.ofNullable(SecurityContextHolder.getContext()) .map(SecurityContext::getAuthentication) .map(Authentication::getName); }}33. ¿Qué es Projection?
Section titled “33. ¿Qué es Projection?”¿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 projectionpublic 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);
// UsoList<UsuarioResumen> resumenes = repository.findByActivo(true, UsuarioResumen.class);34. ¿Qué es @Transactional propagation?
Section titled “34. ¿Qué es @Transactional propagation?”¿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ón35. ¿Qué es @Transactional isolation?
Section titled “35. ¿Qué es @Transactional isolation?”¿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 lentoSpring Security (36-45)
Section titled “Spring Security (36-45)”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:
- Usuario envía credenciales
- AuthenticationFilter intercepta la petición
- AuthenticationManager procesa la autenticación
- AuthenticationProvider valida credenciales
- UserDetailsService carga datos del usuario
- Si válido, crea Authentication en SecurityContext
37. ¿Qué es UserDetailsService?
Section titled “37. ¿Qué es UserDetailsService?”¿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:
@Servicepublic 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(); }}38. ¿Cómo implementar JWT?
Section titled “38. ¿Cómo implementar JWT?”¿Qué es? JSON Web Token - estándar para autenticación stateless.
Flujo:
- Usuario envía credenciales
- Servidor genera JWT con datos del usuario
- Cliente envía JWT en header
Authorization: Bearer <token> - Servidor valida JWT en cada request
Componentes: JwtUtil (generar/validar) + JwtFilter (interceptar requests).
Ejemplo práctico:
// Generar token@Componentpublic 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@Componentpublic 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@EnableMethodSecuritypublic class SecurityConfig {}
@Servicepublic 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() { }}40. ¿Qué es OAuth2?
Section titled “40. ¿Qué es OAuth2?”¿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@Configurationpublic class SecurityConfig {
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .oauth2Login(oauth2 -> oauth2 .loginPage("/login") .defaultSuccessUrl("/dashboard") ) .build(); }}
// application.ymlspring:security: oauth2: client: registration: google: client-id: ${GOOGLE_CLIENT_ID} client-secret: ${GOOGLE_CLIENT_SECRET} scope: email, profile41. ¿Qué es CSRF y cómo manejarlo?
Section titled “41. ¿Qué es CSRF y cómo manejarlo?”¿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@Beanpublic 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:
- @AuthenticationPrincipal: En parámetro del controlador
- Principal: Interface estándar de Java
- 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áticamenteAuthentication auth = SecurityContextHolder.getContext().getAuthentication();String username = auth.getName();Collection<? extends GrantedAuthority> roles = auth.getAuthorities();43. ¿Qué es PasswordEncoder?
Section titled “43. ¿Qué es PasswordEncoder?”¿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:
@Beanpublic PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder();}
// UsoString encoded = passwordEncoder.encode("password123");boolean matches = passwordEncoder.matches("password123", encoded);
// DelegatingPasswordEncoder (recomendado)@Beanpublic PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder();}// Soporta múltiples algoritmos: {bcrypt}hash, {scrypt}hash, etc.44. ¿Cómo proteger endpoints por rol?
Section titled “44. ¿Cómo proteger endpoints por rol?”¿Qué es? Configurar autorización basada en roles para diferentes endpoints.
Métodos comunes:
permitAll(): Acceso públicoauthenticated(): Requiere autenticaciónhasRole("ADMIN"): Requiere rol específicohasAnyRole("USER", "ADMIN"): Cualquiera de los roles
Ejemplo práctico:
@Beanpublic 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();}45. ¿Qué es SecurityContext?
Section titled “45. ¿Qué es SecurityContext?”¿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 actualSecurityContext context = SecurityContextHolder.getContext();Authentication auth = context.getAuthentication();
// Estrategias de almacenamientoSecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL); // DefaultSecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);Testing (46-50)
Section titled “Testing (46-50)”46. ¿Cómo testear controladores?
Section titled “46. ¿Cómo testear controladores?”¿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)); }}47. ¿Cómo testear repositorios?
Section titled “47. ¿Cómo testear repositorios?”¿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:
@DataJpaTestclass 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"); }}48. ¿Cómo testear servicios?
Section titled “48. ¿Cómo testear servicios?”¿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); }}49. ¿Qué es @SpringBootTest?
Section titled “49. ¿Qué es @SpringBootTest?”¿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 servidorRANDOM_PORT: Servidor en puerto aleatorioDEFINED_PORT: Servidor en puerto configurado
Ejemplo práctico:
@SpringBootTestclass 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); }}50. ¿Qué es TestContainers?
Section titled “50. ¿Qué es TestContainers?”¿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@Testcontainersclass 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(); }}