Spring Boot - Pytania Rekrutacyjne dla Java Backend Developera [2026]

Sławomir Plamowski 20 min czytania
backend java jpa microservices rest-api spring spring-boot

Przygotowujesz się do rozmowy na stanowisko Java Backend Developer? Spring Boot to obecnie najważniejszy framework w ekosystemie Java. Ten przewodnik zawiera 56 pytań rekrutacyjnych z odpowiedziami - od podstaw IoC/DI przez Spring MVC, JPA, Security po Spring Cloud i microservices.

Spis treści


Podstawy Spring Framework

Czym jest Spring Framework i jakie problemy rozwiązuje?

Odpowiedź w 30 sekund: Spring to framework dla Javy ułatwiający budowanie aplikacji enterprise. Główne korzyści: Inversion of Control (IoC) - kontener zarządza obiektami, Dependency Injection - luźne powiązanie komponentów, AOP - aspekty cross-cutting (transakcje, logowanie), integracja z technologiami (JPA, MQ, REST).

Odpowiedź w 2 minuty:

Spring Framework rozwiązał kluczowe problemy w rozwoju aplikacji enterprise Java, które wcześniej wymagały dużo boilerplate code i ręcznej konfiguracji.

Problemy przed Springiem:
┌─────────────────────────────────────────────────────────┐
│ ❌ Ręczne tworzenie obiektów (new Service())           │
│ ❌ Silne powiązania między klasami                     │
│ ❌ Boilerplate: JDBC, transakcje, wyjątki              │
│ ❌ Trudne testowanie (brak mocków)                     │
│ ❌ Ręczna konfiguracja serwera                         │
└─────────────────────────────────────────────────────────┘

Spring rozwiązuje:
┌─────────────────────────────────────────────────────────┐
│ ✅ IoC Container - zarządza cyklem życia obiektów      │
│ ✅ DI - wstrzykuje zależności, luźne powiązanie        │
│ ✅ Abstrakcje - JdbcTemplate, RestTemplate             │
│ ✅ AOP - transakcje, security, logowanie               │
│ ✅ Testowanie - @MockBean, TestContext                 │
└─────────────────────────────────────────────────────────┘
// Bez Springa - silne powiązanie
class OrderService {
    private PaymentService payment = new PaymentService(); // new!
    private InventoryService inventory = new InventoryService();
}

// Ze Springiem - luźne powiązanie
@Service
class OrderService {
    private final PaymentService payment;     // wstrzyknięte
    private final InventoryService inventory;

    OrderService(PaymentService p, InventoryService i) {
        this.payment = p;
        this.inventory = i;
    }
}

Wyjaśnij pojęcie Inversion of Control (IoC) i Dependency Injection (DI)

Odpowiedź w 30 sekund: IoC - odwrócenie kontroli: to framework (nie programista) decyduje kiedy tworzyć obiekty i wywoływać metody. DI - forma IoC: kontener automatycznie "wstrzykuje" zależności do obiektów przez konstruktor, setter lub pole. Korzyść: luźne powiązanie, łatwe testowanie.

Odpowiedź w 2 minuty:

Porównajmy tradycyjne podejście z podejściem Spring, aby zobaczyć różnicę między kontrolą programisty a kontrolą frameworka.

// Tradycyjne podejście (programista kontroluje)
class UserService {
    private UserRepository repo = new UserRepository();  // programista tworzy
}

// IoC + DI (Spring kontroluje)
@Service
class UserService {
    private final UserRepository repo;

    // 1. Constructor Injection (zalecane)
    UserService(UserRepository repo) {
        this.repo = repo;  // Spring wstrzykuje
    }

    // 2. Setter Injection
    @Autowired
    void setRepo(UserRepository repo) {
        this.repo = repo;
    }

    // 3. Field Injection (nie zalecane)
    @Autowired
    private UserRepository repo;
}
┌─────────────────────────────────────────────────────────┐
│              IoC Container (ApplicationContext)         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │ UserService │  │UserRepo     │  │PasswordEncoder │  │
│  │             │◄─┤ (bean)      │  │    (bean)       │  │
│  │ @Service    │  └─────────────┘  └─────────────────┘  │
│  └─────────────┘                                        │
│        ▲ Spring tworzy i wstrzykuje zależności         │
└─────────────────────────────────────────────────────────┘
Typ DI Zalety Wady
Constructor Immutable, wymagane zależności Dużo parametrów
Setter Opcjonalne zależności Mutowalne
Field Zwięzłe Utrudnia testowanie

Porównaj @Component, @Repository, @Service i @Controller

Odpowiedź w 30 sekund: Wszystkie są stereotype annotations oznaczające beany do skanowania. @Component - generyczny. @Repository - warstwa danych (dodaje translację wyjątków). @Service - logika biznesowa. @Controller - warstwa prezentacji (HTTP). Semantycznie rozróżniają warstwy aplikacji.

Odpowiedź w 2 minuty:

Każda adnotacja odpowiada za konkretną warstwę aplikacji i ma swoje specjalne zachowanie, jak pokazano poniżej.

// Warstwy aplikacji
┌─────────────────────────────────────────┐
│  @Controller / @RestController          │  ← Prezentacja (HTTP)
├─────────────────────────────────────────┤
│  @Service                               │  ← Logika biznesowa
├─────────────────────────────────────────┤
│  @Repository                            │  ← Dostęp do danych
└─────────────────────────────────────────┘

@Controller
public class UserController {
    @GetMapping("/users")
    public String listUsers(Model model) {...}
}

@Service
public class UserService {
    public User createUser(UserDto dto) {...}
}

@Repository
public class UserRepository {
    public User findByEmail(String email) {...}
}

@Component  // generyczny - gdy nie pasuje do powyższych
public class EmailValidator {...}
Adnotacja Warstwa Specjalne zachowanie
@Component Dowolna Bazowa stereotype
@Repository DAO Translacja SQLException → DataAccessException
@Service Business Semantyczne oznaczenie
@Controller Web Obsługa HTTP, widoki
@RestController REST API @Controller + @ResponseBody

Spring Beans i Konfiguracja

Jak działają zakresy beanów (scope)?

Odpowiedź w 30 sekund: Singleton (domyślny) - jedna instancja na cały kontener. Prototype - nowa instancja przy każdym żądaniu. Request/Session (web) - jedna instancja per żądanie HTTP / sesja użytkownika. Wybór scope zależy od stanu: stateless → singleton, stateful → prototype/request.

Odpowiedź w 2 minuty:

Spring oferuje kilka zakresów, które określają cykl życia beana i liczbę tworzonych instancji w kontenerze.

@Component
@Scope("singleton")  // domyślne
public class SingletonBean { }

@Component
@Scope("prototype")  // nowy przy każdym getBean()
public class PrototypeBean { }

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean { }  // per HTTP request

@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean { }  // per HTTP session
Singleton:
┌─────────────────────────────────────────┐
│     ApplicationContext                  │
│  ┌─────────────────────────────────┐   │
│  │  UserService (singleton)         │   │
│  │  [jedna instancja]               │   │
│  └─────────────────────────────────┘   │
│         ▲           ▲                   │
│   getBean()    getBean()   → ta sama   │
└─────────────────────────────────────────┘

Prototype:
┌─────────────────────────────────────────┐
│  getBean() → new PrototypeBean()        │
│  getBean() → new PrototypeBean()        │
│  (Spring nie zarządza cyklem życia!)    │
└─────────────────────────────────────────┘
Scope Instancje Użycie
singleton 1 per kontener Stateless services
prototype Nowy per żądanie Stateful, nie thread-safe
request 1 per HTTP request Web: dane żądania
session 1 per HTTP session Web: dane sesji

Jak zarządzać cyklem życia beana?

Odpowiedź w 30 sekund: Spring wywołuje metody lifecycle: 1) Konstruktor, 2) DI, 3) @PostConstruct / InitializingBean, 4) Bean ready, 5) @PreDestroy / DisposableBean przy zamykaniu. Alternatywnie: @Bean(initMethod, destroyMethod). Używaj do inicjalizacji zasobów i cleanup.

Odpowiedź w 2 minuty:

Spring pozwala kontrolować cykl życia beana za pomocą adnotacji lub metod konfiguracyjnych, co jest przydatne przy inicjalizacji zasobów.

@Component
public class DatabaseConnection {

    private Connection connection;

    // 1. Konstruktor
    public DatabaseConnection() {
        System.out.println("1. Constructor");
    }

    // 2. @PostConstruct - po DI
    @PostConstruct
    public void init() {
        System.out.println("3. PostConstruct - connecting...");
        this.connection = createConnection();
    }

    // 3. @PreDestroy - przed zniszczeniem
    @PreDestroy
    public void cleanup() {
        System.out.println("4. PreDestroy - closing...");
        connection.close();
    }
}

// Alternatywnie z @Bean
@Configuration
public class AppConfig {
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public DatabaseConnection dbConnection() {
        return new DatabaseConnection();
    }
}
Lifecycle beana:
┌─────────────────────────────────────────────────────┐
│ 1. Instantiation (konstruktor)                      │
│         ↓                                           │
│ 2. Dependency Injection (@Autowired)                │
│         ↓                                           │
│ 3. @PostConstruct / InitializingBean.afterPropertiesSet() │
│         ↓                                           │
│ 4. Bean ready ✓                                     │
│         ↓ (application shutdown)                    │
│ 5. @PreDestroy / DisposableBean.destroy()           │
└─────────────────────────────────────────────────────┘

Spring Boot

Czym jest Spring Boot i w czym pomaga?

Odpowiedź w 30 sekund: Spring Boot to framework upraszczający tworzenie aplikacji Spring. Główne cechy: auto-konfiguracja (zgaduje konfigurację na podstawie classpath), wbudowany serwer (Tomcat, Jetty), startery (gotowe zestawy zależności), opinionated defaults. Zamiast ręcznej konfiguracji XML - @SpringBootApplication i aplikacja działa.

Odpowiedź w 2 minuty:

Spring Boot radykalnie upraszcza konfigurację - wystarczy jedna adnotacja i aplikacja jest gotowa do uruchomienia z wbudowanym serwerem.

// Cała aplikacja Spring Boot
@SpringBootApplication  // = @Configuration + @EnableAutoConfiguration + @ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Działający REST endpoint
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello World";
    }
}
// Uruchom i działa na http://localhost:8080/hello
Spring tradycyjny Spring Boot
web.xml, dispatcher-servlet.xml @SpringBootApplication
Ręczna konfiguracja DataSource Auto-config z application.properties
Deploy na zewnętrzny Tomcat Wbudowany Tomcat (java -jar)
Ręczne zarządzanie zależnościami Startery ze spójnymi wersjami
# application.yml - prosta konfiguracja
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: user
    password: pass

server:
  port: 8080

Co to są startery w Spring Boot?

Odpowiedź w 30 sekund: Startery to gotowe zestawy zależności Maven/Gradle. spring-boot-starter-web zawiera Tomcat, Jackson, Spring MVC. spring-boot-starter-data-jpa zawiera Hibernate, HikariCP, Spring Data. Eliminują ręczne dodawanie zależności i zapewniają kompatybilność wersji.

Odpowiedź w 2 minuty:

Zamiast ręcznie dodawać dziesiątki zależności, wystarczy jeden starter, który zawiera wszystkie potrzebne biblioteki w spójnych wersjach.

<!-- pom.xml - jeden starter = wiele zależności -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Co zawiera spring-boot-starter-web: -->
<!-- - spring-webmvc -->
<!-- - spring-boot-starter-tomcat -->
<!-- - jackson-databind (JSON) -->
<!-- - hibernate-validator -->
Starter Zawiera Użycie
starter-web Tomcat, MVC, Jackson REST API
starter-data-jpa Hibernate, HikariCP Bazy SQL
starter-security Spring Security Autentykacja
starter-test JUnit, Mockito, MockMvc Testowanie
starter-actuator Health checks, metrics Monitoring
starter-validation Hibernate Validator Walidacja
<!-- Typowy zestaw starterów dla REST API z bazą -->
<dependencies>
    <dependency>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

Jak monitorować aplikację Spring Boot (Actuator)?

Odpowiedź w 30 sekund: Spring Boot Actuator dodaje endpoints do monitoringu: /actuator/health (status aplikacji), /actuator/metrics (metryki), /actuator/info (informacje). Integruje się z Prometheus, Grafana, Micrometer. Konfiguruj które endpoints wystawić i zabezpiecz je w produkcji.

Odpowiedź w 2 minuty:

Actuator dodaje gotowe endpointy monitorujące, które można łatwo skonfigurować i wystawić publicznie lub zabezpieczyć.

<dependency>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when_authorized

# Custom info
info:
  app:
    name: My Application
    version: 1.0.0
Endpoint URL Opis
health /actuator/health Status aplikacji
info /actuator/info Informacje o aplikacji
metrics /actuator/metrics Metryki (memory, cpu)
prometheus /actuator/prometheus Format Prometheus
env /actuator/env Zmienne środowiskowe
beans /actuator/beans Lista beanów
// GET /actuator/health
{
  "status": "UP",
  "components": {
    "db": { "status": "UP" },
    "diskSpace": { "status": "UP" }
  }
}
// Custom Health Indicator
@Component
public class PaymentHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        if (paymentServiceAvailable()) {
            return Health.up().withDetail("provider", "Stripe").build();
        }
        return Health.down().withDetail("error", "Service unavailable").build();
    }
}

Spring MVC i REST API

Jak działa przepływ żądania HTTP w Spring MVC?

Odpowiedź w 30 sekund:

  1. Żądanie trafia do DispatcherServlet (front controller), 2) Handler Mapping znajduje odpowiedni @Controller, 3) Controller przetwarza i zwraca ModelAndView lub obiekt, 4) ViewResolver renderuje widok (lub Jackson serializuje JSON), 5) Response wraca do klienta.

Odpowiedź w 2 minuty:

Cały przepływ żądania HTTP w Spring MVC koordynuje DispatcherServlet, który deleguje zadania do odpowiednich komponentów.

HTTP Request
     │
     ▼
┌────────────────────────────────────────────────────────────┐
│                  DispatcherServlet                         │
│                  (Front Controller)                        │
└────────────────────────────────────────────────────────────┘
     │
     ▼ 1. Handler Mapping - znajdź controller
┌────────────────────────────────────────────────────────────┐
│  @GetMapping("/users/{id}")                                │
│  public User getUser(@PathVariable Long id)                │
└────────────────────────────────────────────────────────────┘
     │
     ▼ 2. Handler Adapter - wywołaj metodę
┌────────────────────────────────────────────────────────────┐
│  Argument Resolvers: @PathVariable, @RequestBody, etc.     │
│  Return Value Handlers: ResponseEntity, @ResponseBody      │
└────────────────────────────────────────────────────────────┘
     │
     ▼ 3. View Resolver / HttpMessageConverter
┌────────────────────────────────────────────────────────────┐
│  JSON: MappingJackson2HttpMessageConverter                 │
│  HTML: ThymeleafViewResolver                               │
└────────────────────────────────────────────────────────────┘
     │
     ▼
HTTP Response

Czym różni się @Controller od @RestController?

Odpowiedź w 30 sekund: @Controller - zwraca nazwy widoków (Thymeleaf, JSP), używany w tradycyjnych aplikacjach MVC. @RestController - to @Controller + @ResponseBody, automatycznie serializuje obiekty do JSON/XML, używany w REST API. Różnica: widoki vs dane.

Odpowiedź w 2 minuty:

Główna różnica polega na tym, co kontroler zwraca - nazwy widoków do renderowania HTML czy dane do serializacji JSON.

// @Controller - zwraca widoki (HTML)
@Controller
public class WebController {

    @GetMapping("/users")
    public String listUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        return "users/list";  // → templates/users/list.html
    }

    @GetMapping("/users/{id}")
    @ResponseBody  // potrzebne dla JSON
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);  // JSON
    }
}

// @RestController - zwraca dane (JSON/XML)
@RestController
@RequestMapping("/api/users")
public class UserApiController {

    @GetMapping
    public List<User> listUsers() {
        return userService.findAll();  // automatycznie JSON
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User createUser(@Valid @RequestBody UserDto dto) {
        return userService.create(dto);
    }
}
Aspekt @Controller @RestController
Zwraca Nazwy widoków Obiekty (JSON)
@ResponseBody Manualne Automatyczne
Użycie Web MVC REST API
Content-Type text/html application/json

Jak obsłużyć walidację danych wejściowych?

Odpowiedź w 30 sekund: Użyj Bean Validation (JSR-380) z adnotacjami @Valid i @NotNull, @Size, @Email etc. na DTO. W kontrolerze dodaj @Valid przed @RequestBody. Błędy walidacji obsłuż przez @ControllerAdvice lub BindingResult. Spring automatycznie zwróci 400 Bad Request.

Odpowiedź w 2 minuty:

Bean Validation pozwala deklaratywnie definiować reguły walidacji bezpośrednio na polach DTO za pomocą adnotacji.

// DTO z walidacją
public class CreateUserRequest {

    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 50)
    private String name;

    @NotNull
    @Email(message = "Invalid email format")
    private String email;

    @NotNull
    @Min(18)
    @Max(120)
    private Integer age;

    @Pattern(regexp = "^\\+?[0-9]{9,15}$")
    private String phone;
}

// Controller z @Valid
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
    // Jeśli walidacja nie przeszła → 400 Bad Request
    return ResponseEntity.ok(userService.create(request));
}

// Globalna obsługa błędów walidacji
@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidation(
            MethodArgumentNotValidException ex) {

        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                FieldError::getDefaultMessage
            ));

        return ResponseEntity.badRequest().body(errors);
    }
}
Adnotacja Waliduje
@NotNull Nie null
@NotBlank Nie null, nie pusty, nie whitespace
@Size(min, max) Długość string/collection
@Min, @Max Wartość liczbowa
@Email Format email
@Pattern Regex
@Valid Walidacja zagnieżdżona

Spring Data JPA

Czym jest Spring Data i jak działa JpaRepository?

Odpowiedź w 30 sekund: Spring Data to projekt upraszczający dostęp do danych. JpaRepository to interfejs, który automatycznie generuje implementację CRUD. Wystarczy zdefiniować interfejs rozszerzający JpaRepository<Entity, IdType> - Spring generuje implementację z metodami save(), findById(), findAll(), delete(), etc.

Odpowiedź w 2 minuty:

Spring Data JPA automatycznie generuje implementację repozytorium na podstawie interfejsu i nazw metod, eliminując potrzebę pisania boilerplate kodu DAO.

// 1. Encja JPA
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
}

// 2. Repository - tylko interfejs!
public interface UserRepository extends JpaRepository<User, Long> {
    // Automatycznie dostępne:
    // - save(User), saveAll(List<User>)
    // - findById(Long), findAll()
    // - deleteById(Long), delete(User)
    // - count(), existsById(Long)

    // Query Methods - Spring generuje SQL
    List<User> findByName(String name);
    Optional<User> findByEmail(String email);
    List<User> findByNameContainingIgnoreCase(String name);
    List<User> findByAgeGreaterThanOrderByNameAsc(int age);

    // Custom JPQL
    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
    List<User> findByEmailDomain(@Param("domain") String domain);

    // Native SQL
    @Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
    List<User> findRecentUsers(@Param("date") LocalDate date);
}

// 3. Użycie w Service
@Service
public class UserService {
    private final UserRepository userRepository;

    public User createUser(CreateUserRequest request) {
        User user = new User();
        user.setName(request.getName());
        user.setEmail(request.getEmail());
        return userRepository.save(user);  // INSERT lub UPDATE
    }
}
Metoda Query Method Generowany SQL
findByName WHERE name = ?
findByNameAndAge WHERE name = ? AND age = ?
findByNameContaining WHERE name LIKE %?%
findByAgeGreaterThan WHERE age > ?
findByNameOrderByAgeDesc WHERE name = ? ORDER BY age DESC

Jak działa Lazy vs Eager loading w JPA?

Odpowiedź w 30 sekund: Lazy (domyślne dla kolekcji) - relacje ładowane dopiero przy dostępie. Eager (domyślne dla @ManyToOne) - ładowane od razu z główną encją. Lazy oszczędza pamięć ale może powodować N+1 queries. Eager może ładować za dużo danych. Rozwiązanie: @EntityGraph lub JOIN FETCH.

Odpowiedź w 2 minuty:

Wybór strategii ładowania relacji JPA wpływa na wydajność - lazy loading oszczędza pamięć, ale może prowadzić do problemu N+1 queries.

@Entity
public class Order {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)  // domyślnie EAGER
    private Customer customer;

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)  // domyślnie LAZY
    private List<OrderItem> items;
}

// Problem N+1
List<Order> orders = orderRepository.findAll();  // 1 query
for (Order order : orders) {
    order.getCustomer().getName();  // N queries! (lazy load)
}

// Rozwiązanie 1: JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.customer JOIN FETCH o.items")
List<Order> findAllWithDetails();

// Rozwiązanie 2: @EntityGraph
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findAll();

// Rozwiązanie 3: DTO Projection
@Query("SELECT new com.example.OrderDto(o.id, c.name, o.total) " +
       "FROM Order o JOIN o.customer c")
List<OrderDto> findOrderSummaries();
LAZY:
findAll() → SELECT * FROM orders  (1 query)
getCustomer() → SELECT * FROM customers WHERE id = ?  (N queries)
Problem: N+1 queries!

EAGER / JOIN FETCH:
findAll() → SELECT o.*, c.* FROM orders o
            JOIN customers c ON o.customer_id = c.id  (1 query)
Strategia Zalety Wady
Lazy Oszczędza pamięć N+1 problem
Eager Brak N+1 Może ładować za dużo
JOIN FETCH Kontrola Więcej kodu
@EntityGraph Elastyczność Złożoność

Transakcje i AOP

Jak działa adnotacja @Transactional?

Odpowiedź w 30 sekund: @Transactional opakowuje metodę w transakcję bazodanową. Spring tworzy proxy, które: 1) Rozpoczyna transakcję przed metodą, 2) Commituje po pomyślnym zakończeniu, 3) Rollback przy RuntimeException. Konfiguruj propagację, izolację, timeout, readOnly.

Odpowiedź w 2 minuty:

Spring tworzy proxy wokół metody, które automatycznie zarządza transakcją bazodanową - rozpoczyna, commituje lub wykonuje rollback.

@Service
public class OrderService {

    @Transactional  // proxy rozpocznie/commituje transakcję
    public Order createOrder(CreateOrderRequest request) {
        Order order = new Order();
        orderRepository.save(order);

        for (ItemRequest item : request.getItems()) {
            OrderItem orderItem = new OrderItem(order, item);
            orderItemRepository.save(orderItem);

            inventoryService.decreaseStock(item.getProductId(), item.getQuantity());
        }

        return order;  // commit wszystkich zmian
    }
    // Jeśli wyjątek → rollback całości

    @Transactional(readOnly = true)  // optymalizacja dla odczytu
    public List<Order> findAll() {
        return orderRepository.findAll();
    }

    @Transactional(
        propagation = Propagation.REQUIRED,   // domyślne
        isolation = Isolation.READ_COMMITTED, // domyślne
        timeout = 30,                          // sekundy
        rollbackFor = BusinessException.class  // rollback też dla checked
    )
    public void complexOperation() { }
}
Proxy Transactional:
┌─────────────────────────────────────────────────────────┐
│ Wywołanie: orderService.createOrder(request)           │
│     │                                                   │
│     ▼                                                   │
│ ┌─────────────────────────────────────────────────┐    │
│ │  PROXY (TransactionInterceptor)                 │    │
│ │  1. Begin Transaction                           │    │
│ │     │                                           │    │
│ │     ▼                                           │    │
│ │  2. Wywołaj prawdziwą metodę                    │    │
│ │     │                                           │    │
│ │     ▼                                           │    │
│ │  3. Commit (sukces) / Rollback (wyjątek)        │    │
│ └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

Pułapka: Self-invocation nie działa z proxy!

public void methodA() {
    methodB();  // NIE przejdzie przez proxy, @Transactional ignorowane!
}

@Transactional
public void methodB() { }

Jakie są rodzaje propagacji transakcji?

Odpowiedź w 30 sekund: REQUIRED (domyślne) - użyj istniejącej lub utwórz nową. REQUIRES_NEW - zawsze nowa (zawieszenie obecnej). SUPPORTS - użyj istniejącej lub bez transakcji. NOT_SUPPORTED - wykonaj bez transakcji. MANDATORY - wymaga istniejącej. NEVER - rzuć wyjątek jeśli istnieje.

Odpowiedź w 2 minuty:

Propagacja określa jak transakcje zachowują się gdy metody wywołują się nawzajem - czy używają tej samej transakcji czy tworzą nową.

@Service
public class AuditService {

    // REQUIRES_NEW - niezależna transakcja
    // Nawet jeśli główna transakcja fail, log zostanie zapisany
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(String action) {
        auditRepository.save(new AuditLog(action));
    }
}

@Service
public class OrderService {

    @Transactional
    public void processOrder(Order order) {
        orderRepository.save(order);

        // Nowa niezależna transakcja
        auditService.logAction("Order created: " + order.getId());

        if (someCondition) {
            throw new RuntimeException("Fail!");
            // order - rollback
            // audit log - pozostaje (osobna transakcja)
        }
    }
}
Propagacja Istniejąca TX Brak TX
REQUIRED Użyj Utwórz nową
REQUIRES_NEW Zawieś, utwórz nową Utwórz nową
SUPPORTS Użyj Bez TX
NOT_SUPPORTED Zawieś Bez TX
MANDATORY Użyj Exception
NEVER Exception Bez TX
NESTED Savepoint Utwórz nową

Spring Security

Czym jest Spring Security i jak skonfigurować podstawową autoryzację?

Odpowiedź w 30 sekund: Spring Security to framework do autentykacji i autoryzacji w aplikacjach Spring. Podstawowa konfiguracja: SecurityFilterChain bean definiuje które endpointy wymagają autoryzacji, jak logować użytkowników (form, HTTP Basic, OAuth2) i jakie role mają dostęp do zasobów.

Odpowiedź w 2 minuty:

Konfiguracja Spring Security opiera się na zdefiniowaniu SecurityFilterChain, który określa reguły dostępu i metody autentykacji.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**", "/api/auth/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/**").authenticated()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
            )
            .httpBasic(Customizer.withDefaults())  // dla REST API
            .csrf(csrf -> csrf.disable());         // dla REST API

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();

        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin"))
            .roles("USER", "ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
Security Filter Chain:
Request → [Filters] → Controller
              │
    ┌─────────┴─────────┐
    │ SecurityContext   │
    │ Authentication    │
    │ UserDetails       │
    └───────────────────┘

Jak skonfigurować JWT w Spring Security?

Odpowiedź w 30 sekund:

  1. Wyłącz sesje (STATELESS), 2) Dodaj filtr JWT przed UsernamePasswordAuthenticationFilter, 3) Filtr parsuje token z nagłówka Authorization, 4) Waliduje podpis i expiration, 5) Ustawia Authentication w SecurityContext. Endpoint login generuje token.

Odpowiedź w 2 minuty:

JWT wymaga sesji STATELESS i własnego filtra, który parsuje token z nagłówka Authorization i ustawia autentykację w kontekście.

@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, JwtFilter jwtFilter) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

@Component
public class JwtFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain) throws Exception {

        String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);

            if (jwtUtil.validateToken(token)) {
                String username = jwtUtil.getUsername(token);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());

                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }

        chain.doFilter(request, response);
    }
}
JWT Flow:
1. POST /api/auth/login {username, password}
   → Response: { "token": "eyJhbG..." }

2. GET /api/protected
   Authorization: Bearer eyJhbG...
   → JwtFilter waliduje token
   → SecurityContext.setAuthentication()
   → Controller obsługuje request

Spring Cloud i Microservices

Czym jest Spring Cloud i kiedy go używać?

Odpowiedź w 30 sekund: Spring Cloud to zestaw narzędzi dla architektury microservices. Oferuje: Service Discovery (Eureka), Config Server (centralna konfiguracja), API Gateway, Circuit Breaker (Resilience4j), Load Balancing. Używaj gdy masz wiele serwisów, które muszą się komunikować, skalować i być odporne na awarie.

Odpowiedź w 2 minuty:

Spring Cloud zapewnia kompletny zestaw narzędzi do budowy architektury mikrousług, jak pokazano na poniższym diagramie.

Architektura Microservices ze Spring Cloud:
┌─────────────────────────────────────────────────────────────┐
│                    API Gateway (Spring Cloud Gateway)       │
└─────────────────────────────────────────────────────────────┘
                              │
          ┌───────────────────┼───────────────────┐
          ▼                   ▼                   ▼
    ┌──────────┐        ┌──────────┐        ┌──────────┐
    │ Order    │        │ Payment  │        │ Inventory│
    │ Service  │───────►│ Service  │◄───────│ Service  │
    └──────────┘        └──────────┘        └──────────┘
          │                   │                   │
          └───────────────────┼───────────────────┘
                              ▼
                    ┌─────────────────┐
                    │ Eureka Server   │  Service Discovery
                    │ (Registry)      │
                    └─────────────────┘
                              │
                    ┌─────────────────┐
                    │ Config Server   │  Centralna konfiguracja
                    └─────────────────┘
Komponent Funkcja Biblioteka
Service Discovery Rejestracja serwisów Eureka, Consul
Config Server Centralna konfiguracja Spring Cloud Config
API Gateway Routing, auth Spring Cloud Gateway
Circuit Breaker Odporność na awarie Resilience4j
Load Balancer Balansowanie ruchu Spring Cloud LoadBalancer
Distributed Tracing Śledzenie requestów Sleuth + Zipkin
// Service Discovery - klient
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication { }

// Wywołanie innego serwisu przez nazwę
@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/api/payments")
    PaymentResponse processPayment(@RequestBody PaymentRequest request);
}

Testowanie

Jak testować aplikacje Spring Boot?

Odpowiedź w 30 sekund: Spring Boot Test oferuje: @SpringBootTest - pełny kontekst dla testów integracyjnych, @WebMvcTest - tylko warstwa web (MockMvc), @DataJpaTest - tylko JPA (embedded DB), @MockBean - mockowanie beanów. Używaj odpowiedniego slice testu dla szybszego wykonania.

Odpowiedź w 2 minuty:

Spring Boot oferuje różne "slices" testowe - od pełnego kontekstu aplikacji po testy tylko wybranych warstw dla szybszego wykonania.

// Test integracyjny - pełny kontekst
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean  // mockuje bean w kontekście
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        when(userService.findById(1L))
            .thenReturn(Optional.of(new User(1L, "John")));

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}

// Test warstwy web - tylko kontrolery
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldCreateUser() throws Exception {
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"John\",\"email\":\"john@example.com\"}"))
            .andExpect(status().isCreated());
    }
}

// Test warstwy danych
@DataJpaTest  // tylko JPA + embedded H2
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldFindByEmail() {
        userRepository.save(new User("John", "john@example.com"));

        Optional<User> found = userRepository.findByEmail("john@example.com");

        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("John");
    }
}
Adnotacja Kontekst Użycie
@SpringBootTest Pełny Integracyjne
@WebMvcTest MVC tylko Kontrolery
@DataJpaTest JPA tylko Repozytoria
@MockBean Mock w kontekście Izolacja
@Testcontainers Docker Prawdziwa DB

Zobacz też


Chcesz przećwiczyć te pytania? Sprawdź nasze fiszki Spring z 56 pytaniami rekrutacyjnymi - idealne do nauki przed rozmową kwalifikacyjną.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.