Spring 미드레벨 프레임워크 기술면접
새 면접Q. Spring AOP의 동작 원리를 프록시 패턴 관점에서 설명하고, JDK Dynamic Proxy와 CGLIB의 차이점과 각각의 사용 조건을 설명해주세요.
Spring AOP는 프록시 패턴을 사용하여 타겟 객체를 감싸는 프록시 객체를 생성하고, 메서드 호출 시 부가 기능을 주입합니다. JDK Dynamic Proxy는 인터페이스 기반으로 동작하며 리플렉션을 사용하고, 타겟이 최소 하나의 인터페이스를 구현해야 합니다. CGLIB는 클래스 기반으로 바이트코드 조작을 통해 타겟 클래스를 상속받는 프록시를 생성하며, 인터페이스가 없는 경우나 구체 클래스를 프록시해야 할 때 사용됩니다. Spring Boot 2.0부터는 기본적으로 CGLIB를 사용하며, final 클래스나 메서드는 CGLIB로 프록시할 수 없다는 제약이 있습니다.
- • 프록시 패턴을 통한 타겟 객체 감싸기와 부가 기능 주입
- • JDK Dynamic Proxy는 인터페이스 기반, CGLIB는 클래스 상속 기반
- • Spring Boot 2.0+ 기본 전략은 CGLIB, final 클래스/메서드 제약 존재
프록시 객체 내부에서 타겟 메서드를 호출할 때 this로 다른 메서드를 호출하면 AOP가 적용되지 않는 이유는 무엇인가요?
Q. @Transactional의 전파 속성(Propagation) 중 REQUIRES_NEW와 NESTED의 차이점을 설명하고, 각각 어떤 상황에서 사용하는 것이 적절한지 실무 예시와 함께 설명해주세요.
REQUIRES_NEW는 항상 새로운 물리적 트랜잭션을 생성하며, 외부 트랜잭션과 완전히 독립적으로 동작하여 서로 영향을 주지 않습니다. NESTED는 중첩 트랜잭션을 생성하며 SavePoint를 사용해 부분 롤백이 가능하지만, 외부 트랜잭션이 롤백되면 함께 롤백됩니다. REQUIRES_NEW는 결제 실패 시에도 로그는 반드시 저장해야 하는 경우처럼 독립적인 커밋이 필요할 때 사용하고, NESTED는 주문 처리 중 쿠폰 적용 실패 시 쿠폰만 롤백하고 주문은 계속 진행하는 경우처럼 부분 롤백이 필요할 때 사용합니다. NESTED는 JDBC SavePoint를 지원하는 DataSourceTransactionManager에서만 동작한다는 제약이 있습니다.
- • REQUIRES_NEW는 완전히 독립적인 물리적 트랜잭션, NESTED는 SavePoint 기반 중첩 트랜잭션
- • REQUIRES_NEW는 독립적 커밋/롤백, NESTED는 부분 롤백 가능하나 외부 롤백 시 함께 롤백
- • NESTED는 JDBC SavePoint 지원 필요, JPA에서는 제한적
@Transactional이 적용된 메서드를 같은 클래스 내부에서 호출할 때 트랜잭션이 적용되지 않는 이유와 해결 방법은 무엇인가요?
Q. Spring Bean의 생성부터 소멸까지의 전체 생명주기를 순서대로 설명하고, 각 단계에서 개발자가 커스터마이징할 수 있는 콜백 메커니즘을 설명해주세요.
Spring Bean 생명주기는 1) 빈 인스턴스화, 2) 의존성 주입, 3) 초기화 콜백, 4) 사용, 5) 소멸 콜백 순서로 진행됩니다. 초기화 단계에서는 @PostConstruct, InitializingBean 인터페이스의 afterPropertiesSet(), @Bean의 initMethod 속성을 통해 커스터마이징할 수 있으며, 우선순위는 @PostConstruct가 가장 높습니다. 소멸 단계에서는 @PreDestroy, DisposableBean 인터페이스의 destroy(), @Bean의 destroyMethod 속성을 사용할 수 있습니다. BeanPostProcessor를 구현하면 모든 빈의 초기화 전후 시점에 공통 로직을 적용할 수 있어, AOP 프록시 생성 등에 활용됩니다.
- • 생명주기: 인스턴스화 → 의존성 주입 → 초기화 콜백 → 사용 → 소멸 콜백
- • 초기화: @PostConstruct > InitializingBean > initMethod, 소멸: @PreDestroy > DisposableBean > destroyMethod
- • BeanPostProcessor로 모든 빈의 초기화 전후 공통 처리 가능
순환 참조가 발생했을 때 Spring이 이를 해결하는 방법과 3단계 캐시 전략에 대해 설명해주세요.
Q. DispatcherServlet의 요청 처리 흐름을 HandlerMapping, HandlerAdapter, ViewResolver의 역할과 함께 상세히 설명해주세요.
클라이언트 요청이 들어오면 DispatcherServlet은 1) HandlerMapping에게 요청을 위임해 적절한 컨트롤러(Handler)를 찾고, 2) HandlerAdapter를 통해 컨트롤러를 실행하여 ModelAndView를 반환받습니다. 3) ViewResolver가 논리적 뷰 이름을 실제 View 객체로 변환하고, 4) View가 Model 데이터를 렌더링하여 응답을 생성합니다. HandlerMapping은 @RequestMapping 정보를 기반으로 매핑하고, HandlerAdapter는 다양한 컨트롤러 타입(@Controller, @RestController 등)을 통일된 방식으로 실행할 수 있게 하며, ViewResolver는 JSP, Thymeleaf 등 다양한 뷰 기술을 지원합니다. @RestController의 경우 ViewResolver 없이 HandlerAdapter가 MessageConverter를 사용해 직접 응답을 생성합니다.
- • 요청 흐름: HandlerMapping(컨트롤러 탐색) → HandlerAdapter(실행) → ViewResolver(뷰 변환) → View(렌더링)
- • HandlerAdapter는 다양한 컨트롤러 타입을 통일된 방식으로 처리
- • @RestController는 MessageConverter로 직접 응답 생성, ViewResolver 생략
Interceptor와 Filter의 차이점과 각각의 실행 시점, 그리고 실무에서 어떤 용도로 구분해서 사용하는지 설명해주세요.
Q. N+1 문제가 무엇인지 설명하고, Fetch Join, EntityGraph, Batch Size를 사용한 해결 방법의 차이점과 각 방법의 장단점을 실무 관점에서 설명해주세요.
N+1 문제는 연관된 엔티티를 조회할 때 1번의 쿼리로 N개의 부모를 조회한 후, 각 부모마다 자식을 조회하는 N번의 추가 쿼리가 발생하는 성능 문제입니다. Fetch Join은 JPQL에서 join fetch를 사용해 한 번의 쿼리로 모든 데이터를 조회하지만, 페이징 시 메모리에서 처리되고 둘 이상의 컬렉션은 fetch join할 수 없습니다. EntityGraph는 @EntityGraph 어노테이션으로 동적으로 fetch 전략을 변경할 수 있으나 outer join을 사용해 중복 데이터가 발생할 수 있습니다. Batch Size는 hibernate.default_batch_fetch_size로 설정하면 IN 절을 사용해 N번을 N/batch_size번으로 줄이며, 페이징과 함께 사용 가능하고 메모리 부담이 적어 실무에서 가장 범용적으로 사용됩니다.
- • N+1 문제는 연관 엔티티 조회 시 1+N번의 쿼리가 발생하는 성능 이슈
- • Fetch Join은 1번 쿼리지만 페이징 제약, EntityGraph는 동적이나 outer join 중복 발생
- • Batch Size는 IN 절로 쿼리 횟수 감소, 페이징 가능하여 실무에서 가장 범용적
영속성 컨텍스트의 1차 캐시와 쓰기 지연의 동작 원리를 설명하고, 이것이 성능에 미치는 영향을 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!