HyunsooZo's TIL logo HyunsooZo's TIL

Spring MVC?

Spring MVC는 Spring Framework에서 제공하는 웹 애플리케이션 개발을 위한 모듈 중 하나
MVC는 Model-View-Controller의 약자로, 소프트웨어 개발에서 사용되는 소프트웨어 디자인 패턴.
MVC 핵심구성요소  
Model 애플리케이션의 비즈니스 로직 및 데이터 , 데이터베이스와의 상호작용, 데이터 처리, 비즈니스 규칙 등을 담당
View 사용자에게 결과를 표시하는 부분. 주로 HTML, CSS, JavaScript 등으로 구성되며, 클라이언트의 요청에 따라 데이터를 보여줌.
Controller 클라이언트의 요청을 처리하고, 모델과 뷰 사이의 상호작용을 조정.
클라이언트로부터의 요청을 받아 해당 요청에 대한 작업을 수행하고, 결과를 모델에 반영하고, 적절한 뷰를 선택하여 클라이언트에게 반환.

MVC Archetecture

MVC Archtecture

HTTP Req/Res

Controller/RestController의 차이

Controller : 응답값이 기본적으로 HTML을 준다.
RestController : 응답값으로 Rest API 요청에 대한 응답(Json)을 준다.

Mapping Annotation

@RequestMapping : GET, POST 등 요청방식을 직접 지정한다.

[사용 예시]

@RequestMapping(value = "order/1", method = RequestMethod.GET)
public String getOrder(){
    log.info("Get some order information")'
    return "orderId:1, orderAmount:100";
}
축약형 Mapping Annotiations  
@GetMapping 데이터를 가져옴
@PostMapping 데이터를 전송함
@PutMappting 전체 수정
@PatchMapping 부분 수정
@DeleteMapping 삭제

### Spring HTTP Request Param 전송

Get,Delete

PathVariable : 최근에는 id를 path에 넣는 것을 많이 쓴다.
- @Pathvariable("id") String identity
- 이름이 같으면 생략 가능하고 여러개를 넣을 수 있다.

query-params : 추가적인 정보들 입력
- 게시판의 검색 필터 페이징에 많이 사용
- @RequestParam 사용법 (* PathVariable 처럼 이름을 동일하게 하면 자동으로 받아줌.
required, defaultValue 는 옵셔널
Map,MultiValueMap으로 요청받는 방법)

[예시]

@Slf4j
@RestController
public class SampleController {
    @GetMapping("/order/{orderId}")
    public String getOrder(@PathVariable("orderId") String id) {
        log.info("Get some order" + id);
        return "orderId : "+id+ ", order Amount : 1000";
    }

    @DeleteMapping("/order/{orderId}")
    public String deleteOrder(@PathVariable("orderId") String id) {
        log.info("Get some order" + id);
        return "Delete orderId : "+id;
    }

    @GetMapping("/order")
    public String getOrderWithRequestParam(
            @RequestParam("orderId") String id,
            @RequestParam("orderAmount")String amount) {
        log.info("Get order :" + id + " ,amount : " + amount);
        return "orderId : "+id+ ", order Amount : " + amount;
    }

    @PutMapping("/order")
    public String createOrder() {
        log.info("create order");
        return "order created - > orderId:1 , orderAmount:1000";
    }

길이가 긴 Prameter를 받을 때

@RequestBody : http body정보를 편리하게 받을 수 있음.
@RequestHeader : http header 정보를 편리하게 받을 수 있음.
Post, Put, Patch 사용 (사실 셋 다 비슷함..)

[사용예시]

 @PostMapping("/order")
    public String createOrder(
            @RequestBody CreateOrderRequest createOrderRequest,
            @RequestHeader String userAccountId) {
        log.info("Create order :" + createOrderRequest +
                " ,userAccountId : " + userAccountId);
        return "orderId : "+createOrderRequest.getOrderId()+
                ", order Amount : " + createOrderRequest.getOrderAmount();
    }
    @Data //lombok으로 getter setter 자동생성
    public static class CreateOrderRequest{
        private String orderId;
        private Integer orderAmount;
    }

Filter & Interceptor

Spring MVC 에서는 Filter와 Interceptor 가 존재한다.
둘 의 개념은 다소 비슷한 부분이 있으니 차이점부터 알아보고 시작.

|Filter| |-| |Spring 외부의 서블릿에서 제공하는 공통기능 처리| |Spring 내로 요청이 들어오기 전과 요청이 나갈 때 처리 가능| |상대적으로 Low Level 처리가 가능함| |제일 최전방에서 필터링 해주는 곳|
|Interceptor| |-| |Spring 내부에서 제공하는 공통처리 기능| |실제 매핑된 Handler정보를 확인할 수 있음.| |좀더 상세한 조건식 및 세부스펙을 통해 구체적인 시점에 동작이 가능| |AOP와 비교하면, AOP는 인터셉터보다 더 구체적인 조건과 동작위치를 가짐|

Filter Practice
Filter 인터페이스를 구현하여 사용.

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {
        log.info("Hello LogFilter : " + Thread.currentThread());
        chain.doFilter(request, response); // 외부->filter(->처리->)filter->외부
        log.info("Bye LogFilter : " + Thread.currentThread());

    }
}


@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean loggingFilter(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean
                = new FilterRegistrationBean<Filter>();
        filterFilterRegistrationBean.setFilter(new LogFilter());
        filterFilterRegistrationBean.setOrder(1);
        filterFilterRegistrationBean.addUrlPatterns("/payment");

        return filterFilterRegistrationBean;
    }
}

InterCeptor Practice
HandlerInterceptor인터페이스는 필수 override 메서드가 없다.
필요에 따라 overriding하면 된다.

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) throws Exception {
        log.info("preHandle LogInterceptor : " + Thread.currentThread());
        log.info("preHandle handler : " + handler);
        return true;
    }

    @Override
    public void postHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            ModelAndView modelAndView
    ) throws Exception {
        log.info("postHandle LogInterceptor: " + Thread.currentThread());
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex
    ) throws Exception {
        log.info("afterCompletion LogInterceptor: " + Thread.currentThread());
        if(ex!=null){
            log.error("afterCompletion exception : " + ex.getMessage());
        }
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/*","/image/*");
    }

예외처리

예외 : 프로그램이 예상치 못한 상황을 만나을 때 오류를 발생시키는것.

Spring MVC에서 예외를 처리하는 방법 - (REST API)

@ExceptionHandler를 사용하기!

- Controller 기반의 예외처리
- HTTP Status code를 변경하는 방법 (`@ResponseStatus`,`ResponseEntity`)
- 예외처리 우선순위
ㅤㅤ1. 해당 Exception이 정확히 지정된 Handler
ㅤㅤ2. 해당 Exception이 부모 예외 Handler
ㅤㅤ3. 이도 저도 아니면 그냥 Exception(모든예외의 부모)

@ExceptionHandler, @ResponseStatus 사용예시

    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(IllegalAccessException.class)
    public String handleIllegalAccessException(
            IllegalAccessException e){
        log.error("IllegalAccessException is occurred",e);

        return "INVALID_ACCESS";
    }

dto package에 별도의 Errorcode클래스를 두어 Json형식으로 반환하는것이 낫다!

@ExceptionHandler, ResponseEntity 사용예시

 @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(IllegalAccessException.class)
    public ResponseEntity<ErrorResponse> handleIllegalAccessException(
            IllegalAccessException e){
        log.error("IllegalAccessException is occurred",e);

        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .header("newHeader","Some Value")
                .body(new ErrorResponse(
                        "INVALID_ACCESS",
                        "IllegalAccessException is occurred"));
    }

@RestControllerAdvice (전역 예외처리)

- 전역적으로 예외처리를 할 수 있다.
- 스프링 백엔드 개발에서 현재 가장 많이 활용되는 기술이다.
- @ControllerAdvice 와의 차이는
ㅤㅤa.Controller vs RestController 차이와 동일하다.
ㅤㅤb.ControllerAdvice -> 기본적으로 view 응답
ㅤㅤc.RestControllerAdvice -> RestAPI용으로 객체를 응답(Json)
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalAccessException.class)
    public ResponseEntity<ErrorResponse> handleIllegalAccessException(
            IllegalAccessException e){
        log.error("IllegalAccessException is occurred",e);

        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .header("newHeader","Some Value")
                .body(new ErrorResponse(
                        ErrorCode.TOO_BIG_ID_ERROR,
                        "IllegalAccessException is occurred"));
    }

    @ExceptionHandler(WebSampleException.class)
    public ResponseEntity<ErrorResponse> handleWebSampleException(
            WebSampleException e){
        log.error("WebSampleException is occurred",e);

        return ResponseEntity
                .status(HttpStatus.INSUFFICIENT_STORAGE)
                .body(new ErrorResponse(
                        e.getErrorCode(),
                        "IllegalAccessException is occurred"));
    }


    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> otherException(
            Exception e){
        log.error("Exception is occurred",e);

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse(
                        ErrorCode.INTERNAL_SERVER_ERROR,
                        "Exception is occurred"));
    }
}

Transaction

데이터베이스의 상태를 변화시키기 위해 수행하는 작업단위
주로 DB에 문제가 생겼을 때 롤백 시키기 위해 사용된다.
transaction의 속성  
원자성(Atomicity) 모두 반영되거나 모두 반영되지 않는다.
일관성(Consistency) 작업처리결과는 항상 일관적이어야 한다.
독립성(Isolation) 독립적으로 각각의 트랜잭션들이 수행되어야한다.
지속성(Durability) 성공적으로 완료되었을 시 변화된 DB상태가 지속되어야한다.

트랜잭션의 연산 : Commit / Rollback

Commit 모든 작업이 완료되었을때는 커밋 -> 작업 DB에 반영

Rollback 작업 중 일부만 완료되었을때는 롤백 -> 모든 작업 취소

여러 Transaction이 경쟁하면 생기는 문제

1. Dirty Read 발생(커밋되지 않은 데이터를 읽음)
-한 트랜잭션이 다른 트랜잭션이 아직 커밋되지 않은 데이터를 수정했을 때, 해당 데이터를 읽는 현상
Transaction A : Diary Table의 3번째 Row 수정중.
Transaction B : Diary Table의 3번째 Row 조회 시도.

2. Non-Repeatable Read 발생(같은 쿼리를 반복 실행해도 결과가 일관되지 않음)
-한 트랜잭션이 동일한 쿼리를 실행할 때, 다른 트랜잭션이 해당 데이터를 수정하고 커밋했기 때문에 결과가 달라지는 현상
Transaction A : Diary Table의 3번째 Row 조회 2번
Transaction B : Diary Table의 3번째 Row 수정 후 커밋

3. Phantom Read 발생(쿼리 결과에 존재하지 않던 새로운 행이 나타나는 것)
-한 트랜잭션이 동일한 쿼리를 실행할 때, 다른 트랜잭션이 해당 데이터를 삽입, 삭제 또는 수정하여 결과 집합이 달라지는 현상
Transaction A : Diary Table의 0-4번째 Row 조회 2번
Transaction B : Diary Table의 3번째 Row 수정 후 커밋

Spring에서의 transaction

@Transactional annotation을 선언하여 사용하는 것이 일반적이다.
클래스, 메서드 위에 annotations을 붙임으로써 선언할 수 있고, 트랜잭션 기능이 적용된 프록시객체를 생성한다.

Spring Transaction의 세부설정  
Isolation(격리수준) 트랜잭션에서 일관성이 없는 데이터를 허용하는수준
-DEFAULT
-READ_UNCOMMITTED(dirty read 발생)
-READ_COMMITTED (dirty read 방지)
-REPEATABLE_READ(Non-Repeatable Read 방지)
-SERIALIZABLE (Phantom Read 방지)
예시:@Transactional(isolation=Isolation.DEFAULT)
Propagation(전파수준) 트랜잭션 동장 도중 다른트랜잭션을 호출하는 상황
트랜잭션을 시작하거나
기존 트랜잭션에 참여하는 방법에 대해 결정하는 설정값.
-REQUIRED
-SUPPORTS(시작된TRX가 있으면 그안에서 자식TRX포함)
-REQUIRES_NEW(무조건 새로운 TRX생성)
-NESTED(중첩TRX 실행-부모TRX와 독립적으로..)
ReadOnly 트랜잭션을 읽기 전용 속성으로 지정
예시:Transactional(readOnly=true)
Rollback Exception 예외가 발생했을때 트랜잭션 롤백 시킬경우를 설정
@Transactional(isolation=rollbackFor=Exception.class)
@Transactional(isolation=noRollbackFor=Exception.class)
Timeout 일정시간 내에 트랜잭션 끝내지 못하면 롤백
@Transactional(timeout=10)
TOP