framework/spring

스프링 MVC 6 - thymeleaf, @ModelAttribute, PRG, Redirection

wooweee 2023. 4. 30. 17:03
728x90

0. 부트스트랩

 

 

  • intellij css 작동 안할시 해결방안
    • 'out' 이란 build folder(=compile folder) 삭제후 서버 재시작

 

  • 정적 리소스가 공개되는 /resources/static folder에 HTML 두면, 실제 서비스에서도 공개된다.
    = 실제 서비스 운영시 해당 위치 html를 아무나 접근 가능해짐

 

  • resource folder 내부에 css, js, img 등등 저장이 가능하다.

 

1. 상품 목록 - 타임리프

  •  참고
    • @RequestArgsConstructor : 생성자 주입 자동으로 해주는 lombok의 기능
      final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
    • @PostConstruct : DI가 끝난 후 초기화 용도로 사용

 

  • Controller - 메인 화면
package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") //공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

    @GetMapping
    public String items(Model model){
        List<Item> items = itemRepository.findAll();
        model.addAttribute("items", items);
        return "basic/items";
    }
}

 

  • /resources/templates/basic/items.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link  th:href="@{/css/bootstrap.min.css}"
            href="/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>상품 목록</h2>
    </div>
    <div class="row">
        <div class="col">
            <button class="btn btn-primary float-end"
                    onclick="location.href='addForm.html'"
                    th:onclick="|location.href='@{/basic/items/add}'|"
                    type="button">상품 등록</button>
        </div>
    </div>
    <hr class="my-4">
    <div>
        <table class="table">
            <thead>
            <tr>
                <th>ID</th>
                <th>상품명</th>
                <th>가격</th>
                <th>수량</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="item : ${items}">
                <td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
                <td><a href="item.html" th:href="@{|/basic/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
                <td th:text="${item.price}">가격</td>
                <td th:text="${item.quantity}">물량</td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>

 

  • 정리
    1. <html xmlns:th="http://www.thymeleaf.org">
      • 타임리프 사용 선언

    2. th : PROPERTY = " @{} or ${} or | | or ( )"
      • 타임리프는 th:로 타임리프 문법이 시작된다.

      • th에 적용할 property를 쓰고 " " 내부에 적용하고 싶은 문법을 적용한다.
        • 어지간한 property는 다 th: property 종류 가능하다.
        • th: property를 해도 그냥 property도 작성을 하는데
          이는 렌더링 전에는 그냥 property가 적용되고
          렌더링 후에는 th: property가 적용된다.
      • @{} : url 경로

      • ${} : controller로 부터 받아온 model 객체의 key를 작성하면 value를 반환해줌

      • | | : 리터럴로 JS의 벡틱 같은 역할이라고 보면 된다.
        • 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용
           <span th:text="'Welcome to our application, ' + ${user.name} + '!'"> 
          <span th:text="|Welcome to our application, ${user.name}!|">

      • ()
        • @{} 내부에서 model로받아온 정보를 변수를 설정해서 pathvariable로 사용하고 싶을 때
        • 쿼리 파라미터도 사용 가능
          th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
          생성 링크:  http://localhost:8080/basic/items/1?query=test

      • th:each
        반복문

 

2. 상품 등록

 

2.1. 상품등록 

package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

    @GetMapping("/add")
    public String addForm(){
        return "basic/addForm";
    }
}

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <link  th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
  <style>
    .container {
      max-width: 560px;
    }
  </style>
</head>
<body>
<div class="container">
  <div class="py-5 text-center">
    <h2>상품 등록 폼</h2>
  </div>
  <h4 class="mb-3">상품 입력</h4>
  <form action="item.html" th:action="|/basic/items/add|" method="post">
    <div>
      <label for="itemName">상품명</label>
      <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">
    </div>
    <div>
      <label for="price">가격</label>
      <input type="text" id="price" name="price" class="form-control"
             placeholder="가격을 입력하세요">
    </div>
    <div>
      <label for="quantity">수량</label>
      <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">
    </div>
    <hr class="my-4">
    <div class="row">
      <div class="col">
        <button class="w-100 btn btn-primary btn-lg" type="submit">상품 등록</button>
      </div>
      <div class="col">
        <button class="w-100 btn btn-secondary btn-lg"
                onclick="location.href='items.html'"
                th:onclick="|location.href='@{basic/items}'|" type="button">취소</button>
      </div>
    </div>
  </form>
</div> <!-- /container -->
</body>
</html>

 

  • th: action
    property에 값을 넣지 않으면 자동으로 현 url로 이동한다.

 

2.2. 상품등록 처리 - @ModelAttribute

 

package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

//    @PostMapping("/add")
    public String addItemV1(@RequestParam String itemName,
                       @RequestParam int price,
                       @RequestParam Integer quantity,
                       Model model){
        Item item = new Item();
        item.setItemName(itemName);
        item.setPrice(price);
        item.setQuantity(quantity);

        itemRepository.save(item);

        model.addAttribute("item", item);

        return "basic/item";
    }

//    @PostMapping("/add")
    public String addItemV2(@ModelAttribute("item") Item item, Model model){
        itemRepository.save(item);
//        model.addAttribute("item", item); @ModelAttribute의 params가 Model에 담아줌 - Model obj는 spring이 만들어 놨음
        return "basic/item";
    }

//    @PostMapping("/add")
    public String addItemV3(@ModelAttribute Item item){
        itemRepository.save(item);
        return "basic/item";
    }

//    @PostMapping("/add")
    public String addItemV4(Item item){
        itemRepository.save(item);
        return "basic/item"; // 해당 url로도 상세화면이 가는 이유는 @ModelAttribute가 basic/item에 직접 값을 넣어주기 때문
    }
}

 

  • ModelAttribute 기능
    1. 요청 파라미터 처리
    2. Model 에 요청 파라미터로 처리한 객체 넣는 기능 수행
      Model model을 params로 넣지 않아도 자동으로 model에 넣어준다.

 

  • v2
    • @ModelAttribute("item") Item item 의 "item" 이 model.addAttribute("item", xxx)의 key 값과 동일

    • @ModelAttribute("item") Item item 의  item    이 model.addAttribute("xxx", item)의 value 값과 동일
    • "item"이 view에서 item 객체 값을 사용할 수 있는 변수명이 된다.
    @PostMapping("/add")
    public String addItemV2(@ModelAttribute("item") Item item, Model model){
        itemRepository.save(item);
//        model.addAttribute("item", item); @ModelAttribute의 params가 Model에 담아줌 - Model obj는 spring이 만들어 놨음
        return "basic/item";
    }

 

  • v3
    • @ModelAttribute 의 name 생략시 (name은 이전 "item" 위치의 string) 요청 params로 받는 Item type에서 I를 i로 변경 후 name으로 설정 된다.
      Item -> item
    @PostMapping("/add")
    public String addItemV3(@ModelAttribute Item item){
        itemRepository.save(item);
        return "basic/item";
    }

 

  • v4
    • @ModelAttribute 생략 - 2개가 생략되는 것임. @ModelAttribute 랑 @ModelAttribute의 name
    @PostMapping("/add")
    public String addItemV4(Item item){
        itemRepository.save(item);
        return "basic/item"; // 해당 url로도 상세화면이 가는 이유는 @ModelAttribute가 basic/item에 직접 값을 넣어주기 때문
    }

 

 

3. 상품수정 개발 - redirect 방법

 

@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item){
    itemRepository.update(itemId, item);
    return "redirect:/basic/items/{itemId}";
}
  • spring Redirect 방법
    1. "redirect:/... "
    2. @PostMappind의 PathVariable 값을 redirect 란에 {itemId} 이렇게 사용 가능

 

 

4. 상품등록 - PRG

 

  • view template으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트를 호출해주면 된다.

 

 

  •  v5
    • 이전 add는 basic/item으로 해서 view로 바로 forward 했지만
    • 이제는 redirect:/basic/items/item.getId()로 수행
    • 문제점
    • redirect에서  +item.getId() 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다

 

package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

    @PostMapping("/add")
    public String addItemV5(Item item){
        itemRepository.save(item);
        return "redirect:/basic/items/"+item.getId(); // 현재 encoding이 안되채로 넣은거라 되게 위험한 상황
    }
}

 

 

v6

  • RedirectAttribute 기능 - data가 view로 넘어가는 것이 아니고 Url redirection 정보로 사용된다.
    1. url 인코딩 기능 수행
      redirectAttributes.addAttribute("itemId", savedItem.getId());
       
    2. 추가 기능까지 수행
      redirectAttributes.addAttribute("status", true); 

  • addFlashAttribute
    • 그냥 Model로 data 넘겨도 되는데 회원가입 완료 알림창 같은 경우 model에 담게되면 이동시마다 계속 나타나게 된다.
    • 위의 메서드 사용시, data가 session에 담긴후, view에서 한번만 수행된다.
    • 회원가입 완료 알림창 같은 경우 사용된다.

 

package hello.itemservice.web.basic;

@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
    private final ItemRepository itemRepository;

    @PostMapping("/add")
    public String addItemV6(Item item, RedirectAttributes redirectAttributes){
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true); 
        // 추가기능, url 마지막에 query 형식으로 ?status=true 추가됨
        return "redirect:/basic/items/{itemId}";
    }
}

 

  • 추가 기능 이용 방법

  • th:if="${param.paramKEY 명}" : 타임리프에서 쿼리 파라미터를 편리하게 조회하는 기능
    원래는 모델에 직접 담고 값을 꺼내야 한다. 그런데 쿼리 파라미터는 자주 사용해서 타임리프에서 직접 지원
<h2 th:if="${param.status}" th:text="'save collect!'"></h2>

 

 

5. spring 저장소 4가지

 

  • Spring에서 제공하는 저장소인 page, request, session, application은 다음과 같은 특징이 있습니다.

    1. Page: 페이지 단위로 데이터를 저장하는 저장소입니다. 대부분의 경우, Spring MVC에서는 Controller에서 View로 데이터를 전달할 때 모델(Model)을 사용하므로 Page 저장소를 사용할 일이 많지 않습니다.

    2. Request: HTTP 요청 단위로 데이터를 저장하는 저장소입니다. HTTP 요청이 끝나면 데이터가 삭제됩니다. 주로 같은 요청에서 여러 개의 Controller나 View에서 데이터를 공유해야 할 때 사용합니다.

    3. Session: HTTP 세션 단위로 데이터를 저장하는 저장소입니다. 세션이 끝나기 전까지 데이터가 유지됩니다. 주로 로그인 상태나 사용자 정보 등을 저장하는 데 사용합니다.

    4. Application: 어플리케이션 단위로 데이터를 저장하는 저장소입니다. 어플리케이션이 종료될 때까지 데이터가 유지됩니다. 주로 어플리케이션 전체에서 공유해야 하는 설정 정보나 상수 등을 저장하는 데 사용합니다.

  • 특징
    • Page와 Application 저장소는 Spring에서는 사용 빈도가 낮기 때문에, 사용을 권장하지 않습니다. 

    • Request와 Session 저장소는 사용 빈도가 높고 유용한 기능이 많기 때문에, 필요한 경우에는 적극적으로 사용하는 것이 좋습니다. 

    • 단, Session 저장소를 사용할 때에는 서버의 메모리를 차지하고 있기 때문에, 과도한 사용은 서버 성능에 영향을 미칠 수 있으므로, 사용 시에는 주의가 필요합니다.