[Spring MVC 1편] 7. 스프링 MVC 활용

HJ·2022년 8월 19일
0

Spring MVC 1편

목록 보기
7/8

김영한 님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard


1. 주의사항

@Getter
@Setter
public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;
}
  • @Data를 핵심 도메인 모델에 사용하는 것은 좋지 않다

    • @Getter, @Setter 정도만 사용하는 것이 좋음

    • DTO의 경우에는 @Data를 사용해도 괜찮다

  • int 대신 Integer를 사용한 이유

    • int는 값이 0이라도 들어가야함

    • 가격이나 수량이 아직 없을 수도 있기 때문에 null을 받을 수 있도록 Integer로 작성


@Repository
public class ItemRepository {

    private static final Map<Long, Item> store = new HashMap<>();
    private static long sequence = 0L;
}
  • @Repository가 붙어있으므로 컴포넌트 스캔의 대상이 된다

  • 동시성 문제 때문에 HashMap<>을 사용하면 안되고 ConcurrentHashMap<>을 사용하는 것이 좋다

  • 위와 같은 이유로 long 대신 AtomicLong을 사용하는 것이 좋다


public class Item {
    public void update(Long itemId, Item updateParam) {
        Item findItem = findById(itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }
}
  • updateParam으로 Item객체를 사용하는 것보다 DTO를 만들어서 사용하는 것이 좋음

    • id를 제외한 나머지 부분이 변경되는데 Item 객체로 만들어버리면 id를 사용하지 않기 때문
  • DTO를 id를 제외한 나머지 3개의 필드를 갖도록 만든다




2. Thymeleaf

<html xmlns:th="http://www.thymeleaf.org">
  • thymeleaf 사용 선언

    • th 사용 가능

<link th:href="@{/css/bootstrap.min.css}"
         href="../css/bootstrap.min.css" rel="stylesheet">
  • thymeleaf view template을 거치게 되면 thymeleaf가 XXX를 th가 붙은 th:XXX로 변경

    • th:XXX 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체

    • th:XXX가 없는 경우, 기존 html의 xxx 속성이 그대로 사용

    • th:XXX만 존재하고, html의 XXX가 없는 경우, XXX를 생성해서 값을 넣는다

    • 대부분의 HTML 속성을 th:XXX 로 변경할 수 있다

  • 기본은 href의 경로가 실행되지만 view template을 거치면 th:href이 지정된 경로로 변경

    • 즉, HTML을 그대로 볼 때는 href가 사용되고 view template을 거치면 th:href의 값이 href로 대체되면서 동적으로 변경

<td th:text="${모델명.필드}">기존내용</td>

<td th:text="${반복변수.필드}">기존내용</td>
  • ${ } : 변수 표현식

    • 모델에 포함된 값이나, thymeleaf 변수로 선언한 값을 조회
  • 기존내용을 ${반복변수.필드}의 값으로 변경


<tr th:each="반복변수 : ${모델명}">
  • 모델에 포함된 데이터가 반복변수 하나씩 포함된다

  • 반복문 안에서 ( 태그 내부에서 ) 반복변수를 사용할 수 있다

  • 데이터 수 만큼 <tr> .. </tr>이 하위 태그를 포함해서 생성된다

  • 자바의 for-each문과 동일한 형식인듯


<button onclick="location.href='addForm.html'"
        th:onclick="|location.href='@{/basic/items/add}'|">
</button>
  • 리터럴 대체 : | |

    • thymeleaf에서 문자와 표현식은 분리되어 있어서 + 를 사용해야 하는데 | |를 사용하면 분리해서 사용하지 않아도 된다

    • 자바스크립트의 ` ` 기능인듯 하다

  • ex>

    • 사용 전 : <span th:text="'Welcome to our application, ' + ${user.name} + '!'">

    • 사용 후 : <span th:text="|Welcome to our application, ${user.name}!|">


<a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">상품id</a>
  • @{ } : URL 링크 표현식 ( thymeleaf에서 링크를 걸 때 사용 )

    • 경로 변수와 쿼리 파라미터 지정이 가능
  • 링크 안에 모델의 값으로 경로 변수를 사용하려면 ( ) 안에 지정해줘야함

    • "@{/basic/items/{itemId}(itemId=${item.id})}"
  • query 속성을 사용해 쿼리 파라미터 생성 가능

    • ex> th:href="@{/items/{itemId}(itemId=${item.id}, query='test')}"

    • 생성 링크 : http://localhost:8080/items/1?query=test

  • 리터럴 대체 문법을 활용해서 아래처럼 작성 가능

    • th:href="@{|/basic/items/${item.id}|}"

<h2 th:if="${param.status}" th:text="'저장 완료!'"></h2>
  • th:if : 조건이 참이면 실행

  • ${param.파라미터 이름} : 쿼리 파라미터를 조회하는 기능

  • 위의 경우, ?status=true와 같이 쿼리 파라미터로 값이 넘어온 경우에 사용하고 status 값이 true이면 실행된다




3. 내용 정리

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {

    private final ItemRepository itemRepository;
}
  • 생성자가 하나만 있는 경우, 생성자에 붙은 @Autowired 생략 가능

  • @RequiredArgsConstructor를 사용하면 final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다

    • 생성자 코드를 생략할 수 있다

@PostConstruct
public void init() {
    itemRepository.save(new Item("itemA", 10000, 10));
    itemRepository.save(new Item("itemB", 20000, 20));
}
  • @PostConstruct 때문에 해당 빈의 의존관계가 모두 주입되고 나면 초기화 용도로 호출된다

@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
                        @RequestParam int price,
                        @RequestParam Integer quantity,
                        Model model) {
    ...
}
  • form 태그 내부 input 태그의 name 속성으로 넘어옴

    • input 태그의 name 속성과 파라미터 이름이 동일해야함
  • <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">


@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item) {
    ...
}
  • @ModelAttribute의 기능

    • 요청 파라미터 처리 : @ModelAttribute가 Item 객체를 생성해 요청 파라미터로 받은 값들을 넣어준다

    • Model 추가 : @ModelAttribute로 지정한 객체를 Model에 자동으로 넣어준다 ( "item"이라는 이름으로 )

      • model.addAttribute("item", item)을 자동으로 처리

      • 그래서 파라미터에 Model을 선언할 필요도 없고 model.addAttribute()를 사용할 필요도 없다

      • ( ) 안에 이름을 생략하면 클래스명의 첫 글자를 소문자로 변경해서 model에 등록한다 ( Item → item )

  • @ModelAttribute 생략 가능

    • 생략하면 String과 같은 단순 타입이 들어오면 @RequestParam이 적용되고, 객체 타입인 경우 @ModelAttribute가 적용됨

@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
    itemRepository.update(itemId, item);
    return "redirect:/basic/items/{itemId}";
}
  • @Pathvariable로 넘어온 itemId, 넘어온 요청 파라미터와 @ModelAttribute로 생성된 model로 기존 item을 수정

  • 스프링은 redirect:/ 로 편리하게 리다이렉트 지원

  • 리다이렉트 시, @PathVariable Long itemId 의 값을 그대로 사용해서 경로 지정 가능

  • 상태 코드를 확인하면 302, Location 헤더 정보는 /basic/items/{itemId}




4. PRG : Post / Redirect / Get

4-1. 리다이렉트 사용 전

@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
    ...
    return basic/item
}
  • Post 요청의 결과로 단순히 다른 화면으로 전환하도록 했을 때 새로고침을 누르면 중복으로 등록되는 문제가 발생

    • 새로고침은 마지막으로 수행한 요청이 다시 수행하는데 웹 브라우저 기준에서 마지막으로 한 요청이 Post 방식

    • 즉, 마지막에 서버에 전송한 데이터를 다시 전송하기 때문에 중복 등록 문제 발생

  • ➡️ 리다이렉트를 사용해서 해결

  • 리다이렉트를 하면 웹 브라우저가 url이 바뀌면서 Get 방식으로 다시 요청

  • 리다이렉트 후 새로고침을 눌러도 마지막 요청이 Get 방식이기 때문에 아무런 문제가 발생하지 않는다


4-2. 리다이렉트 사용 후

@PostMapping("/add")
public String addItemV5(@ModelAttribute("item") Item item, 
						RedirectAttributes redirectAttributes) {

    Item savedItem = itemRepository.save(item);

    //return "redirect:/basic/items/" + item.getId();

    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);


    return "redirect:/basic/items/{itemId}";
}
  • 상품 상세 화면으로 리다이렉트 : return "redirect:/basic/items/" + item.getId();
  • 그러나 위의 방식은 위험한 방식

    • URL에 변수를 더해서 사용하는 것은 URL 인코딩이 되지 않기 때문

    • 이런 문제를 해결하기 위해 RedirectAttributes를 사용

  • RedirectAttributes : 리다이렉트할 때 파라미터를 붙여서 보낼 수 있음

    • 지정한 속성 이름과 return 에서 사용한 경로변수와 이름이 같은 경우, 경로 변수에 값이 들어가게 된다

    • 경로에 이름이 없는 경우, ?status=true처럼 쿼리 파라미터 형식으로 전달된다

    • 즉, RedirectAttributes는 URL 인코딩, pathVarible , 쿼리 파라미터까지 처리해준다

profile
공부한 내용을 정리해서 기록하고 다시 보기 위한 공간

0개의 댓글