Spring Boot - Pytania Rekrutacyjne dla Java Backend Developera [2026]
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
- Spring Beans i Konfiguracja
- Spring Boot
- Spring MVC i REST API
- Spring Data JPA
- Transakcje i AOP
- Spring Security
- Spring Cloud i Microservices
- Testowanie
- Zobacz też
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:
- Żą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 |
| 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:
- 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ż
- Kompletny Przewodnik - Rozmowa Java Backend Developer - pełny przewodnik przygotowania do rozmowy Java
- Java Pytania Rekrutacyjne - fundamenty języka Java
- SQL Pytania Rekrutacyjne - bazy danych relacyjne
- Wzorce i Architektura Backend - wzorce projektowe i architektura
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.
