Skip to content

Ôn tập phỏng vấn Java Spring Boot: những điểm phải nắm chắc

20 min read

Bài viết này là một checklist ôn tập có chiều sâu: mỗi phần gồm những khái niệm cốt lõi, các “bẫy” interviewer hay hỏi, và câu hỏi mẫu kèm ý chính để trả lời. Đọc phần TL;DR để biết mình đang hổng ở đâu, sau đó đi sâu vào từng phần.


TL;DR — Bản đồ kiến thức#

Một buổi phỏng vấn Java Spring Boot backend thường xoay quanh 8 mảng sau. Mức độ ưu tiên dựa trên tần suất xuất hiện thực tế:

# Mảng kiến thức Ưu tiên Phải trả lời được
1 Core Java ⭐⭐⭐ OOP, equals/hashCode, Collections (đặc biệt HashMap), Stream API, Exception
2 Concurrency & JVM ⭐⭐ Thread safety, synchronized vs Lock, volatile, CompletableFuture, heap/stack, GC
3 Spring Core ⭐⭐⭐ IoC, DI, Bean scope, Bean lifecycle, AOP và proxy
4 Spring Boot ⭐⭐⭐ Auto-configuration hoạt động thế nào, starter, profile, externalized config
5 Spring MVC / REST ⭐⭐⭐ Luồng request qua DispatcherServlet, validation, exception handling, filter vs interceptor
6 Spring Data JPA & Transaction ⭐⭐⭐ Persistence context, lazy loading, N+1, @Transactional (propagation, rollback, self-invocation)
7 Spring Security ⭐⭐ Filter chain, authentication vs authorization, JWT, SecurityFilterChain
8 Testing & Microservices ⭐⭐ Slice test, Mockito, Testcontainers, caching, messaging, resilience

Nếu chỉ có một buổi tối để ôn, hãy tập trung vào 5 câu hỏi “gần như chắc chắn sẽ gặp”:

  1. HashMap hoạt động bên trong như thế nào?
  2. Dependency Injection là gì, tại sao nên dùng constructor injection?
  3. Spring Boot auto-configuration hoạt động ra sao?
  4. @Transactional hoạt động thế nào, khi nào nó không có tác dụng?
  5. N+1 query problem là gì và cách xử lý?
flowchart LR
    A[Core Java] --> B[Spring Core]
    B --> C[Spring Boot]
    C --> D[Web / REST]
    C --> E[Data JPA + Transaction]
    C --> F[Security]
    D & E & F --> G[Testing]
    G --> H[Microservices / System Design]

1. Core Java#

1.1. OOP và các nguyên tắc thiết kế#

Nắm chắc 4 tính chất: Encapsulation, Inheritance, Polymorphism, Abstraction. Quan trọng hơn là giải thích được bằng ví dụ thực tế trong code của bạn, không chỉ đọc định nghĩa.

Các điểm hay bị hỏi:

  • Abstract class vs Interface: interface từ Java 8 có default và static method, từ Java 9 có private method. Abstract class có state (field instance) và constructor; interface thì không. Một class chỉ extends một class nhưng implements nhiều interface.
  • Overloading vs Overriding: overloading quyết định lúc compile-time (static binding), overriding quyết định lúc runtime (dynamic dispatch).
  • Composition over Inheritance: tại sao nên ưu tiên composition — giảm coupling, dễ test, tránh fragile base class.
  • SOLID: nên có ví dụ cho từng nguyên tắc. Dependency Inversion Principle là cầu nối tự nhiên sang Spring DI.

1.2. equals() và hashCode()#

Contract: nếu a.equals(b) là true thì a.hashCode() == b.hashCode() bắt buộc phải đúng. Chiều ngược lại không bắt buộc (hash collision là bình thường).

Bẫy kinh điển: override equals mà quên hashCode, sau đó dùng object làm key trong HashMap hoặc phần tử trong HashSet — sẽ không tìm lại được object.

Với JPA entity, cần cẩn thận: đừng dựa hashCode vào ID tự sinh vì ID là null trước khi persist.

1.3. String#

  • String là immutable: an toàn khi dùng làm key, thread-safe, cho phép cache hash code, và cho phép String pool.
  • == so sánh reference, equals() so sánh nội dung.
  • Nối chuỗi trong vòng lặp → dùng StringBuilder (không thread-safe, nhanh) thay vì StringBuffer (synchronized, chậm hơn).

1.4. Collections Framework — phần quan trọng nhất của Core Java#

flowchart TB
    C[Collection] --> L[List]
    C --> S[Set]
    C --> Q[Queue]
    L --> AL[ArrayList]
    L --> LL[LinkedList]
    S --> HS[HashSet]
    S --> LHS[LinkedHashSet]
    S --> TS[TreeSet]
    Q --> PQ[PriorityQueue]
    Q --> AD[ArrayDeque]
    M[Map] --> HM[HashMap]
    M --> LHM[LinkedHashMap]
    M --> TM[TreeMap]
    M --> CHM[ConcurrentHashMap]

HashMap internals — phải giải thích được trôi chảy:

  1. Bên trong là một mảng các bucket (Node<K,V>[] table), capacity mặc định 16.
  2. put(key, value): tính hash(key) (có XOR bit cao để phân bố đều), xác định index bằng (n - 1) & hash.
  3. Nếu bucket đã có phần tử (collision), phần tử mới được thêm vào linked list của bucket đó.
  4. Từ Java 8, khi một bucket có hơn 8 phần tử và table size ≥ 64, linked list chuyển thành red-black tree → lookup từ O(n) xuống O(log n).
  5. Khi số phần tử vượt capacity × load factor (mặc định 0.75), table resize gấp đôi và rehash.
  6. HashMap cho phép một key null, không thread-safe.

So sánh cần thuộc:

Đặc điểm Khi nào dùng
ArrayList Mảng động, get O(1), insert giữa O(n) Mặc định cho List
LinkedList Doubly linked list, get O(n) Hiếm khi thực sự tốt hơn ArrayList
HashMap Không thứ tự Mặc định cho Map
LinkedHashMap Giữ thứ tự insert (hoặc access) Cần thứ tự, làm LRU cache
TreeMap Sorted theo key, O(log n) Cần range query, sắp xếp
ConcurrentHashMap Thread-safe, lock theo bucket/CAS, không cho key/value null Map dùng chung giữa nhiều thread

Thêm: fail-fast iterator — sửa collection khi đang iterate (không qua iterator) sẽ ném ConcurrentModificationException.

1.5. Java 8+ features#

  • Lambda & Functional Interface: Function, Predicate, Consumer, Supplier, BiFunction.
  • Stream API: phân biệt intermediate operation (lazy: map, filter, sorted) và terminal operation (collect, forEach, reduce). Stream không chạy nếu không có terminal operation. map vs flatMap. Cẩn trọng với parallelStream() — dùng common ForkJoinPool, không phải lúc nào cũng nhanh hơn.
  • Optional: dùng làm return type, không dùng làm field hay parameter. Tránh optional.get() không kiểm tra; ưu tiên orElse, orElseGet, orElseThrow, map. Hiểu khác biệt orElse (luôn evaluate argument) và orElseGet (lazy).
  • Tính năng mới hay được hỏi (Java 17/21): record, sealed class, pattern matching cho instanceof và switch, text block, virtual threads (Java 21).

1.6. Exception#

flowchart TB
    T[Throwable] --> E[Exception]
    T --> ER["Error (OutOfMemoryError, StackOverflowError)"]
    E --> CE["Checked Exception (IOException, SQLException)"]
    E --> RE["RuntimeException - Unchecked (NullPointerException, IllegalArgumentException)"]
  • Checked phải được khai báo throws hoặc catch; unchecked thì không.
  • try-with-resources cho các class implement AutoCloseable.
  • finally luôn chạy (trừ khi System.exit() hoặc JVM crash).
  • Liên hệ với Spring: @Transactional mặc định chỉ rollback với unchecked exception và Error — xem phần 6.

2. Concurrency và JVM#

2.1. Concurrency#

Các khái niệm phải nắm:

  • Thread vs Process, các trạng thái của thread (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED).
  • synchronized: lock trên object (hoặc class với static method), reentrant. Đảm bảo cả atomicity và visibility.
  • volatile: đảm bảo visibility và ngăn reorder, nhưng không đảm bảo atomicity. count++ trên biến volatile vẫn là race condition → dùng AtomicInteger.
  • ReentrantLock so với synchronized: có tryLock() với timeout, fairness, nhiều Condition, có thể interrupt khi đang chờ.
  • ExecutorService / thread pool: tại sao không nên new Thread() tùy tiện. Các tham số ThreadPoolExecutor: core size, max size, queue, rejection policy.
  • CompletableFuture: supplyAsync, thenApply, thenCompose, thenCombine, allOf, xử lý lỗi với exceptionally/handle.
  • Deadlock: 4 điều kiện (mutual exclusion, hold and wait, no preemption, circular wait) và cách phòng tránh (lock ordering, timeout).
  • Virtual threads (Java 21): thread nhẹ do JVM quản lý, phù hợp workload I/O-bound blocking. Spring Boot 3.2+ bật bằng spring.threads.virtual.enabled=true. Lưu ý vấn đề pinning khi block trong synchronized (đã được cải thiện đáng kể từ Java 24).

2.2. JVM#

flowchart LR
    subgraph JVM Memory
        H["Heap (shared) - Young Gen: Eden, Survivor - Old Gen"]
        MS["Metaspace (class metadata)"]
        ST["Stack (mỗi thread một stack) - local variables, method frames"]
    end
  • Heap chứa object, dùng chung giữa các thread; Stack chứa local variable và reference, riêng cho từng thread.
  • Garbage Collection: generational hypothesis, Minor GC vs Major/Full GC. Biết tên các GC: G1 (default từ Java 9), ZGC, Shenandoah (low latency).
  • OutOfMemoryError vs StackOverflowError (thường do đệ quy không điểm dừng).
  • Memory leak trong Java vẫn xảy ra: static collection giữ reference, listener không unregister, ThreadLocal không remove() trong thread pool.

3. Spring Core#

3.1. IoC và Dependency Injection#

Inversion of Control: thay vì object tự tạo dependency, container (ApplicationContext) tạo và “tiêm” vào. DI là cách hiện thực IoC.

Ba cách inject:

// 1. Constructor injection — KHUYẾN NGHỊ
@Service
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) { // không cần @Autowired nếu chỉ có 1 constructor
        this.paymentService = paymentService;
    }
}

// 2. Setter injection — dùng cho optional dependency
// 3. Field injection (@Autowired trên field) — nên tránh

Tại sao constructor injection tốt hơn: field có thể final (immutable), dependency bắt buộc rõ ràng, dễ unit test (không cần reflection), phát hiện circular dependency sớm, và constructor quá dài là tín hiệu class đang vi phạm Single Responsibility.

3.2. Bean#

  • Khai báo bean: @Component (và các stereotype @Service, @Repository, @Controller) cho class của mình; @Bean trong class @Configuration cho class của thư viện bên ngoài hoặc khi cần logic khởi tạo.
  • @Repository còn có tác dụng dịch exception của persistence thành DataAccessException của Spring.
  • Nhiều bean cùng type: dùng @Primary hoặc @Qualifier.
  • @Configuration vs @Component chứa @Bean: @Configuration (với proxyBeanMethods = true mặc định) được CGLIB proxy, nên gọi một method @Bean từ method khác vẫn trả về cùng singleton.

Bean scope:

Scope Ý nghĩa
singleton (mặc định) Một instance cho mỗi ApplicationContext
prototype Mỗi lần request bean tạo một instance mới
request Một instance cho mỗi HTTP request
session Một instance cho mỗi HTTP session
application Một instance cho mỗi ServletContext

Bẫy hay hỏi: inject prototype bean vào singleton bean → prototype chỉ được tạo một lần lúc singleton khởi tạo. Cách xử lý: ObjectProvider<T>, @Lookup, hoặc scoped proxy.

Bẫy thứ hai: singleton bean không tự động thread-safe. Đừng lưu state có thể thay đổi (mutable state) theo request trong field của singleton.

3.3. Bean lifecycle#

flowchart TB
    A[Instantiate - gọi constructor] --> B[Populate properties - inject dependency]
    B --> C["Aware callbacks (BeanNameAware, ApplicationContextAware)"]
    C --> D["BeanPostProcessor.postProcessBeforeInitialization"]
    D --> E["@PostConstruct → afterPropertiesSet() → init-method"]
    E --> F["BeanPostProcessor.postProcessAfterInitialization (tạo AOP proxy ở đây)"]
    F --> G[Bean sẵn sàng sử dụng]
    G --> H["@PreDestroy → destroy() → destroy-method"]

Ý quan trọng: AOP proxy được tạo ở bước postProcessAfterInitialization. Đây là lý do @Transactional, @Async, @Cacheable hoạt động được — và cũng là gốc rễ của bẫy self-invocation.

3.4. Circular dependency#

A cần B, B cần A. Với constructor injection, Spring không thể giải quyết và báo lỗi khi startup. Từ Spring Boot 2.6, circular reference bị cấm mặc định kể cả với field/setter injection (spring.main.allow-circular-references=false). Cách xử lý đúng là thiết kế lại (tách phần dùng chung ra class thứ ba, dùng event), @Lazy chỉ là giải pháp tạm.

3.5. AOP#

  • Khái niệm: Aspect, Advice (@Before, @After, @AfterReturning, @AfterThrowing, @Around), Pointcut, Join Point.
  • Spring AOP dựa trên proxy (runtime), chỉ áp dụng cho method call từ bên ngoài bean, và chỉ với method public (với JDK proxy) hoặc non-private non-final (với CGLIB).
  • JDK Dynamic Proxy (dựa trên interface) vs CGLIB (subclass). Spring Boot mặc định dùng CGLIB (spring.aop.proxy-target-class=true).
  • Class hoặc method final không proxy được bằng CGLIB.

4. Spring Boot#

4.1. Spring Boot giải quyết vấn đề gì?#

So với Spring Framework thuần: auto-configuration, starter dependency, embedded server (Tomcat/Jetty/Undertow), externalized configuration, production-ready features (Actuator). Phương châm: convention over configuration, nhưng luôn có thể override.

4.2. Auto-configuration — câu hỏi gần như chắc chắn gặp#

@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan.

flowchart TB
    A["@SpringBootApplication"] --> B["@EnableAutoConfiguration"]
    B --> C["AutoConfigurationImportSelector"]
    C --> D["Đọc danh sách class từ META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"]
    D --> E{"Đánh giá các điều kiện @Conditional*"}
    E -- "Thỏa mãn" --> F[Đăng ký bean]
    E -- "Không thỏa" --> G[Bỏ qua]

Các annotation điều kiện quan trọng:

  • @ConditionalOnClass / @ConditionalOnMissingClass: có class trên classpath hay không. Ví dụ có HikariDataSource trên classpath → cấu hình Hikari.
  • @ConditionalOnBean / @ConditionalOnMissingBean: chìa khóa của việc override. Auto-config chỉ tạo DataSource nếu bạn chưa định nghĩa.
  • @ConditionalOnProperty: bật/tắt theo property.
  • @ConditionalOnWebApplication.

Lưu ý: file spring.factories là cơ chế cũ cho auto-configuration (Spring Boot 2.x); từ Spring Boot 3 dùng file AutoConfiguration.imports như trên.

Mẹo debug hay được hỏi: chạy với --debug hoặc debug=true để xem Condition Evaluation Report — auto-config nào được áp dụng, cái nào bị bỏ qua và vì sao. Có thể loại bỏ auto-config bằng @SpringBootApplication(exclude = ...).

4.3. Starter#

Starter là một dependency “gom” các thư viện cần thiết cho một tính năng, ví dụ spring-boot-starter-web kéo theo Spring MVC, Tomcat embedded, Jackson. Nên biết cách tự viết một custom starter (autoconfigure module + starter module) nếu phỏng vấn level senior.

4.4. Configuration#

  • application.properties / application.yml, profile (application-dev.yml, spring.profiles.active=dev), @Profile.
  • Thứ tự ưu tiên (từ cao xuống): command line arguments → environment variables → file config theo profile → application.yml mặc định. Không cần thuộc toàn bộ danh sách, nhưng phải biết command line và env var ghi đè file.
  • @Value vs @ConfigurationProperties: @ConfigurationProperties type-safe, gom nhóm, hỗ trợ validation (@Validated), relaxed binding — nên ưu tiên cho cấu hình có cấu trúc.
  • Không commit secret vào file config — dùng env var, Vault, hoặc secret manager của cloud.

4.5. Actuator#

Endpoint như /actuator/health, /actuator/metrics, /actuator/info, /actuator/prometheus. Biết cách expose có chọn lọc và bảo vệ bằng Security. Liên quan: Micrometer cho metrics và tracing.

4.6. Những thay đổi lớn cần biết theo version#

  • Spring Boot 3.x: baseline Java 17, chuyển từ javax.* sang jakarta.* (Jakarta EE), hỗ trợ GraalVM native image, observability qua Micrometer Observation, virtual threads (3.2+), RestClient (3.2+), @MockitoBean thay thế @MockBean (3.4+).
  • Spring Boot 4.x / Spring Framework 7 (ra mắt cuối 2025): Jakarta EE 11, null-safety với JSpecify, hỗ trợ API versioning có sẵn, HTTP service client. Nếu công ty đang dùng version mới, nên đọc release notes chính thức trước buổi phỏng vấn.

5. Spring MVC / REST API#

5.1. Luồng xử lý request#

sequenceDiagram
    participant C as Client
    participant F as Filter Chain
    participant DS as DispatcherServlet
    participant HM as HandlerMapping
    participant I as HandlerInterceptor
    participant CT as Controller
    participant MC as HttpMessageConverter

    C->>F: HTTP Request
    F->>DS: request đã qua filter (Security, CORS...)
    DS->>HM: tìm handler phù hợp
    HM-->>DS: HandlerExecutionChain
    DS->>I: preHandle()
    DS->>CT: gọi method (qua HandlerAdapter, bind argument)
    CT-->>DS: return object
    DS->>MC: serialize object sang JSON (Jackson)
    DS->>I: postHandle() rồi afterCompletion()
    DS-->>C: HTTP Response

5.2. Filter vs Interceptor#

Filter HandlerInterceptor
Thuộc về Servlet API Spring MVC
Vị trí Trước DispatcherServlet Sau DispatcherServlet, quanh controller
Biết handler nào sẽ xử lý? Không Có
Use case Security, logging thô, CORS, encoding, request ID Kiểm tra quyền theo handler, audit, đo thời gian xử lý controller

5.3. Annotation và thực hành cần nắm#

  • @RestController = @Controller + @ResponseBody.
  • @PathVariable, @RequestParam, @RequestBody, @RequestHeader.
  • ResponseEntity để kiểm soát status code và header.
  • Validation: @Valid / @Validated + Bean Validation (@NotNull, @NotBlank, @Size, @Email…). Lỗi validation của @RequestBody ném MethodArgumentNotValidException.
  • Global exception handling: @RestControllerAdvice + @ExceptionHandler. Biết chuẩn ProblemDetail (RFC 9457, trước đó là RFC 7807) được Spring 6 hỗ trợ sẵn.
  • Không trả entity trực tiếp ra API — dùng DTO để tránh lộ field nội bộ, vòng lặp serialize và lỗi lazy loading.
  • REST design: HTTP method đúng ngữ nghĩa, idempotency (GET, PUT, DELETE idempotent; POST thì không), status code (200, 201, 204, 400, 401 vs 403, 404, 409, 500), pagination, versioning.
  • HTTP client: RestTemplate (maintenance mode), RestClient (sync, hiện đại), WebClient (reactive), khai báo interface với @HttpExchange.

5.4. Spring MVC vs WebFlux#

MVC: mô hình thread-per-request, blocking. WebFlux: non-blocking, event loop, dựa trên Project Reactor (Mono, Flux). WebFlux chỉ có lợi khi toàn bộ stack non-blocking. Với Java 21, virtual threads giúp MVC blocking xử lý concurrency cao mà vẫn giữ code đơn giản — đây là câu hỏi thảo luận hay gặp gần đây.


6. Spring Data JPA và Transaction#

Đây là phần dễ lộ kinh nghiệm thực tế nhất, hãy chuẩn bị ví dụ từ dự án của bạn.

6.1. JPA cơ bản#

  • JPA là specification, Hibernate là implementation, Spring Data JPA là lớp trừu tượng phía trên (repository interface, derived query).
  • Entity lifecycle: Transient → Managed (persist) → Detached (sau khi persistence context đóng) → Removed.
  • Persistence Context (first-level cache): trong một transaction, cùng một ID chỉ có một instance entity. Dirty checking: entity managed bị thay đổi sẽ tự động được UPDATE khi flush — không cần gọi save().
  • save() trên entity mới gọi persist, trên entity có ID gọi merge.
  • getReferenceById() trả về proxy (lazy), findById() query ngay.

6.2. Fetch type và N+1 problem#

Default: @ManyToOne, @OneToOne là EAGER; @OneToMany, @ManyToMany là LAZY. Khuyến nghị thực tế: đặt tất cả thành LAZY và fetch có chủ đích.

N+1 problem: 1 query lấy danh sách N entity, sau đó mỗi entity phát sinh thêm 1 query để lấy association khi truy cập.

sequenceDiagram
    participant App
    participant DB
    App->>DB: SELECT * FROM orders (1 query, trả về N order)
    loop Với mỗi order
        App->>DB: SELECT * FROM customers WHERE id = ? (N query)
    end
    Note over App,DB: Tổng cộng N + 1 query

Cách xử lý:

// 1. JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();

// 2. @EntityGraph
@EntityGraph(attributePaths = {"customer"})
List<Order> findAll();

// 3. Batch fetching: spring.jpa.properties.hibernate.default_batch_fetch_size=100
// 4. DTO projection — chỉ lấy đúng các cột cần

Lưu ý: JOIN FETCH collection kết hợp pagination sẽ khiến Hibernate phân trang trong bộ nhớ (cảnh báo HHH90003004). Cách phát hiện N+1: bật SQL log, dùng Hibernate statistics, hoặc thư viện đếm số query trong test.

LazyInitializationException: truy cập lazy association sau khi session đã đóng. Cách xử lý đúng là fetch dữ liệu cần trong transaction hoặc dùng DTO. Ngoài ra nên biết Open Session In View (spring.jpa.open-in-view, mặc định true và Spring Boot log warning) — tiện nhưng giữ connection DB suốt request, thường nên tắt.

6.3. @Transactional — trọng tâm#

Cách hoạt động: Spring tạo proxy quanh bean. Khi method được gọi qua proxy: mở transaction → gọi method thật → commit nếu thành công, rollback nếu có exception phù hợp.

sequenceDiagram
    participant Caller
    participant Proxy as Proxy (TransactionInterceptor)
    participant Target as OrderService thật
    participant TM as TransactionManager

    Caller->>Proxy: placeOrder()
    Proxy->>TM: begin transaction
    Proxy->>Target: placeOrder()
    Target-->>Proxy: return / throw
    alt thành công
        Proxy->>TM: commit
    else RuntimeException
        Proxy->>TM: rollback
    end
    Proxy-->>Caller: kết quả

Khi nào @Transactional KHÔNG hoạt động — câu hỏi “bẫy” phổ biến nhất:

  1. Self-invocation: method A trong cùng class gọi method B có @Transactional → gọi qua this, không qua proxy, nên annotation trên B bị bỏ qua.
  2. Method private (và với Spring 6 trở về trước, cả method không public trong nhiều trường hợp) hoặc final.
  3. Exception là checked exception → mặc định không rollback. Cần @Transactional(rollbackFor = Exception.class).
  4. Exception bị catch và nuốt bên trong method.
  5. Class không phải Spring bean (tự new).
  6. Chạy trong thread khác (ví dụ @Async, CompletableFuture) — transaction gắn với thread qua ThreadLocal.
@Service
public class OrderService {

    public void process() {
        this.save();   // ❌ self-invocation: @Transactional bị bỏ qua
    }

    @Transactional
    public void save() { ... }
}

Cách khắc phục self-invocation: tách method sang bean khác (khuyến nghị), inject chính mình qua @Lazy, hoặc dùng TransactionTemplate theo kiểu programmatic.

Propagation — ít nhất nắm 3 loại đầu:

Propagation Hành vi
REQUIRED (mặc định) Dùng transaction hiện có, không có thì tạo mới
REQUIRES_NEW Luôn tạo transaction mới, tạm dừng transaction hiện tại. Dùng cho audit log cần lưu dù transaction chính rollback
NESTED Savepoint trong transaction hiện tại (chỉ với JDBC/DataSource transaction manager)
SUPPORTS Có thì dùng, không có thì chạy không transaction
MANDATORY Bắt buộc phải có transaction sẵn, không có thì throw
NOT_SUPPORTED / NEVER Chạy ngoài transaction / throw nếu đang có transaction

Bẫy: với REQUIRED, nếu method bên trong throw RuntimeException và bên ngoài catch lại, transaction vẫn bị đánh dấu rollback-only → khi commit sẽ gặp UnexpectedRollbackException.

Các thuộc tính khác:

  • isolation: liên quan trực tiếp đến 4 isolation level (Read Uncommitted, Read Committed, Repeatable Read, Serializable) — hãy ôn lại dirty read, non-repeatable read, phantom read.
  • readOnly = true: gợi ý tối ưu (Hibernate bỏ dirty checking, có thể route sang replica).
  • timeout.

6.4. Concurrency trong database#

  • Optimistic locking: @Version — update kèm điều kiện version, xung đột thì ném OptimisticLockException. Phù hợp khi xung đột hiếm.
  • Pessimistic locking: @Lock(LockModeType.PESSIMISTIC_WRITE) → SELECT ... FOR UPDATE. Phù hợp khi xung đột thường xuyên, nhưng giảm concurrency và có nguy cơ deadlock.

6.5. Performance cơ bản#

Connection pool HikariCP (mặc định của Spring Boot), index, pagination (Pageable, Slice vs Page), batch insert (hibernate.jdbc.batch_size; lưu ý GenerationType.IDENTITY vô hiệu hóa batch insert).


7. Spring Security#

7.1. Kiến trúc#

flowchart LR
    R[Request] --> DFP["DelegatingFilterProxy"]
    DFP --> FCP["FilterChainProxy"]
    FCP --> SFC["SecurityFilterChain: CorsFilter → CsrfFilter → Authentication filters → ExceptionTranslationFilter → AuthorizationFilter"]
    SFC --> DS[DispatcherServlet]
  • Authentication (bạn là ai) vs Authorization (bạn được làm gì).
  • Thành phần chính: AuthenticationManager → AuthenticationProvider → UserDetailsService + PasswordEncoder. Kết quả lưu trong SecurityContextHolder (mặc định là ThreadLocal).
  • 401 Unauthorized (chưa xác thực) vs 403 Forbidden (đã xác thực nhưng không đủ quyền).

7.2. Cấu hình hiện đại (Spring Security 6+)#

WebSecurityConfigurerAdapter đã bị loại bỏ. Cấu hình bằng bean SecurityFilterChain với lambda DSL:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())   // chỉ hợp lý với API stateless dùng token
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
            .build();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

7.3. Những điểm hay hỏi#

  • JWT: cấu trúc (header.payload.signature), stateless, nhược điểm khó revoke → access token ngắn hạn + refresh token. Payload chỉ được encode Base64URL, không mã hóa — đừng đặt dữ liệu nhạy cảm.
  • OAuth2 / OpenID Connect: phân biệt vai trò Resource Server, Authorization Server, Client; Authorization Code flow + PKCE.
  • CSRF: tại sao có thể tắt cho API stateless dùng Bearer token, nhưng không nên tắt khi dùng cookie session.
  • CORS: cấu hình ở tầng Security (http.cors(...)), không chỉ ở MVC.
  • Method security: @PreAuthorize("hasRole('ADMIN')") — cũng dựa trên proxy, nên cũng dính bẫy self-invocation.
  • Password: luôn hash bằng BCrypt/Argon2, không bao giờ lưu plain text hoặc chỉ dùng MD5/SHA.

8. Testing#

8.1. Test pyramid#

flowchart TB
    E2E["E2E test - ít, chậm"] --> IT["Integration test - @SpringBootTest, Testcontainers"]
    IT --> SL["Slice test - @WebMvcTest, @DataJpaTest"]
    SL --> UT["Unit test - JUnit 5 + Mockito - nhiều, nhanh"]
  • Unit test: không cần Spring context. JUnit 5 + Mockito (@Mock, @InjectMocks, when().thenReturn(), verify(), ArgumentCaptor).
  • Slice test: chỉ load một phần context.
    • @WebMvcTest(OrderController.class) + MockMvc: test tầng web.
    • @DataJpaTest: test repository, mặc định rollback sau mỗi test.
  • @SpringBootTest: load toàn bộ context, chậm nhất. Dùng cho integration test.
  • @MockitoBean (Spring Framework 6.2 / Boot 3.4+) thay thế @MockBean đã deprecated: thay bean trong context bằng mock.
  • Testcontainers: chạy DB, Kafka, Redis thật trong Docker khi test — tốt hơn H2 vì tránh khác biệt dialect. Spring Boot 3.1+ hỗ trợ @ServiceConnection.
  • Hiểu context caching: các test dùng cùng cấu hình context sẽ tái sử dụng context; lạm dụng @MockitoBean với các tổ hợp khác nhau làm test chậm.

9. Microservices và các chủ đề mở rộng#

Mức độ sâu phụ thuộc vị trí ứng tuyển. Với mid/senior, cần thảo luận được trade-off:

  • Monolith vs Microservices: đừng mặc định microservices là tốt hơn. Nêu được chi phí: distributed transaction, network latency, observability, deployment phức tạp.
  • Giao tiếp: sync (REST, gRPC) vs async (Kafka, RabbitMQ). Khi nào dùng message queue.
  • Distributed transaction: tại sao 2PC ít được dùng, Saga pattern (choreography vs orchestration), Outbox pattern để đảm bảo ghi DB và publish event nhất quán.
  • Idempotency: consumer phải xử lý được message trùng lặp (at-least-once delivery).
  • Resilience: Resilience4j — circuit breaker, retry (kèm backoff), rate limiter, bulkhead, timeout.
  • Caching: @Cacheable, @CacheEvict, @CachePut với Redis/Caffeine. Các vấn đề cache: invalidation, cache stampede, TTL. @Cacheable cũng dựa trên proxy.
  • Async: @Async + @EnableAsync, luôn cấu hình executor riêng; @Scheduled và vấn đề khi chạy nhiều instance (ShedLock).
  • Observability: log có cấu trúc + correlation ID / trace ID, metrics (Micrometer + Prometheus), distributed tracing (OpenTelemetry).
  • Deployment: Docker, image layering, health check cho Kubernetes (liveness vs readiness probe qua Actuator), graceful shutdown (server.shutdown=graceful).

10. Bộ câu hỏi tự kiểm tra#

Hãy thử trả lời thành tiếng mỗi câu trong khoảng 1–2 phút. Câu nào ấp úng là chỗ cần ôn lại.

Core Java

  1. HashMap xử lý collision như thế nào? Điều gì thay đổi ở Java 8?
  2. Tại sao String là immutable?
  3. ConcurrentHashMap khác Collections.synchronizedMap() và Hashtable ra sao?
  4. volatile có đủ để làm biến đếm thread-safe không?
  5. Khác biệt giữa map và flatMap trong Stream?

Spring Core / Boot

  1. Tại sao nên dùng constructor injection?
  2. Singleton bean có thread-safe không?
  3. Inject prototype bean vào singleton bean thì chuyện gì xảy ra?
  4. Giải thích cách Spring Boot tự cấu hình DataSource khi bạn chỉ thêm dependency và vài dòng config.
  5. Làm sao override một bean do auto-configuration tạo ra?

Web / Data

  1. Mô tả đường đi của một HTTP request từ client đến controller và ngược lại.
  2. Filter khác Interceptor thế nào? Khi nào dùng cái nào?
  3. N+1 problem là gì, phát hiện và xử lý ra sao?
  4. Liệt kê các trường hợp @Transactional không có tác dụng.
  5. REQUIRED khác REQUIRES_NEW thế nào? Cho một use case thực tế của REQUIRES_NEW.
  6. Optimistic locking và pessimistic locking — khi nào chọn cái nào?

Security / Testing / Architecture

  1. Mô tả luồng xác thực JWT trong một API Spring Boot.
  2. 401 và 403 khác nhau thế nào?
  3. @WebMvcTest khác @SpringBootTest thế nào?
  4. Làm sao đảm bảo vừa lưu order vào DB vừa gửi event lên Kafka mà không bị mất nhất quán?

11. Kế hoạch ôn tập gợi ý#

flowchart LR
    D1["Ngày 1-2: Core Java + Collections + Concurrency"] --> D2["Ngày 3: Spring Core + AOP"]
    D2 --> D3["Ngày 4: Spring Boot + MVC/REST"]
    D3 --> D4["Ngày 5: JPA + Transaction + Isolation level"]
    D4 --> D5["Ngày 6: Security + Testing"]
    D5 --> D6["Ngày 7: Microservices + ôn bộ câu hỏi + chuẩn bị câu chuyện dự án"]

Theo từng level, trọng tâm khác nhau:

  • Junior / Fresher: Core Java thật chắc (OOP, Collections, Exception), DI và Bean cơ bản, viết được REST CRUD có validation và exception handling, JPA cơ bản, SQL cơ bản.
  • Mid-level: thêm cơ chế bên trong (auto-configuration, proxy, @Transactional pitfalls), N+1, locking, Security với JWT, viết test tử tế, hiểu concurrency.
  • Senior: thiết kế hệ thống, trade-off kiến trúc, performance tuning (JVM, connection pool, query), distributed system (Saga, Outbox, idempotency, resilience), observability, và khả năng kể lại các sự cố production bạn đã xử lý.

12. Lời khuyên khi vào phòng phỏng vấn#

  • Trả lời theo cấu trúc: định nghĩa ngắn → cơ chế hoạt động → ví dụ thực tế → trade-off hoặc bẫy. Ví dụ với @Transactional: nó là gì → proxy hoạt động ra sao → bạn đã dùng thế nào → các trường hợp nó không hoạt động.
  • Gắn với kinh nghiệm thật: “Ở dự án trước, API danh sách đơn hàng chậm vì N+1, mình đã dùng @EntityGraph và giảm từ 101 query xuống 1” có giá trị hơn nhiều so với định nghĩa sách vở.
  • Chuẩn bị 2–3 câu chuyện theo format STAR (Situation, Task, Action, Result): một bug khó, một lần tối ưu performance, một quyết định thiết kế.
  • Nói “không biết” một cách chuyên nghiệp: nêu những gì bạn biết liên quan và cách bạn sẽ tìm hiểu, thay vì đoán bừa.
  • Luyện live coding: viết một REST API nhỏ có validation, exception handler, repository và test trong khoảng 45 phút mà không cần tra cứu nhiều.

Chúc bạn phỏng vấn thành công! 🚀