티스토리 뷰
Q1. Spring에서 트랜잭션 관리를 어떻게 할 수 있는지 설명하고, @Transactional의 동작 원리를 설명해주세요.
📌 Spring의 트랜잭션 관리 방식
스프링은 트랜잭션을 처리할 때 선언적 트랜잭션 관리(Declarative Transaction Management) 방식을 제공하며, 이를 @Transactional을 통해 쉽게 적용할 수 있음.
@Service
public class OrderService {
@Transactional
public void placeOrder() {
// 1. 트랜잭션 시작
// 2. 주문 생성 로직 수행
// 3. 결제 로직 수행
// 4. 재고 감소 로직 수행
// 5. 트랜잭션 커밋 (예외 발생 시 롤백)
}
}
📌 @Transactional의 동작 원리
1. 프록시 기반 AOP (Proxy Pattern)
✅ Spring AOP를 사용하여 @Transactional이 적용된 메서드를 프록시로 감싸서 실행
✅ 트랜잭션을 시작하고, 정상적으로 끝나면 commit(), 예외가 발생하면 rollback()을 수행
🔹 실제 동작 방식
- 클라이언트가 placeOrder() 메서드 호출
- 트랜잭션 프록시가 동작하여 트랜잭션 시작
- placeOrder() 내부 코드 실행
- 정상 실행 → commit() 수행
- 예외 발생 → rollback() 수행
👉 이 덕분에 개발자가 try-catch로 직접 트랜잭션을 관리할 필요가 없음!
2. PlatformTransactionManager 활용
@Transactional은 내부적으로 PlatformTransactionManager를 사용하여 DB 트랜잭션을 관리함.
- JPA 사용 시 → JpaTransactionManager
- JDBC 사용 시 → DataSourceTransactionManager
- MyBatis 사용 시 → MyBatisTransactionManager
스프링은 적절한 TransactionManager를 자동으로 선택해서 적용해 줌.
📌 @Transactional의 주요 옵션
rollbackFor | 특정 예외가 발생했을 때만 롤백 (기본적으로 RuntimeException만 롤백) |
noRollbackFor | 특정 예외가 발생해도 롤백하지 않도록 설정 |
propagation | 트랜잭션 전파 방식 설정 (기본값 REQUIRED) |
isolation | 트랜잭션 격리 수준 설정 |
📌 예제 코드
1. 기본적인 @Transactional 사용
@Service
public class OrderService {
@Transactional
public void placeOrder() {
// 트랜잭션 시작
orderRepository.save(new Order());
paymentService.processPayment();
// 트랜잭션 커밋 (예외 발생 시 롤백)
}
}
2. 특정 예외에 대해서만 롤백
@Transactional(rollbackFor = Exception.class) // 모든 예외 발생 시 롤백
public void updateOrder() throws Exception {
orderRepository.updateStatus(1L, "COMPLETED");
throw new Exception("강제 예외 발생"); // 기본적으로 Checked Exception은 롤백되지 않음 → rollbackFor 설정 필요
}
3. 트랜잭션 전파(Propagation) 설정
@Transactional(propagation = Propagation.REQUIRES_NEW) // 항상 새로운 트랜잭션 시작
public void processRefund() {
refundRepository.save(new Refund());
}
✅ Propagation.REQUIRES_NEW → 기존 트랜잭션과 별개로 새로운 트랜잭션을 시작
📌 결론: @Transactional을 사용하면 좋은 이유
✅ 트랜잭션 관리 코드(commit(), rollback())를 자동으로 처리해줌
✅프록시 기반 AOP 방식으로 트랜잭션을 관리
✅PlatformTransactionManager를 활용하여 DB 트랜잭션을 쉽게 제어 가능
✅ 전파 방식(Propagation)과 예외 롤백 정책을 유연하게 설정 가능
Q2. Spring의 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)에 대해 설명하고, 실제로 활용할 수 있는 예시를 들어보세요.
📌 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)란?
✅ 핵심 비즈니스 로직(service)과 공통 관심사(Cross-Cutting Concern, 예: 로깅, 트랜잭션, 보안)를 분리하는 프로그래밍 기법
✅ 주요 목적: 서비스 로직을 깔끔하게 유지하면서 공통 기능을 효율적으로 관리
public void serviceMethod() {
// [공통 관심사] 로깅
System.out.println("Method 실행 전");
// [핵심 관심사] 실제 서비스 로직
executeBusinessLogic();
// [공통 관심사] 로깅
System.out.println("Method 실행 후");
}
➡ 위처럼 모든 서비스 메서드마다 로깅을 넣으면 코드가 지저분해짐.
➡ 그래서 공통 로직(로깅)을 AOP로 따로 관리하여 핵심 로직을 깨끗하게 유지하는 것!
📌 AOP의 핵심 개념
Aspect | 여러 클래스에 공통으로 적용되는 관심사 (ex: 로깅, 트랜잭션, 보안) |
Advice | 언제 실행될지 정의 (Before, After, Around 등) |
JoinPoint | Advice가 실행될 지점 (ex: 특정 메서드 실행) |
Pointcut | JoinPoint를 필터링하여 특정 조건에 맞는 곳에서만 Advice 실행 |
Weaving | Advice를 실제 코드에 적용하는 과정 (런타임, 컴파일 타임 등) |
📌 AOP 활용 예제 (로깅 기능 적용)
1. AOP 설정 (Aspect, Advice, Pointcut 활용)
@Aspect
@Component
public class LoggingAspect {
// Pointcut: 실행 위치 지정 (service 패키지의 모든 메서드)
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// Before Advice: 메서드 실행 전에 로깅
@Before("serviceMethods()")
public void beforeMethodExecution(JoinPoint joinPoint) {
System.out.println("Executing: " + joinPoint.getSignature().toShortString());
}
// After Advice: 메서드 실행 후 로깅
@After("serviceMethods()")
public void afterMethodExecution(JoinPoint joinPoint) {
System.out.println("Finished: " + joinPoint.getSignature().toShortString());
}
}
✅ @Before, @After를 사용하여 공통 로직을 실행할 수 있음
✅ 서비스 코드에서는 로깅을 신경 쓰지 않아도 됨!
2. 서비스 코드 (핵심 로직만 작성)
@Service
public class OrderService {
public void processOrder() {
System.out.println("주문 처리 중...");
}
}
➡ 이제 OrderService.processOrder()가 실행되면 AOP가 자동으로 로깅을 수행!
📌 AOP의 장점
✅ 핵심 로직(Service)과 공통 관심사(Logging, Security 등)를 분리하여 코드가 깔끔해짐
✅ 반복되는 공통 기능을 AOP로 관리하여 유지보수성이 향상됨
✅ 기존 코드 수정 없이 새로운 기능을 추가할 수 있음 (OCP 원칙 준수)
📌 AOP 활용 사례
로깅 (Logging) | 모든 서비스 메서드 실행 정보를 자동으로 기록 |
트랜잭션 관리 | @Transactional을 통해 자동 트랜잭션 처리 |
보안 (Security) | 특정 메서드 실행 전에 인증/인가 로직 수행 |
메서드 실행 시간 측정 | 성능 모니터링을 위한 실행 시간 로깅 |
예외 처리 | 특정 예외 발생 시 공통 로직 실행 (e.g. 이메일 알림) |
📌 결론
✅ AOP는 공통 관심사(로깅, 트랜잭션, 보안 등)를 핵심 로직과 분리하는 강력한 기법
✅ 핵심 로직(service 코드)은 더 깔끔해지고, 유지보수성이 높아짐
✅ Spring에서는 @Aspect와 @Pointcut을 사용하여 쉽게 AOP를 적용할 수 있음
Q3. Spring MVC에서 DispatcherServlet의 역할을 설명하고, 요청이 들어왔을 때 처리 과정(요청 흐름)을 단계별로 설명해주세요.
📌 DispatcherServlet이란?
- Spring MVC의 Front Controller 패턴을 구현한 핵심 컴포넌트
- 모든 HTTP 요청을 가로채고, 컨트롤러(Controller)로 전달하여 응답을 반환하는 역할
📌 Spring MVC 요청 처리 흐름 (DispatcherServlet의 역할)
클라이언트가 HTTP 요청을 보냈을 때, DispatcherServlet이 어떻게 요청을 처리하는지 단계별로 설명할게.
1️⃣ 클라이언트 요청
- 사용자가 https://example.com/users/1에 GET 요청을 보냄.
2️⃣ DispatcherServlet이 요청을 가로챔
- web.xml 또는 Spring Boot의 @SpringBootApplication 설정에 의해 DispatcherServlet이 모든 요청을 처리함.
3️⃣ 핸들러 매핑(Handler Mapping) 확인
- DispatcherServlet은 요청 URL을 보고 어떤 컨트롤러(Controller)가 이 요청을 처리할지 결정
- 내부적으로 HandlerMapping 인터페이스를 사용하여 적절한 컨트롤러 메서드(핸들러)를 찾음
- @RequestMapping("/users/{id}") 같은 애노테이션이 이 과정에서 사용됨
4️⃣ 핸들러 어댑터(Handler Adapter) 실행
- DispatcherServlet은 핸들러(Controller)를 찾았지만, 이를 실행하는 방식이 다를 수 있음
- 이를 실행할 수 있도록 도와주는 것이 HandlerAdapter
5️⃣ 컨트롤러 실행
- 실제 비즈니스 로직이 실행됨
- 예시: UserController.getUserById() 실행
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findUserById(id);
return ResponseEntity.ok(user);
}
}
✅ 컨트롤러의 역할은 비즈니스 로직을 수행하고, 결과를 반환하는 것!
6️⃣ View Resolver를 통해 응답 처리
(A) JSON 응답 (RestController)
- @RestController나 @ResponseBody가 붙어 있다면 JSON 데이터를 반환
- HttpMessageConverter를 통해 JSON 형식으로 변환 후 응답
(B) View 반환 (Thymeleaf, JSP)
- @Controller를 사용하고 문자열을 반환하면, View Resolver가 JSP, Thymeleaf 등의 템플릿을 찾아 렌더링
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home"; // home.jsp 또는 home.html로 이동
}
}
➡ ViewResolver가 home.jsp를 찾아서 응답 페이지를 렌더링함
7️⃣ 응답 반환 (DispatcherServlet이 클라이언트에게 전달)
- DispatcherServlet은 컨트롤러에서 받은 데이터를 JSON 또는 HTML 형태로 변환하여 클라이언트에게 반환
📌 DispatcherServlet 요청 처리 흐름 정리
- 클라이언트 요청 (GET /users/1)
- DispatcherServlet이 요청을 가로챔
- HandlerMapping을 통해 적절한 컨트롤러를 찾음
- HandlerAdapter를 통해 컨트롤러 실행
- 컨트롤러가 비즈니스 로직을 실행하고 데이터를 반환
- View Resolver 또는 HttpMessageConverter를 통해 응답 처리
- DispatcherServlet이 최종 응답을 반환
📌 정리
✅ DispatcherServlet은 Spring MVC의 "중앙 허브"로 모든 요청을 제어함
✅ 핸들러 매핑 → 컨트롤러 실행 → 응답 변환 과정을 수행
✅ JSON 응답일 경우 HttpMessageConverter, HTML 응답일 경우 ViewResolver가 사용됨
Q4. Spring의 Filter와 Interceptor의 차이점은 무엇이며, 각각 어떤 경우에 사용해야 하나요?
📌Filtervs Interceptor 차이점 정리
Filter | Interceptor | |
실행 위치 | DispatcherServlet 전후로 실행 (Servlet 단계) | DispatcherServlet 이후 컨트롤러 실행 전/후 |
동작 범위 | 모든 요청 (정적 리소스 포함) | 컨트롤러로 들어오는 요청만 적용 |
적용 방식 | web.xml 또는 @Component로 등록 (javax.servlet.Filter) | HandlerInterceptor 구현 후 addInterceptors() 등록 |
사용 목적 | 보안, 인코딩, 로깅, CORS 등의 전반적인 요청 처리 | 인증/인가, 성능 분석, API 요청 처리 전/후 추가 로직 실행 |
제어 가능 범위 | 요청/응답을 변경 가능 | 컨트롤러 실행 전/후에만 제어 가능 |
📌 Filter (서블릿 필터)
Filter는 DispatcherServlet이 실행되기 전에 모든 요청을 가로채는 역할을 함.
➡ 정적 리소스(CSS, JS, 이미지) 요청도 포함
📌 Filter 예제 (CORS 처리)
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-Control-Allow-Origin", "*"); // CORS 설정
chain.doFilter(request, response); // 다음 필터로 요청 전달
}
}
✅ CORS, 인증, 요청 데이터 조작 등의 경우 Filter를 사용
✅ 모든 요청에 대해 전역적으로 처리 가능
📌 Interceptor (인터셉터)
Interceptor는 Spring MVC의 DispatcherServlet이 실행된 후, 컨트롤러에 접근하기 전에 동작함.
➡ 정적 리소스는 제외하고, 컨트롤러 요청에 대해서만 동작
📌 Interceptor 예제 (로그인 체크)
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("user") == null) {
response.sendRedirect("/login"); // 로그인 안 된 경우 로그인 페이지로 리다이렉트
return false; // 요청 차단
}
return true; // 요청 진행
}
}
✅ preHandle()에서 인증 여부 확인 후, 인증되지 않은 경우 요청 차단
✅ JWT 토큰 검증, API 사용량 체크 등의 경우 Interceptor를 사용
📌 Interceptor 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/secure/**") // 특정 URL 패턴에만 적용
.excludePathPatterns("/public/**"); // 제외할 URL 패턴
}
}
📌 Filter vs Interceptor 언제 사용할까?
Filter | Interceptor | |
CORS 설정 | ✅ | ❌ |
XSS/CSRF 보안 | ✅ | ❌ |
요청 인코딩 설정 | ✅ | ❌ |
API 인증 (JWT, OAuth) | ✅ | ✅ (보통 Interceptor 사용) |
성능 모니터링 (로그 수집) | ❌ | ✅ |
사용자 권한 체크 (관리자 여부 확인) | ❌ | ✅ |
📌 결론
✅ Filter는 서블릿 레벨에서 전역적으로 동작하며, 모든 요청(정적 리소스 포함)을 제어할 때 사용
✅ Interceptor는 Spring MVC에서 컨트롤러 진입 전에 동작하며, API 인증/인가, 성능 로깅 등에 사용
✅ CORS, 보안 관련 처리는 Filter를, 인증 및 로깅은 Interceptor를 사용하는 것이 일반적