일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 불변 객체
- API 개발 고급
- 스프링
- Spring Data JPA
- JPQL
- jpa 활용
- Bean Validation
- 타임리프
- 예제 도메인 모델
- 김영한
- 프로젝트 환경설정
- 벌크 연산
- 실무활용
- QueryDSL
- JPA
- 일론머스크
- 값 타입 컬렉션
- 기본문법
- 타임리프 문법
- 페이징
- 스프링 데이터 JPA
- JPA 활용2
- 로그인
- JPA 활용 2
- 임베디드 타입
- 스프링MVC
- 검증 애노테이션
- 컬렉션 조회 최적화
- 트위터
- 스프링 mvc
- Today
- Total
RE-Heat 개발자 일지
스프링 MVC 1편 - [6] 기본 기능(상편) 본문
출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의
웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -
www.inflearn.com
인프런 김영한님의 스프링 MVC 1편 강의를 듣고 정리한 내용입니다.
[1] 프로젝트 생성
Jar 선택 이유
- JSP가 아닌 Thymeleaf로 작성 예정이라 War가 아닌 Jar 선택.
- Jar를 사용하면 항상 내장 서버(톰캣)를 사용하고 webapp 경로를 사용하지 않음(내장 서버 최적화)
- War도 내장서버 활용이 가능하나, 주로 외부 서버를 이용해 외부 서버에 배포하는 목적으로 사용
Welcome 페이지
/resources/static/ 위치에 index.html 파일을 두면 Welcome 페이지로 처리해 준다.
[2] 로깅 간단히 알아보기
System.out.println() 대신 별도의 로킹 라이브러리를 사용해 로그를 출력. 콘솔뿐만 아니라 파일 생성도 가능
SLF4J(Simple Logging Facade for Java)
: java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리
LogTestController
@Slf4j
@RestController
public class LogTestController {
//private final Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/log-test")
public String logTest(){
String name = "Spring";
System.out.println("name = " + name);
log.trace(" trace my log="+name);
log.trace("trace log={}", name);
log.debug(" debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error(" error log={}", name);
return "ok";
}
}
로그 선언방법
① private Logger log = LoggerFactory.getLogger(getClass()); : slf4j에 있는 것을 import해서 사용하기
② private static final Logger log = LoggerFactory.getLogger(Xxx.class) : slf4j에 있는 것을 import해서 사용하기
③ @Slf4j : 롬복이 제공하는 애노테이션으로 위 코드 대체 가능
@RestController
- @Controller는 뷰 이름을 반환한다. 하지만 REST API 발달로 텍스트, JSON값 등을 리턴해줘야 하는 일이 생기면서 @RestController가 생김.
- @RestController는 반환값으로 뷰를 찾지 않고, HTTP 메시지 바디에 바로 입력함.
로그 출력
로그 출력 포맷 : 시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지
로그 레벨 및 설정 방법
- 로그 레벨 : TRACE > DEBUG > INFO > WARN >ERROR
- 설정방법
logging.level.hello.springmvc = info로 적으면 info 값, debug로 적으면 debug 하위값이 콘솔에 출력
개발 서버는 DEBUG, 운영서버는 INFO 출력이 대세
기본값은 INFO [logging.level.root=info (default값)]
만일 info가 아닌 debug로 하면 스프링 라이브러리 관련 값까지 콘솔창에 다 찍히므로 유의
올바른 로그 사용법
log.trace("trace={}", data) ㅇ
log.trace("trace="+ data) X
이유 : 로그 레벨이 info면 위 로그는 debug이므로 사용 안됨. 그러나 JAVA 특성상 + 연산이 먼저 실행되므로 두 번째 코드는 출력에 관계없이 연산이 시작됨. 리소스 낭비를 막기 위해 첫 번째 방식이 올바름.
로깅의 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 확인할 수 있고, 출력 모양을 조정할 수 있다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 사용할 수 있다.
- 콘솔 외에 파일이나 네트워크 등 별도의 위치에 로그를 남길 수 있다.
- 성능도 System.out보다 수십 배 낫다. 그래서 실무에선 꼭 로그를 사용함.
[3] 요청 매핑
MappingController
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
public String helloBasic() {
log.info("hello basic");
return "ok";
}
/**
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
/**
* 편리한 축약 애노테이션 (코드보기)
*
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mappingGetV2");
return "ok";
}
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
*
* @PathVariable("userId") String userId -> @PathVariable userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
/**
* PathVariable 사용 다중
*/
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "ok";
}
/**
* 파라미터로 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug" (! = )
* params = {"mode=debug","data=good"}
*/
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
/**
* Content-Type 헤더 기반 추가 매핑 Media Type
* consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
/**
* Accept 헤더 기반 Media Type
* produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
}
① 매핑은 배열형태로 URL 다중 설정이 가능하다.
@RequestMapping({"/hello-basic, "hello-go"}) /hello-basic or /hello-go면 호출됨. 단, 배열은 {}로 묶어줘야 함.
② 매핑할 때 HTTP 메서드(GET, HEAD, POST, PUT, PATCH, DELETE)를 지정할 수 있음
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET) 여기에 POST 요청을 하면
스프링이 HTTP 405 상태코드(Method Not Allowed)를 반환함.
③ ②번을 축약해서 사용할 수 있음 (@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping)
④ @PathVariable로 경로 변수 사용
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data){
=> userId는 식별자 부분
⑤ @PathVariable과 파라미터의 이름이 같으면 생략 가능
public String mappingPath(@PathVariable String userId){
⑥ @PathVariable 다중사용도 가능
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
orderId) {
⑥ 특정 파라미터 조건 매핑
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
http://localhost:8080/mapping-param?mode=debug
mode=debug가 넘어와야 가능. 실질적으로 잘 쓰이진 않음
⑦ 특정 헤더 조건 매핑
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
헤더도 조건 파라미터처럼 사용가능
⑧ 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
HTTP 요청 메시지의 Content-Type헤더를 기반으로 타입 매핑.
⑨ 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)
HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
"applicaton/json", "text/html"등 직접 쓰는 방식도 있으나, MediaType의 변수명으로 하면 오타가 날 확률도 없어 바람직하다.
[4] 요청 매핑 - API 예시
- 회원 관리 API
회원 목록 조회: GET /users
회원 등록: POST /users
회원 조회: GET /users/{userId}
회원 수정: PATCH /users/{userId}
회원 삭제: DELETE /users/{userId}
MappingClassController
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
@GetMapping
public String user(){
return "get users";
}
@PostMapping
public String addUser(){
return "post users";
}
@GetMapping("/{userId}")
public String findUser(@PathVariable("userId") String userId){
return "get userId=" + userId;
}
@PatchMapping("/{userId}")
public String updateUser(@PathVariable("userId") String userId){
return "update userId=" + userId;
}
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable("userId") String userId){
return "delete userId=" + userId;
}
}
①클래스 위에 @RequestMapping("/mapping/users")를 작성하면 메소드에 "/mapping/users" 생략 가능
ex) @Getmapping("/{userId}") == @Getmapping("/mapping/users/{userId}) 앞 상위 경로가 생략된 것.
② 같은 URL + HTTP 메서드(GET, POST, PATCH, DELETE 등)로 구분하는 게 깔끔하다.
[5] HTTP 요청 - 기본, 헤더 조회
HTTP 헤더 조회하는 방법
RequestHeaderController
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "myCookie", required = false) String cookie
){
log.info("request={}", request);
log.info("response={}", response);
log.info("httpMethod={}", httpMethod);
log.info("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return "ok";
}
}
① 스프링은 @Controller 파라미터에 다양한 방식을 지원함.
Locale: 지역, 선호언어
MultiValueMap<> : 하나의 키에 여러 값을 받을 때 사용
ex) userId=1&userId=2
@RequestHeader("host") String host : 헤더 이름을 지정하면 해당 헤더값만 가져 옴
@CookieValue(value="쿠키명", 속성) : 헤더 Cookie값 조회
참고 사이트
@Controller 파라미터 목록 : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html
@Controller 리턴값 목록 : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/return-types.html
[6] HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
HTTP 요청 메시지를 통해 클라이언트에서 서버로 전달하는 3가지 방법① GET- 쿼리 파라미터 : ex) /url?userId=1&age=30
② POST - HTML Form : 메시지 바디에 쿼리파라미터 형식으로 전달
③ HTTP message body에 데이터를 직접 담아 요청
스프링으로 요청파라미터 조회하는 방법
RequestParamController
@Slf4j
@Controller
public class RequestParamController {
@RequestMapping("/request-param-v1")
public void requestParmaV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge
) {
log.info("username={}, age={}", memberName, memberAge);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
@RequestParam String username,
@RequestParam int age
) {
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username, // 반드시 있어야 함.
@RequestParam(required = false) Integer age) {
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
@RequestParam(required = true, defaultValue = "guest") String username, // 반드시 있어야 함.
@RequestParam(required = false, defaultValue = "-1") int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
return "ok";
}
}
① HttpServletRequest의 getParameter()로 데이터를 조회하는 방식
② @RequestParam
1] @RequestParam을 사용해 요청 파라미터를 조회하는 방식
2] 파라미터명과 변수명이 가능할 경우 파라미터명 생략 가능
3] String, int, Integer 등 단순 타입이면 @RequestParam도 생략 가능.
영한님 말씀 : 하지만, 코드를 읽을 때 명확하게 구분하기 위해 @ReuqestParam을 써주는 게 바람직
③ @RequestParam(required=true)
request에 해당 파라미터가 있어야 하는지 여부. default값은 true. 디폴트값이 true인데 파라미터가 없으면 400 예외 발생
☞ 주의사항 : 빈문자("")와 null값은 다르다. 빈문자를 넣으면 디폴트값이 true여도 빈문자로 통과
④ @RequestParam(defaultValue="값")
빈문자나 null값이 올 경우 디폴트로 작성한 값을 넣어준다. 그래서 required=true or false가 사실상 필요 없음.
⑤ @RequestParam Map<String, Object> paramMap
파라미터 값을 받은 후 Map으로 꺼내쓸 수 있음.
단, 파라미터 값이 하나라면 Map을 써도 되나 그렇지 않다면(?userId=1&userId=2) MultiValueMap을 써야 함.
[7] HTTP 요청 파라미터 - @RequestParam
@RequestParam을 사용하면 파라미터를 매우 편리하게 사용할 수 있다.
RequestParamController-V2
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge
) {
log.info("username={}, age={}", memberName, memberAge);
return "ok";
}
@Requestparam : 파라미터의 이름으로 바인딩
@ResponseBody : return값을 메시지 바디에 담아 보냄.
RequestParamController-V3
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
@RequestParam String username,
@RequestParam int age
) {
log.info("username={}, age={}", username, age);
return "ok";
}
HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name=”xxx”) 생략 가능
RequestParamController-V4
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
String, int, Integer 등 단순 타입이면 @RequestParam도 생략 가능.
영한님 견해 : @Requestparam이 있으면 명확하게 요청 파라미터에서 데이터를 읽는다는 것을 이해할 수 있으므로 V4보단 V3이 바람직하다.
RequestParamController: param-required
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username, // 반드시 있어야 함.
@RequestParam(required = false) Integer age) {
log.info("username={}, age={}", username, age);
return "ok";
}
@RequestParam(required=true) 파라미터 필수 여부. 넘어오지 않으면 400 예외 발생.
참고 : int에 null문자 불가능해 Integer로 받음.
주의!!! 빈문자 “”와 null은 다르다.
RequestParamController: param-default
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
@RequestParam(required = true, defaultValue = "guest") String username, // 반드시 있어야 함.
@RequestParam(required = false, defaultValue = "-1") int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
빈 문자나 null값이 올 경우 defaultValue값으로 자동 할당.
RequestParamController: param-map
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
return "ok";
}
Map으로도 하나하나 꺼내쓸 수 있다.
파라미터 값이 1개가 아니면 Map 대신 MultiValueMap을 써야 함.
'백엔드 > 스프링' 카테고리의 다른 글
스프링 MVC 1편 - [7] 웹 페이지 만들기 [상편] (0) | 2023.07.05 |
---|---|
스프링 MVC 1편 - [6] 기본 기능(하편) (0) | 2023.07.02 |
스프링 MVC 1편 - [5] 스프링 MVC 구조 이해 (0) | 2023.06.29 |
스프링 MVC 1편 - [4] MVC 프레임워크 만들기 (0) | 2023.06.23 |
스프링 MVC 1편 - [3] 서블릿·JSP·MVC 패턴 (0) | 2023.06.22 |