MVC1 FINAL Step

최보현·2022년 8월 2일
0

MVC

목록 보기
7/18
post-thumbnail

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - sec07
출처 : 스프링 MVC 1편

실제 웹 서버 만들기

서비스 제공 흐름


요구사항이 정리되고 디자이너, 웹 퍼블리셔, 백엔드 개발자가 업무를 나누어 진행한다.

  • 디자이너: 요구사항에 맞도록 디자인하고, 디자인 결과물을 웹 퍼블리셔에게 넘겨줌
  • 웹 퍼블리셔: 다자이너에서 받은 디자인을 기반으로 HTML, CSS를 만들어 개발자에게 제공
  • 백엔드 개발자: 디자이너, 웹 퍼블리셔를 통해서 HTML 화면이 나오기 전까지 시스템을 설계하고, 핵심 비즈니스 모델을 개발 이후 HTML이 나오면 이 HTML을 뷰 템플릿으로 변환해서 동적으로 화면을 그리고 웹 화면의 흐름을 제어함

HTML

정적 리소스를 우리는 /resources/static 폴더에 넣어둔다
하지만, 여기에 넣어두면 실제 서비스에서도 HTML이 공개됨 => 서비스를 운영할 때 지금처럼 공개할 필요없는 HTML은 넣어 두는 것을 고려해봐야 함

타임리프

타임리프는 순수 HTML 파일을 웹 브라우저에서 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있음 ↔️ JSP는 오직 서버를 통해서 열어야 함

우선 하나 알아두고 가기
@RequiredArgsConstructor
final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어줌

public BasicItemController(ItemRepository itemRepository) {
	this.itemRepository = itemRepository;
}
  • 생성자가 딱 1개만 있으면 스프링이 해당 생성자에 @Autowired 로 의존관계를 주입해 줌
  • final 키워드를 빼면 안된다! unless, ItemRepository 의존관계 주입이 안안 됨

테스터용 데이터 추가

@PostConstruct는 해당 빈의 의존관계가 모두 주입되고 나면 초기화 용도로 호출되는 기능을 가지고 있음 -> 이 친구를 활용하면 됨

타임리프 사용 방법

뷰 템플릿 영역에 html을 만들고 타임리프화 해줘야 함

타임리프 사용 선언

<html xmlns:th="http://www.thymeleaf.org">

타임리프 핵심

  • th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체 => 없으면 기존 html의 xxx 속성이 그대로 사용됨
  • HTML을 파일로 직접 열었을 때, th:xxx 가 있어도 웹 브라우저는 th: 속성을 알지 못하므로 무시
  • HTML을 파일 보기를 유지하면서 템플릿 기능도 가능

URL 링크 표현식 - @{...},

th:href="@{/css/bootstrap.min.css}"
@{...} : URL 링크를 사용하는 경우 사용되며 이것을 URL 링크 표현식이라 함

  • URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함함

URL 링크 표현식2 - @{...},

th:href="@{/basic/items/{itemId}(itemId=${item.id})}"

  • URL 링크 표현식을 사용하면 경로를 템플릿처럼 편리하게 사용 가능
  • 경로 변수( {itemId} ) 뿐만 아니라 쿼리 파라미터도 생성
    예) th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
    생성 링크: http://localhost:8080/basic/items/1?query=test

URL 링크 간단히

th:href="@{|/basic/items/${item.id}|}"
상품 이름을 선택하는 링크를 확인봐야함
리터럴 대체 문법을 활용해서 간단히 사용 가능

속성 변경 - th:onclick

다른 html로 이동
th:onclick="|location.href='@{/basic/items/add}'|"
여기에는 다음에 설명하는 리터럴 대체 문법이 사용됨

속성 변경 - th:value

th:value="${item.id}"
모델에 있는 item 정보를 획득하고 프로퍼티 접근법으로 출력 ( item.getId() )
value 속성을 th:value 속성으로 변경

속성 변경 - th:action

  • HTML form에서 action 에 값이 없으면 현재 URL에 데이터를 전송
  • 상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고 HTTP 메서드로 두 기능을 구분
    ex)
    상품 등록 폼: GET /basic/items/add
    상품 등록 처리: POST /basic/items/add
    => 이렇게 하면 하나의 URL로 등록 폼과, 등록 처리를 깔끔하게 처리 가능
리터럴 대체 - |...|

|...| : 이렇게 사용한다.

  • 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 함
    <span th:text="'Welcome to our application, ' + ${user.name} + '!'">
    하지만 리터럴 대체 문법을 사용하면, 더 편리하게 사용할 수 있음
    <span th:text="|Welcome to our application, ${user.name}!|">

반복 출력 - th:each

<tr th:each="item : ${items}">

  • 모델에 포함된 items 컬렉션 데이터가 item 변수에 하나씩 포함되고, 반복문 안에서 item 변수를 사용할 수 있음
  • 컬렉션의 수 만큼 .. 이 하위 테그를 포함해서 생성된다.

변수 표현식 - ${...}

<td th:text="${item.price}">10000</td>

  • 모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있음
  • 프로퍼티 접근법을 사용함 ( item.getPrice() )

내용 변경 - th:text

<td th:text="${item.price}">10000</td>

  • 내용의 값을 th:text 의 값으로 변경한다.

조건문 - th:if

해당 조건이 참이면 실행

쿼리 파라미터 조회 - ${param. *}

파라미터는 자주 사용해서 타임리프에서 직접 지원함

@ModelAttribute

요청 파라미터 처리

@ModelAttribute 는 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해줌

Model 추가

모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어줌

  • 모델에 데이터를 담을 때는 이름은 @ModelAttribute 에 지정한 name(value) 속성을 사용 만약, @ModelAttribute 의 이름을 다르게 지정하면 다른 이름으로 모델에
    포함됨
    ex)
    @ModelAttribute("hello") Item item 이름을 hello 로 지정
    model.addAttribute("hello", item); 모델에 hello 이름으로 저장

ModelAttribute 이름 생략

이름을 생략하면 모델에 저장될 때 클래스명을 사용 => 클래스의 첫글자만 소문자로 변경해서 등록한다.
예) @ModelAttribute 클래스명 모델에 자동 추가되는 이름 Item -> item

전체 생략

@ModelAttribute 자체도 생략 가능 => 대상 객체는 모델에 자동 등록됨

리다이렉트

스프링은 redirect:/... 으로 편리하게 리다이렉트를 지원
ex) redirect:/basic/items/{itemId}

  • 컨트롤러에 매핑된 @PathVariable 의 값은 redirect 에도 사용 가능
  • redirect:/basic/items/{itemId} 에서 {itemId} 는 @PathVariable Long itemId 의 값을 그대로 사용

PRG(POST, Redirect, GET)

코드 addItemV1~V4까지를 보면 웹 브라우저의 새로고침을 계속 할 경우 중복등록되는 것을 확인 할 수 있음

  • 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송
  • 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송 => 이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 됨
    즉, 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 됨

그래서 그 해결방법이 PRG

  • 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트를 호출해주면 됨
  • 웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동 => 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 됨
    결과) 이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제 해결

    주의해야할 점
    "redirect:/basic/items/" + item.getId() redirect에서 +item.getId() 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험 => RedirectAttributes 를 사용해야 함

RedirectAttributes

URL 인코딩도 해주고, pathVarible , 쿼리 파라미터까지 처리해준다.
redirect:/basic/items/{itemId}
pathVariable 바인딩: {itemId}
나머지는 쿼리 파라미터로 처리: ?status=true

profile
Novice Developer's Blog

0개의 댓글