| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
| 31 |
- side project
- Sts
- 클래스 class
- Java
- GIT
- Algorism study
- AWS
- 내일배움카드
- MVC
- group study
- #패스트캠퍼스 #국비지원교육 #메가바이트스쿨 #MegabyteSchool #개발자취업부트캠프 #내일배움카드
- 패스트캠퍼스
- Spring
- github
- 게시판 리뷰 만들기
- 개발자취업부트캠프
- crud
- #javaStudy
- MegabyteSchool
- 게시판 만들기
- spring boot
- 국비지원교육
- tomcat
- View
- 클래스 상속
- 메가바이트스쿨
- Interface
- MVC 패턴
- Entity
- array
- Today
- Total
tuter77
2023.01.10 TIL 본문
<목차>
<공부계획표>
13:00 ~ 14:00 그룹스터디
mvc패턴 학습 및 get(조회), post(저장) 페이지 구현
14:00 ~ 18:00 (3시간) 온라인강의 시청.
14:00 ~ 15:00 Spring 강의 시청
15:00 ~ 16:00 Spring 강의 시청
17:00 ~ 18:00 Spring 강의 시청
18:00 ~ 19:00 식사시간
19:00 ~ 22:00 온라인 강의 시청
19:00 ~ 20:00 그룹스터디 과제 공부
20:00 ~ 21:00 그룹스터디 과제 공부
21:00 ~ 22:00 그룹스터디 과제 공부
22:00 ~ 23:00 블로그 작성 및 프로그래머스 문제 풀기.
<Spring>
● HTTP 요청과 응답
▷ 텍스트 파일 vs 바이너리 파일
- 텍스트 파일 : 문자만 저장되어있는 파일로 숫자를 문자로 변환 후 쓴다.
- 바이너리 파일 : 문자와 숫자가 저장되어있는 파일로 데이터를 있는 그대로 읽고 쓴다.
( 메모장에 넣었을 때 못읽으면 바이너리 파일이다.)
- 텍스트 파일의 숫자를 문자로 변환하는 경우, 유리한 것과 불리한 것이 공존한다.
(예로들면 int를 문자로 변환하면, 4byte의 int가 2byte로 사이즈가 줄어든다. float 숫자는 6byte의 문자로 사이즈가 커진다.)
▷MIME(Multipurpose Internet Mail Extensions)
- 텍스트 기반 프로토콜에 바이너리 데이터를 전송하기 위해 고안된 것이다.
- HTTP의 경우 이미지, 동영상도 첨부가 가능한 것은 MIME을 통해 전송할 데이터의 타입을 명시해서이다.(Content-Type 헤더에 사용한다.)
예시)
| 타입 | 설명 | MIME타입 예시(타입/서브타입) |
| image | 모든 종류의 이미지 | image/bmp, image/webp |
| video | 모든 종류의 비디오 파일 | video/webm, video/ogg |
위 타입/서브타입은 response.setContentType("text/html") 예시와 같은 것이다.
- 실습
Postman 에서 getYoil로 url을 입력하고 파라미터값에 year는 text로 month 대신 img 파일을 넣으면, preview를 통해 어떻게 요청을 하는지 대략적으로 볼 수 있다.


이 Preview를 보면 맨위부터 빈줄까지는 Header고 그 다음 코드는 Body로 year가 먼저 나온다. 이는 아래의 2021로 텍스트로 되어있다.
2021 이후의 코드가 바이너리 데이터로 img파일을 요청하는 것을 알 수 있다.
----WebKitFormBoundaryE19zNvXGzXaLvS5C 이 구문이 boundary로 경계라고 할 수 있다.이 경계로 text 데이터와 binary 데이터를 구분해서 요청한다.
상단의 Limitation에 마우스 커서를 가져다대면 chrome에서 자동제공되는 헤더들을 볼 수 없다는 내용이 나온다.
예제1)
package com.fastcampus.ch2;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RequestHeader {
@RequestMapping("/requestHeader")
public void main(HttpServletRequest request) {
Enumeration<String> e = request.getHeaderNames();
while (e.hasMoreElements()) {
String name = e.nextElement();
System.out.println(name + ":" + request.getHeader(name));
}
}
}
RequestHeader라는 프로그램을 새로 만들어 실행해보면, 콘솔창에서 아래와 같은 header를 볼 수 있다.

위 이미지는 결과가 나온 콘솔창으로 헤더내용을 모두 볼 수 있다.
getHeaderNames() 라는 메서드를 반복문을 이용해 콘솔창에 하나씩 찍은것이다.
예제2)
package com.fastcampus.ch2;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RequestMessage {
@RequestMapping("/requestMessage")
public void main(HttpServletRequest request) throws Exception {
// 1. request line
String requestLine = request.getMethod(); // GET 또는 POST
requestLine += " " + request.getRequestURI(); // /ch2/requestMessage
String queryString = request.getQueryString(); // year=2021&month=10&day=1
requestLine += queryString == null ? "" : "?"+queryString;
requestLine += " " + request.getProtocol(); // HTTP/1.1
System.out.println(requestLine);
// 2. request headers
Enumeration<String> e = request.getHeaderNames();
while (e.hasMoreElements()) {
String name = e.nextElement();
System.out.println(name + ":" + request.getHeader(name));
}
// 3. request body - POST일 때만 해당, GET은 body가 없음(CONTENT_LENGTH=0)
final int CONTENT_LENGTH = request.getContentLength();
// System.out.println("content length="+CONTENT_LENGTH);
if(CONTENT_LENGTH > 0) {
byte[] content = new byte[CONTENT_LENGTH];
InputStream in = request.getInputStream();
in.read(content, 0, CONTENT_LENGTH);
System.out.println(); // empty line
System.out.println(new String(content, "utf-8")); // year=2021&month=10&day=1
} // if
} // main
}
이번엔 header 뿐 아니라, 메세지 전체를 볼 수 있게한 코드문이다.
getRequestURI를 통해 uri를 가져올 수 있다.


각각 get, post 메세지 내용들을 다 볼 수 있다.
● 관심사의 분리, MVC 패턴 - 이론
▷ 관심사의 분리(Separation of Concerns)
YoilTeller 예시를 보면 입력, 요일계산, 계산한 결과 출력으로 세 파트로 나뉜다.
각 파트들을 관심사(Concern)이라고 한다.(해야할 작업)
- oop 5대 설계원칙(SOLID) 중 SRP
SRP : 단일 책임의 원칙 > 하나의 메서드는 하나의 책임(관심사)만 져야한다.
때문에 YoilTeller의 경우 3개의 관심사를 갖고있어서 각 관심사를 분리해야한다.
이 분리는 관심사의 분리, 변하는 것/(자주)변하지 않는 것의 분리, 공통(중복)코드의 분리로 종류를 나눌 수 있다.
@Controller
public class YoilTeller {
// public static void main(String[] args) {
@RequestMapping("/getYoil")
public void main(HttpServletRequest request, HttpServletResponse response) throws IOException{
//년월일을 입력하면 요일을 알려주는 프로그램
//1. 입력.
String year = request.getParameter("year");
String month = request.getParameter("month");
String day = request.getParameter("day");
int yyyy = Integer.parseInt(year);
int mm = Integer.parseInt(month);
int dd = Integer.parseInt(day);
// 2. 작업
Calendar cal = Calendar.getInstance();
cal.set(yyyy, mm - 1, dd );
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
char yoil = " 일월화수목금토".charAt(dayOfWeek);
//3. 출력
response.setContentType("text/html"); //텍스트
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();// response 객체에서 브라우저로의 출력 스트림을 얻는다.
out.println(year + "년 " + month + "월 " + day + "일은 ");
out.println(yoil + "요일입니다.");
}
}
(강사님은 학습자 수준에 설계부터하기엔 너무 벅차기때문에 코드를 만들고 OOP 설계원칙에 해당하는지를 살펴보며 수정하는 방향으로 알려주신다고 했다.)
▷ 공통 코드의 분리 - 입력의 분리
- 각 기능들을 입력, 처리, 출력 3가지로 contorller를 분리하면 입력기능(request.gerParameter())이 중복되게 되는데, 이 입력을 하나로 모으고 각 컨트롤러에는 해당 기능에 맞는 처리, 출력만 남긴다.
@RequestMapping("/getYoil")
public void main(String year, String month, String day, HttpServletResponse response) throws IOException{
int yyyy = Integer.parseInt(year);
int mm = Integer.parseInt(month);
int dd = Integer.parseInt(day);
위와 같이 매개변수를 개별 매개 변수로 사용함으로써 getParameter를 사용하지 않게되어 분리할 수 있다.
이 각 매개변수들은 숫자기 때문에 아래와 같이 더 간결하게 나타낼 수 있다.
@RequestMapping("/getYoil")
public void main(int year, int month, int day, HttpServletResponse response) throws IOException{
// 아래는 처리구문.
각 String 값들이 int로 자동변환되어 Integer.parsInt()를 사용한 코드도 지워줄 수 있다.
이제 입력 관심사는 사라졌다.
이후 처리와 출력을 분리하려하는데 앞서 입력값(year, month, day)들은 기존의 구문에서 같은 메소드 내에 있기 때문에 바로 접근이 가능하지만, 분리되고 나면 접근이 되지 않는다.
때문에 Model model 객체가 필요하다.
이 객체를 만들어두고, 처리 완료된 결과값들을 객체에 저장하고 출력부분에 model객체를 전달하면된다.
이런식으로 처리하는 방식을 MVC 패턴이라고 한다.
관심사가 다른 처리부는 Controller, 출력부는 View로 분리하고, 이 둘 간의 데이터는 Model 객체를 통해 주고 받는다.

그림으로 보면 위와 같이 분리된 것을 알 수 있다.
아직은 DB와 Service 등에 대해 제대로 배우진 않았지만, 스터디그룹을 통해 배운것으로 이해하자면, 브라우저에서 컨트롤러로 요청을 보내고, 해당 정보는 서비스로 전달된다.
서비스 인터페이스의 구현체인 impl 파일에서 기능이 구현되어 레포지토리로 요청이 전달되고, 레포지토리는 서비스의 기능을 따라 데이터베이스에서 요청된 데이터를 가져와 뷰로 전달하고 이 값들은 컨트롤러에 의해 브라우저로 전달된다.
사실 이 그림이 온전히 알아보지 못하겠어서 단순한 예시를 가져왔다.

단순한 예시를 보면,
위 컨트롤러에서 요청을 받는 것은 DispatcherServlet이라고 하는데, 입력을 처리해서 컨트롤러로 전달한다.
DispatcherServlet(입력)에서 만들어진 model객체는 처리부(Controller)와 정보를 주고받는데 쓰인다.
Controller에서 처리된 결과값은 다시 DispatcherServlet의 Model객체 내에 저장되고 해당 모델을 View로 전달한다.
그러면 View에서는 작업결과를 읽어내 최종응답을 생성해서 브라우저로 전송한다.
이렇게 분리된 getYoil은 아래와 같다.
import java.io.IOException;
import java.util.Calendar;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class YoilTellerMVC {
@RequestMapping("/getYoilMVC") //?year=2022&month=5&day=1
public String main(int year, int month, int day, Model model) throws IOException{
//1. 유효성검사.
if(!isValid(year, month, day))
return "yoilError"; //유효하지 않으면, /Error.jsp로 이동.
//2. 처리 - 요일 계산
char yoil = getYoil(year, month, day);
//3. Model에 작업 결과 저장.
model.addAttribute("year", year);
model.addAttribute("month", month);
model.addAttribute("day", day);
model.addAttribute("yoil", yoil);
//4. 작업결과를 보여줄 View의 이름을 반환
return "yoil"; // yoil.jsp
}
private boolean isValid(int year, int month, int day) {
return false;
}
private char getYoil(int year, int month, int day) {
Calendar cal = Calendar.getInstance();
cal.set(year, month - 1, day);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return " 일월화수목금토".charAt(dayOfWeek);
}
}
컨트롤러에서 읽기부분을 확 축약하고, 아래 처리부는 메서드를 private로 선언하여 분리했다.
이 분리된 처리부는 한 줄의 코드로 호출하여 사용하고, 아래에 모델 객체에 각 값들을 저장한 뒤 출력부인 yoil.jsp 파일을 반환해준다.
혹시 에러가 날 경우를 가정하여, 맨 처음에 유효성검사 항목을 if문으로 넣어 에러가 난경우 error.jsp 파일을 반환해주도록 했다.
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>YoilTellerMVC</title>
</head>
<body>
<h1>${year}년 ${month}년 ${day}일은 ${yoil}요일 입니다.</h1>
</body>
</html>
여기가 출력부인 yoil.jsp이다.
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<!DOCTYPE html>
<html>
<head>
<title>YoilTellerMVC</title>
</head>
<body>
<h1>잘못된 요청입니다. 다시 요청해 주세요.</h1>
<h1>year, month, day를 모두 올바른 값으로 입력하셔야 합니다.</h1>
</body>
</html>
여기는 에러가 날 경우 브라우저로 반환할 다른 출력부인 yoilError.jsp 파일이다.
Controller에서 main 함수호출이 String임을 볼 수 있는데, 이는 반환할 출력부(View)가 텍스트로 되어있기 때문이다.
다른 방법으로 String을 void로 바꾸어, View의 이름을 반환하지 않는 방법도 있다.
@RequestMapping("/getYoilMVC") //?year=2022&month=5&day=1
public void main(int year, int month, int day, Model model) throws IOException{
..
// return "yoil"; // yoil.jsp
이 처럼 반환타입을 안줄 경우, 위의 매핑된 주소에 해당하는 JSP파일이 바로 출력된다.(일반적으로 사용 잘 안함)
또 다른 방법으로는 void 대신 ModelAndView 를 사용한 방법이 있다.
public class YoilTellerMVC {
@RequestMapping("/getYoilMVC") //?year=2022&month=5&day=1
public ModelAndView main(int year, int month, int day) throws IOException{
ModelAndView mv = new ModelAndView();
// //1. 유효성검사.
// if(!isValid(year, month, day))
// return "yoilError"; //유효하지 않으면, /Error.jsp로 이동.
//2. 처리 - 요일 계산
char yoil = getYoil(year, month, day);
//3. Model에 작업 결과 저장.
mv.addObject("year", year);
mv.addObject("month", month);
mv.addObject("day", day);
mv.addObject("yoil", yoil);
//4. 작업결과를 보여줄 View의 이름을 반환
// return "yoil"; // yoil.jsp
// 4'. 결과를 보여줄 view를 지정.
mv.setViewName("yoil");
return mv;
}
이와 같이 ModelAndView를 사용하면, 기존의 medel 객체를 빼주고 생성자로 새로 생성한다.
이 후 새로 생성한 mv라는 객체로 ModelAndView의 addObject메서드를 사용해 각 값들을 저장하고 결과를 보여줄 View를 지정하여 반환한다.
기존의 모델 객체를 사용할 때와의 차이점을 알아보면, 기존 방법에는 DispatcherServlet이 Model 객체를 생성했었다.
이 매개변수로 선언하지 않으면, DispatcherServlet이 객체를 생성하지 않는다.
대신 아까 본것처럼 컨트롤러 내에서 생성자의 인스턴스로 생성하게 된다.
이 인스턴스를 활용하여 객체 값과 viewName이 DispatcherServlet에 전달되게 되는 것이다.
이 방식 역시 잘 사용하지 않는다.
▷ 정리
MVC 패턴에서 컨트롤러 메서드의 반환타입은 세가지 종류가 있다.1. [String] 뷰 이름 반환.2. [void] 매핑된 url의 끝단어가 뷰 이름으로 반환.3. [ModelAndView] Model과 뷰 이름을 반환.
<Group Study>
● 연관관계 엔티티 피드백 및 다음 과제
▷ 연관관계 엔티티
Review와 healthInfo 엔티티를 봤을때, 팀장님이 내가 작성한 엔티티에서 List를 활용한 구문을 짚어주셨다.
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "healthInfo")
private List<Review> reviews = new ArrayList<>();
이 구문에서 로딩방식을 Lazy로 적용한 것과 엔티티의 상태변화를 적용할 cascade를 활용한 점이 좋았다고 했다.
또한 아래의 List로 생성한 이유를 아느냐고 했는데, 나는 이유를 몰랐었다.
설명해주신 부분은 댓글이 없는 경우에 null에러 예외를 방지하고자 이렇게 선언한다고 한다.
전반적으로 코드가 깔끔하게 짜여있다고 칭찬받았다.
▷다음 과제
기존의 healthInfo를 활용해 get, post mapping으로 정보를 가져오거나 저장할 수 있는 페이지를 구현하라는 과제를 받았다.
특히 저장하는 과정에서 정보를 조회하는 페이지 > 저장페이지 > 다시 조회 페이지로 재요청을 보내도록 하는것을 PRG패턴이라고 했는데 이 방식을 적용하라고 팁을 주셨다.
또한 이 과제에서 MVC 패턴을 학습하기를 바란다고 하셨는데, 일단은 Spring강의를 통해 MVC 패턴에 대해 좀 알아보아야 한다.
정리하면 MVC 패턴을 학습하기 위해 Html 파일을 두 가지를 만들고 (데이터 조회용, 저장용) 해당 페이지의 기능을 구현할 HomeController를 만들면 된다.
참조: https://www.researchgate.net/figure/The-Spring-MVC-architecture-as-depicted-in-16_fig5_349049076
'TIL' 카테고리의 다른 글
| 2023.01.12 TIL (0) | 2023.01.12 |
|---|---|
| 2023.01.11 TIL (0) | 2023.01.11 |
| 2023.01.09 TIL (0) | 2023.01.09 |
| 2023.01.06 TIL (0) | 2023.01.06 |
| 2023.01.05 TIL (0) | 2023.01.05 |