dailyrium
/find

Java & Spring Boot 면접 빈출 질문 정리

Java & Spring Boot 면접 빈출 질문 정리


📦 PART 1. Java 핵심


1. JVM 구조와 동작 원리

질문: JVM의 구조를 설명하고, 자바 코드가 실행되는 과정을 말해주세요.

JVM은 크게 Class Loader, Runtime Data Area, Execution Engine 세 부분으로 구성된다.

① Class Loader
.java → 컴파일 → .class(바이트코드) → Class Loader가 JVM 메모리에 로드한다.
로딩 → 링킹(검증/준비/해석) → 초기화 순서로 진행된다.

② Runtime Data Area (메모리 구조)

영역설명공유 여부
Method Area클래스 메타데이터, static 변수, 상수 풀스레드 공유
Heap객체 인스턴스, 배열스레드 공유
Stack메서드 호출 스택, 지역 변수, 프레임스레드별 독립
PC Register현재 실행 중인 명령어 주소스레드별 독립
Native Method StackC/C++ 네이티브 메서드스레드별 독립

③ Execution Engine
바이트코드를 실제 기계어로 변환해서 실행한다.

  • 인터프리터: 한 줄씩 해석 (느림)
  • JIT 컴파일러: 자주 실행되는 코드를 통째로 기계어로 컴파일 (빠름)
  • GC: Heap 영역의 불필요한 객체 정리

2. GC (Garbage Collection)

질문: GC의 동작 원리와 종류를 설명해주세요.

기본 원리: Reachability
GC Root(스택 변수, static 변수 등)에서 참조 가능한 객체는 살리고, 닿지 않는 객체는 수거한다.

Heap 구조

Young Generation          Old Generation    Metaspace
┌──────────────────┐     ┌──────────────┐  ┌──────────┐
│ Eden | S0 | S1    │ →   │              │  │ 클래스     │
└──────────────────┘     └──────────────┘  │ 메타데이터  │
  Minor GC 발생               Major GC       └──────────┘
  • Eden: 새 객체가 태어나는 곳
  • Survivor(S0/S1): Minor GC에서 살아남은 객체 이동
  • Old: 오래 살아남은 객체 (여기서 GC = Major GC, 느림)

GC 종류

GC특징사용 시점
Serial GC단일 스레드, STW소형 앱
Parallel GC멀티 스레드, 처리량 중심Java 8 기본
G1 GC영역 분할, 예측 가능한 pauseJava 9+ 기본
ZGC초저지연, 1ms 이하 pause대규모 서비스
ShenandoahRed Hat, ZGC 유사저지연 필요 시

STW(Stop-The-World): GC 실행 중 모든 애플리케이션 스레드가 멈추는 현상. 이걸 줄이는 게 GC 튜닝의 핵심이다.


3. Java 메모리 누수

질문: Java에서 메모리 누수가 발생하는 경우를 설명해주세요.

Java는 GC가 있지만 메모리 누수가 생길 수 있다.

대표 케이스

// 1. static 컬렉션에 계속 추가하고 제거 안 할 때
static List<Object> cache = new ArrayList<>();

// 2. Connection, Stream 등을 close() 안 했을 때
Connection conn = dataSource.getConnection();
// close() 없음 → 누수

// 3. 리스너/콜백 등록 후 해제 안 했을 때
eventBus.register(listener);
// unregister() 없음

// 4. ThreadLocal 사용 후 remove() 안 했을 때
ThreadLocal<UserContext> ctx = new ThreadLocal<>();
ctx.set(user);
// ctx.remove() 없음 → 스레드풀에서 누수

해결: try-with-resources, WeakReference 활용, ThreadLocal.remove() 습관화


4. equals()와 hashCode()

질문: equals()와 hashCode()를 같이 오버라이드해야 하는 이유는?

// 오버라이드 안 한 경우
User u1 = new User("kim", 30);
User u2 = new User("kim", 30);

u1.equals(u2); // false (Object 기본: 주소 비교)

Set<User> set = new HashSet<>();
set.add(u1);
set.contains(u2); // false → 논리적으로 같은데 다르다고 판단

HashSet/HashMap 동작 원리

  1. hashCode()로 버킷 위치 결정
  2. 같은 버킷 안에서 equals()로 동일성 비교

→ hashCode만 같고 equals가 다르면: 같은 버킷에 중복 저장
→ equals만 같고 hashCode가 다르면: 다른 버킷에 저장돼서 contains() 실패

올바른 오버라이드

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User)) return false;
    User user = (User) o;
    return age == user.age && Objects.equals(name, user.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

5. String, StringBuilder, StringBuffer

질문: 셋의 차이점과 언제 뭘 써야 하는지 설명해주세요.

구분불변 여부스레드 안전성능
String불변 (immutable)안전연산 많으면 느림
StringBuilder가변 (mutable)안전하지 않음빠름
StringBuffer가변 (mutable)synchronized 안전중간
// String: + 연산마다 새 객체 생성 → 루프에서 쓰면 메모리 낭비
String s = "";
for (int i = 0; i < 1000; i++) s += i; // 객체 1000개 생성

// StringBuilder: 단일 스레드 문자열 조작에 적합
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i); // 객체 1개

// StringBuffer: 멀티스레드 환경에서만 사용

6. 컬렉션 프레임워크

질문: List, Set, Map의 차이와 각 구현체의 특징을 설명해주세요.

List

구현체특징시간복잡도
ArrayList동적 배열, 인덱스 조회 빠름조회 O(1), 삽입/삭제 O(n)
LinkedList이중 연결 리스트, 삽입/삭제 빠름조회 O(n), 삽입/삭제 O(1)

Set

구현체특징
HashSet순서 없음, 가장 빠름 O(1)
LinkedHashSet삽입 순서 유지
TreeSet정렬 유지, O(log n)

Map

구현체특징
HashMap순서 없음, O(1), null 키 허용
LinkedHashMap삽입 순서 유지
TreeMap키 기준 정렬
ConcurrentHashMap멀티스레드 안전, 세그먼트 락

HashMap 동작 원리

key → hashCode() → 버킷 인덱스 결정
같은 버킷에 충돌 시 → LinkedList로 연결 (Java 8부터 8개 초과 시 TreeNode로 전환)

7. 스레드와 동시성

질문: synchronized, volatile, atomic의 차이를 설명해주세요.

synchronized

// 메서드 레벨
public synchronized void increment() { count++; }

// 블록 레벨 (더 세밀한 제어)
synchronized (this) { count++; }

→ 한 번에 한 스레드만 진입 보장. 성능 오버헤드 있음.

volatile

private volatile boolean running = true;

→ 변수를 CPU 캐시가 아닌 메인 메모리에서 직접 읽고 씀.
→ 가시성(visibility) 보장. 하지만 원자성(atomicity)은 보장 안 함.
→ count++ 같은 복합 연산에는 부족하다.

AtomicInteger

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // CAS(Compare-And-Swap)로 원자성 보장

→ synchronized보다 가볍고 빠름.


8. 인터페이스 vs 추상 클래스

질문: 인터페이스와 추상 클래스의 차이와 언제 무엇을 쓰는지 설명해주세요.

구분인터페이스추상 클래스
다중 구현가능불가능
생성자없음있음
상태(필드)상수만인스턴스 변수 가능
접근제어자public 기본제한 없음
목적할 수 있는 것 정의공통 속성/행동 정의
// 인터페이스: 기능 계약 (Flyable, Serializable, Runnable)
interface Flyable { void fly(); }

// 추상 클래스: 공통 베이스 (Animal → Dog, Cat)
abstract class Animal {
    String name;
    abstract void sound();
    void breathe() { System.out.println("숨쉬기"); } // 공통 구현
}

→ “~할 수 있다”면 인터페이스, “~의 일종이다”면 추상 클래스


9. Java 8 주요 기능

질문: Java 8에서 추가된 주요 기능들을 설명해주세요.

① 람다 표현식

// 이전
Comparator<String> c = new Comparator<String>() {
    public int compare(String a, String b) { return a.compareTo(b); }
};
// 이후
Comparator<String> c = (a, b) -> a.compareTo(b);

② Stream API

List<String> result = users.stream()
    .filter(u -> u.getAge() > 20)
    .map(User::getName)
    .sorted()
    .collect(Collectors.toList());

③ Optional

// NPE 방어
Optional<User> user = userRepository.findById(id);
user.ifPresent(u -> System.out.println(u.getName()));
String name = user.map(User::getName).orElse("unknown");

④ default 메서드

interface Collection {
    default void forEach(Consumer action) { ... } // 인터페이스에 구현 가능
}

⑤ 날짜/시간 API

LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();
Duration d = Duration.between(start, end);

10. 제네릭

질문: 제네릭의 목적과 와일드카드를 설명해주세요.

// 제네릭 없으면: 타입 캐스팅 필요, 런타임 오류 위험
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 캐스팅 필요

// 제네릭: 컴파일 타임 타입 체크
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 캐스팅 불필요

와일드카드

PECS 원칙: Producer Extends, Consumer Super


🌱 PART 2. Spring Core


11. IoC와 DI

질문: IoC와 DI의 개념을 설명해주세요.

IoC (Inversion of Control, 제어의 역전)
원래 객체 생성과 의존성 관리를 개발자가 직접 했는데, 이 제어권을 스프링 컨테이너에게 넘기는 것.

// IoC 없을 때: 내가 직접 생성
UserService userService = new UserServiceImpl(new UserRepositoryImpl());

// IoC 있을 때: 컨테이너가 만들어서 주입
@Service
public class UserController {
    @Autowired
    private UserService userService; // 스프링이 알아서 주입
}

DI (Dependency Injection, 의존성 주입) 3가지 방법

// 1. 생성자 주입 (가장 권장)
@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// 2. 세터 주입 (선택적 의존성)
@Autowired
public void setRepository(UserRepository repo) { this.repo = repo; }

// 3. 필드 주입 (편리하지만 비권장)
@Autowired
private UserRepository userRepository;

생성자 주입이 권장되는 이유

  • 불변성 보장 (final 사용 가능)
  • 순환 의존성 컴파일 타임에 감지
  • 테스트 시 Mock 주입 용이
  • 필수 의존성 명시

12. Spring Bean 스코프

질문: Spring Bean의 스코프 종류를 설명해주세요.

스코프설명사용 예
singleton기본값, 컨테이너당 인스턴스 1개대부분의 서비스
prototype요청마다 새 인스턴스상태를 가지는 객체
requestHTTP 요청마다 새 인스턴스웹 요청 처리
sessionHTTP 세션마다 새 인스턴스로그인 세션
applicationServletContext당 1개앱 전역 상태
@Component
@Scope("prototype")
public class ShoppingCart { ... }

주의: singleton 빈이 prototype 빈을 주입받으면 prototype이 사실상 singleton처럼 동작한다. 이때는 ObjectProvider또는 @Lookup을 사용해야 한다.


13. AOP

질문: AOP(관점 지향 프로그래밍)가 무엇이고 스프링에서 어떻게 동작하는지 설명해주세요.

AOP란?
로깅, 보안, 트랜잭션 같은 횡단 관심사(cross-cutting concern) 를 핵심 비즈니스 로직에서 분리하는 기법.

핵심 용어

용어설명
Aspect횡단 관심사 모듈 (로깅, 트랜잭션 등)
JoinPointAdvice를 적용할 수 있는 지점 (메서드 실행 등)
Pointcut어떤 JoinPoint에 적용할지 표현식
Advice실제 실행될 코드 (Before/After/Around 등)
ProxyAOP가 적용된 객체를 감싸는 대리 객체
@Aspect
@Component
public class LoggingAspect {

    // "service 패키지의 모든 public 메서드"에 적용
    @Around("execution(* com.example.service.*.*(..))")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // 실제 메서드 실행
        long end = System.currentTimeMillis();
        System.out.println(pjp.getSignature() + " 실행 시간: " + (end - start) + "ms");
        return result;
    }
}

스프링 AOP 동작 방식

  • JDK Dynamic Proxy: 인터페이스 기반
  • CGLIB Proxy: 클래스 기반 (인터페이스 없을 때)
  • 스프링 부트는 기본적으로 CGLIB 사용

14. @Transactional

질문: @Transactional의 동작 원리와 주의사항을 설명해주세요.

동작 원리
AOP 프록시가 트랜잭션을 시작하고, 메서드 실행 후 커밋/롤백을 결정한다.

@Service
public class OrderService {
    @Transactional
    public void createOrder(OrderDto dto) {
        // 프록시가 트랜잭션 시작
        orderRepository.save(order);
        stockRepository.decrease(dto.getItemId());
        // 성공 시 커밋, 예외 시 롤백
    }
}

전파(Propagation) 옵션

옵션설명
REQUIRED (기본)기존 트랜잭션 있으면 참여, 없으면 새로 생성
REQUIRES_NEW항상 새 트랜잭션 생성, 기존 것 일시 중단
NESTED중첩 트랜잭션 (savepoint 사용)
SUPPORTS트랜잭션 있으면 참여, 없으면 없이 실행
NOT_SUPPORTED트랜잭션 없이 실행
NEVER트랜잭션 있으면 예외

격리(Isolation) 수준

수준Dirty ReadNon-Repeatable ReadPhantom Read
READ_UNCOMMITTED발생발생발생
READ_COMMITTED방지발생발생
REPEATABLE_READ방지방지발생
SERIALIZABLE방지방지방지

⚠️ 주의사항 (자주 실수하는 것들)

// 1. private 메서드에는 적용 안 됨 (프록시 통과 불가)
@Transactional
private void doSomething() { ... } // 무효

// 2. 같은 클래스 내 self-invocation은 프록시 우회
public void outer() {
    inner(); // 트랜잭션 적용 안 됨! 프록시를 거치지 않음
}
@Transactional
public void inner() { ... }

// 3. RuntimeException만 기본 롤백
@Transactional // IOException은 롤백 안 됨
public void save() throws IOException { ... }

// rollbackFor 지정해야 함
@Transactional(rollbackFor = Exception.class)
public void save() throws IOException { ... }

🚀 PART 3. Spring Boot 핵심


15. Spring Boot 자동 설정 원리

질문: Spring Boot의 자동 설정(Auto Configuration)이 어떻게 동작하는지 설명해주세요.

@SpringBootApplication
    ↓
@EnableAutoConfiguration
    ↓
spring.factories (또는 AutoConfiguration.imports) 파일 읽기
    ↓
조건(@Conditional)에 맞는 설정 클래스들을 자동으로 빈으로 등록

예를 들어 spring-boot-starter-data-jpa 의존성을 추가하면:

  1. classpath에 JPA 라이브러리 존재 감지
  2. @ConditionalOnClass(DataSource.class) 조건 충족
  3. DataSource, EntityManagerFactory, TransactionManager 자동 등록
// 이런 식의 자동 설정 클래스들이 내부에 있음
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class) // 내가 직접 정의하면 적용 안 함
public class DataSourceAutoConfiguration { ... }

→ application.yml에서 spring.datasource.url 등을 설정하면 이 설정 클래스들이 읽어서 적용한다.


16. Spring Boot 실행 과정

질문: SpringApplication.run()이 실행될 때 내부적으로 무슨 일이 일어나는지 설명해주세요.

1. SpringApplication 객체 생성
2. ApplicationContext 타입 결정 (웹/비웹)
3. ApplicationListener, Initializer 로드
4. run() 실행
   ├── Environment 준비 (application.yml 로드)
   ├── ApplicationContext 생성 및 준비
   ├── Bean 정의 로드 (@ComponentScan)
   ├── 자동 설정 적용
   ├── 모든 Bean 생성 및 의존성 주입
   ├── CommandLineRunner, ApplicationRunner 실행
   └── 서버 시작 (내장 Tomcat)

17. 필터 vs 인터셉터

질문: Filter와 Interceptor의 차이를 설명해주세요.

구분FilterInterceptor
속한 곳서블릿 컨테이너 (J2EE)스프링 컨테이너
실행 시점DispatcherServlet 앞DispatcherServlet 뒤
스프링 빈 접근어려움쉬움
요청/응답 조작가능 (바이트 레벨)일부 가능
사용 예인코딩, CORS, XSS 방어인증/인가, 로깅, 권한 체크
HTTP 요청
    ↓
[Filter 1] → [Filter 2]
    ↓
DispatcherServlet
    ↓
[Interceptor preHandle]
    ↓
Controller 실행
    ↓
[Interceptor postHandle]
    ↓
View 렌더링
    ↓
[Interceptor afterCompletion]
    ↓
[Filter 2] → [Filter 1] (역순)
    ↓
HTTP 응답

18. Spring Security

질문: Spring Security의 인증 처리 흐름을 설명해주세요.

HTTP 요청
    ↓
SecurityFilterChain (여러 Filter로 구성)
    ↓
UsernamePasswordAuthenticationFilter
    ↓
AuthenticationManager (ProviderManager)
    ↓
AuthenticationProvider (DaoAuthenticationProvider)
    ↓
UserDetailsService.loadUserByUsername()
    ↓
UserDetails 반환 → 비밀번호 검증
    ↓
Authentication 객체 생성 → SecurityContextHolder에 저장
    ↓
인증 성공 → 요청 계속 진행

JWT 방식에서의 차이

HTTP 요청 (Authorization: Bearer xxx)
    ↓
JwtAuthenticationFilter (커스텀 필터)
    ↓
토큰 검증 → Authentication 생성 → SecurityContext 저장
    ↓
나머지 필터/컨트롤러 진행

19. JPA / Hibernate

질문: JPA의 영속성 컨텍스트와 지연 로딩을 설명해주세요.

영속성 컨텍스트(Persistence Context)
엔티티를 관리하는 1차 캐시. EntityManager가 관리한다.

[영속성 컨텍스트]
┌─────────────────────────────┐
│  1차 캐시: {id: 1 → User}     │
│  변경 감지 (Dirty Checking)    │
│  쓰기 지연 SQL 저장소            │
└─────────────────────────────┘

엔티티 생명주기

비영속(new) → persist() → 영속(managed)
영속 → detach() → 준영속(detached)
영속 → remove() → 삭제(removed)

더티 체킹(Dirty Checking)

@Transactional
public void update(Long id, String newName) {
    User user = userRepository.findById(id).get(); // 영속 상태
    user.setName(newName); // 그냥 값만 바꿈
    // save() 안 해도 트랜잭션 종료 시 자동으로 UPDATE 쿼리 실행
}

N+1 문제

// LAZY 로딩일 때
List<Order> orders = orderRepository.findAll(); // 쿼리 1번
for (Order o : orders) {
    o.getMember().getName(); // 각 Order마다 Member 쿼리 → N번 추가 발생
}
// 총 쿼리: 1 + N번

// 해결책: fetch join
@Query("SELECT o FROM Order o JOIN FETCH o.member")
List<Order> findAllWithMember();

지연(LAZY) vs 즉시(EAGER) 로딩

  • EAGER: 연관 엔티티를 즉시 조회 → 예상치 못한 쿼리 발생 위험
  • LAZY: 실제 사용 시점에 조회 → 권장. 하지만 세션 밖에서 접근 시 LazyInitializationException 주의

20. REST API 설계

질문: RESTful API 설계 원칙을 설명해주세요.

REST 6가지 제약

  1. Client-Server 분리
  2. Stateless (서버는 클라이언트 상태 저장 안 함)
  3. Cacheable
  4. Uniform Interface
  5. Layered System
  6. Code on Demand (선택)

올바른 URL 설계

# 나쁜 예
GET /getUsers
POST /createUser
PUT /updateUser/1
DELETE /deleteUser/1

# 좋은 예
GET    /users          → 목록 조회
POST   /users          → 생성
GET    /users/{id}     → 단건 조회
PUT    /users/{id}     → 전체 수정
PATCH  /users/{id}     → 부분 수정
DELETE /users/{id}     → 삭제

HTTP 상태 코드

코드의미
200 OK성공
201 Created생성 성공
204 No Content성공, 응답 본문 없음 (DELETE)
400 Bad Request잘못된 요청
401 Unauthorized인증 필요
403 Forbidden권한 없음
404 Not Found리소스 없음
409 Conflict충돌 (중복 등)
500 Internal Server Error서버 오류

21. 예외 처리 전략

질문: Spring Boot에서 예외 처리를 어떻게 구성하는지 설명해주세요.

// 1. 커스텀 예외 클래스
public class UserNotFoundException extends RuntimeException {
    private final ErrorCode errorCode;
    public UserNotFoundException(Long id) {
        super("User not found: " + id);
        this.errorCode = ErrorCode.USER_NOT_FOUND;
    }
}

// 2. 에러 코드 enum
public enum ErrorCode {
    USER_NOT_FOUND(404, "사용자를 찾을 수 없습니다"),
    DUPLICATE_EMAIL(409, "이미 사용 중인 이메일입니다");

    private final int status;
    private final String message;
}

// 3. 전역 예외 핸들러
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse(e.getErrorCode()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleUnexpected(Exception e) {
        log.error("Unexpected error", e);
        return ResponseEntity.internalServerError()
            .body(new ErrorResponse(ErrorCode.INTERNAL_ERROR));
    }
}

22. 캐시 처리

질문: Spring의 캐시 추상화를 설명해주세요.

// @EnableCaching 활성화 후

@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        // 캐시에 있으면 메서드 실행 안 하고 바로 반환
        return userRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "users", key = "#id")
    public void deleteById(Long id) {
        // 삭제 후 캐시도 제거
        userRepository.deleteById(id);
    }

    @CachePut(value = "users", key = "#result.id")
    public User update(UserUpdateDto dto) {
        // 항상 실행하고 결과를 캐시에 저장
        return userRepository.save(dto.toEntity());
    }
}

Redis 연동 시 spring-boot-starter-data-redis + application.yml 설정만으로 자동 적용된다.


23. 빈 생명주기

질문: Spring Bean의 생명주기를 설명해주세요.

컨테이너 시작
    ↓
빈 인스턴스화 (생성자 호출)
    ↓
의존성 주입
    ↓
BeanPostProcessor.postProcessBeforeInitialization()
    ↓
@PostConstruct 실행
    ↓
InitializingBean.afterPropertiesSet()
    ↓
@Bean(initMethod) 실행
    ↓
빈 사용
    ↓
컨테이너 종료
    ↓
@PreDestroy 실행
    ↓
DisposableBean.destroy()
    ↓
@Bean(destroyMethod) 실행
@Component
public class DatabaseConnector {
    @PostConstruct
    public void init() {
        // DB 커넥션 풀 초기화 등
    }

    @PreDestroy
    public void cleanup() {
        // 연결 종료, 리소스 반납
    }
}

24. 멀티스레드 / 비동기

질문: Spring에서 비동기 처리를 어떻게 하는지 설명해주세요.

// @EnableAsync 활성화 후

@Service
public class MailService {

    @Async // 별도 스레드풀에서 비동기 실행
    public CompletableFuture<Void> sendMail(String to) {
        // 시간 오래 걸리는 작업
        mailSender.send(to);
        return CompletableFuture.completedFuture(null);
    }
}

// 스레드풀 커스텀 설정
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

25. 테스트

질문: Spring Boot에서 단위 테스트와 통합 테스트를 어떻게 작성하는지 설명해주세요.

단위 테스트 (Mockito)

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    UserService userService;

    @Test
    void 사용자_조회_성공() {
        // given
        User user = new User(1L, "kim");
        given(userRepository.findById(1L)).willReturn(Optional.of(user));

        // when
        User result = userService.findById(1L);

        // then
        assertThat(result.getName()).isEqualTo("kim");
    }
}

통합 테스트 (SpringBootTest)

@SpringBootTest
@AutoConfigureMockMvc
@Transactional // 테스트 후 롤백
class UserControllerTest {

    @Autowired MockMvc mockMvc;

    @Test
    void 사용자_생성_API_테스트() throws Exception {
        mockMvc.perform(post("/users")
                .contentType(APPLICATION_JSON)
                .content("{\"name\":\"kim\",\"age\":30}"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("kim"));
    }
}

슬라이스 테스트

@WebMvcTest(UserController.class) // Controller 레이어만
@DataJpaTest                       // JPA 레이어만
@DataRedisTest                     // Redis 레이어만

💡 PART 4. 설계 & 아키텍처


26. SOLID 원칙

원칙설명위반 시 문제
SRP (단일 책임)클래스는 하나의 책임만변경 이유가 많아짐
OCP (개방-폐쇄)확장엔 열려있고 수정엔 닫혀있음기존 코드 수정 빈번
LSP (리스코프 치환)하위 타입은 상위 타입 대체 가능다형성 깨짐
ISP (인터페이스 분리)작은 인터페이스 여러 개불필요한 메서드 구현 강제
DIP (의존 역전)추상에 의존, 구체에 의존 금지결합도 높아짐

27. 디자인 패턴

질문: 스프링에서 사용되는 주요 디자인 패턴을 설명해주세요.

패턴스프링에서의 사용
Singleton기본 Bean 스코프
FactoryBeanFactory, ApplicationContext
ProxyAOP, @Transactional
Template MethodJdbcTemplate, RestTemplate
StrategyAuthenticationProvider, HandlerMapping
ObserverApplicationEvent / EventListener
DecoratorHttpServletRequestWrapper
Chain of ResponsibilityFilterChain, HandlerInterceptor

28. 마이크로서비스 관련

질문: 모놀리식과 MSA의 차이, 그리고 MSA에서 트랜잭션 처리를 어떻게 하는지 설명해주세요.

모놀리식 vs MSA

구분모놀리식MSA
배포전체 재배포서비스별 독립 배포
확장전체 스케일업서비스별 스케일아웃
트랜잭션ACID 보장 쉬움분산 트랜잭션 어려움
복잡도코드 복잡도운영/인프라 복잡도

분산 트랜잭션 패턴: SAGA

# Choreography 방식 (이벤트 기반)
주문서비스 → OrderCreated 이벤트
    → 결제서비스 PaymentProcessed 이벤트
        → 재고서비스 StockDecreased 이벤트
            → 완료

# 실패 시 보상 트랜잭션
재고서비스 실패 → StockDecreaseFailed
    → 결제서비스 보상: PaymentCancelled
        → 주문서비스 보상: OrderCancelled

🗄️ PART 5. DB & 성능


29. 인덱스

질문: DB 인덱스의 원리와 주의사항을 설명해주세요.

B-Tree 인덱스 원리

인덱스 없을 때: 전체 테이블 스캔 O(n)
인덱스 있을 때: B-Tree 탐색 O(log n)

인덱스 걸 때 고려사항

  • 카디널리티(중복도)가 높은 컬럼에 유리 (성별은 비효율, 이메일은 효율적)
  • WHERE, JOIN, ORDER BY에 자주 쓰는 컬럼
  • 복합 인덱스는 선두 컬럼이 WHERE에 있어야 효과

인덱스가 안 타는 경우

-- 함수 사용
WHERE YEAR(created_at) = 2024  -- ❌
WHERE created_at >= '2024-01-01'  -- ✅

-- LIKE 앞에 %
WHERE name LIKE '%kim'  -- ❌
WHERE name LIKE 'kim%'  -- ✅

-- 타입 불일치
WHERE user_id = '123'  -- user_id가 INT면 ❌
WHERE user_id = 123    -- ✅

30. 커넥션 풀

질문: DB 커넥션 풀의 원리와 HikariCP 주요 설정을 설명해주세요.

DB 연결은 비싼 작업이다 (TCP 연결 + 인증). 커넥션 풀은 미리 연결을 만들어두고 재사용하는 전략이다.

spring:
  datasource:
    hikari:
      maximum-pool-size: 10      # 최대 커넥션 수
      minimum-idle: 5            # 최소 유지 커넥션 수
      connection-timeout: 30000  # 커넥션 못 얻을 때 대기 시간 (ms)
      idle-timeout: 600000       # 유휴 커넥션 유지 시간
      max-lifetime: 1800000      # 커넥션 최대 수명

풀 사이즈 공식 (HikariCP 권장)

connections = (core_count * 2) + effective_spindle_count

→ CPU 4코어면 약 8~10개가 적정. 무조건 크다고 좋지 않다.


💡 마지막 팁: 면접에서 중요한 건 단순 암기가 아니라 “왜?”를 설명할 수 있는 것이다. “생성자 주입을 씁니다” 보다 “불변성 보장과 순환참조 조기 감지를 위해 생성자 주입을 선호합니다” 처럼 이유를 붙이는 연습을 해라. 그게 합격과 불합격을 가른다.




Leave a comment