티스토리 뷰

Spring

스프링 두뇌 풀 가동 2

GiHoo 2025. 3. 5. 15:26

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()을 수행

 

🔹 실제 동작 방식

  1. 클라이언트가 placeOrder() 메서드 호출
  2. 트랜잭션 프록시가 동작하여 트랜잭션 시작
  3. placeOrder() 내부 코드 실행
  4. 정상 실행 → commit() 수행
  5. 예외 발생 → 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️⃣ 클라이언트 요청

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 요청 처리 흐름 정리

  1.  클라이언트 요청 (GET /users/1)
  2. DispatcherServlet이 요청을 가로챔
  3. HandlerMapping을 통해 적절한 컨트롤러를 찾음
  4. HandlerAdapter를 통해 컨트롤러 실행
  5. 컨트롤러가 비즈니스 로직을 실행하고 데이터를 반환
  6. View Resolver 또는 HttpMessageConverter를 통해 응답 처리
  7. DispatcherServlet이 최종 응답을 반환

📌 정리

DispatcherServlet은 Spring MVC의 "중앙 허브"로 모든 요청을 제어함

핸들러 매핑 → 컨트롤러 실행 → 응답 변환 과정을 수행

JSON 응답일 경우 HttpMessageConverter, HTML 응답일 경우 ViewResolver가 사용됨

 


Q4. Spring의 FilterInterceptor의 차이점은 무엇이며, 각각 어떤 경우에 사용해야 하나요?

더보기

📌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를 사용하는 것이 일반적

공지사항
최근에 올라온 글
최근에 달린 댓글
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30