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