월루를 꿈꾸는 대학생

[MVC-1] 스프링 MVC -웹 페이지 본문

Programing/Spring Boot

[MVC-1] 스프링 MVC -웹 페이지

하즈시 2023. 8. 8. 21:50
728x90

요구사항 분석

상품 도메인 모델

상품 ID
상품명
가격
수량

상품 관리 기능

상품 목록
상품 상세
상품 등록
상품 수정


흰색 - 컨트롤러
검은색 -뷰

https://javacan.tistory.com/entry/what-is-a-domain-model


@Data는 데이터모델에 쓰기엔 예측하지 못한 동작을 할 수 있기에 게터 ,세터 정도만 쓰는 것이 좋음
쓸려면 확인하고 쓰자 !

레포지토리 작업

@Repository // 안에 @Component가 있으니 컴포넌트 스캔  
public class ItemRepository {  
    private static final Map<Long, Item> store = new HashMap<>(); //static 사용  
    private static long sequence = 0L; //static 사용  
    public Item save(Item item) {  
        item.setId(++sequence);  
        store.put(item.getId(), item);  
        return item;  
    }  
    public Item findById(Long id) {  
        return store.get(id);  
    }  
    public List<Item> findAll() {  
        return new ArrayList<>(store.values());  
    }  
    public void update(Long itemId, Item updateParam) {  
        Item findItem = findById(itemId);  
        findItem.setItemName(updateParam.getItemName());  
        findItem.setPrice(updateParam.getPrice());  
        findItem.setQuantity(updateParam.getQuantity());  
    }  
    public void clearStore() {  
        store.clear();  
    }  
}

상품서비스 html

부트스트랩
  • 웹 사이트 쉽게 만들어주는 html ,css, js프레임워크

정적 리소스가 공개되는 /resources/static 폴더에 html 넣어두면 공개가 되어버리니 주의가 필요함


상품목록 - 타임리프

컨트롤러

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

    private final ItemRepository itemRepository;  

    @Autowired  
    public  BasicItemController(ItemRepository itemRepository){  
        this.itemRepository = itemRepository;  
    }  

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

    @PostConstruct // 만들어지기 전에 먼저 호출되는   
public void init() {  
        itemRepository.save(new Item("testA", 10000, 10));  
        itemRepository.save(new Item("testB", 20000, 20));  
    }  
}
RequiredArgsConstructor
  • final 붙은 멤버변수만 사용해서 생성자 자동 만들어줌
  • 생성자가 딱 한 개 있으면 스프링이 @Autowired로 의존관계 주입해줌
@PostConstruct
  • 해당 빈의 의존관계 주입이 모두 끝나고 초기화 용도로 호출

타임리프는 html 태그를 그대로 사용

<!DOCTYPE HTML>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="utf-8">  
    <link href="../css/bootstrap.min.css"  
          th: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}">10000</td>  
                <td th:text="${item.quantity}">10</td>  
            </tr>            </tbody>        </table>    </div></div> <!-- /container -->  
</body>  
</html>
속성 변경 - th
<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>
  • 타임리프 뷰 템플릿을 거치면 원래 태그를 th:로 시작하는 태그로 덮어씌워서 대체
  • th:xxx 가 붙은 부분은 서버사이드에서 랜더링 되고 기존 것을 대체 th:xxx가 없으면 기존 xxx속성 사용
링크 표현식
<link href="../css/bootstrap.min.css"  
      th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
  • @{} : 타임리프에서 url 사용시 씀
리터럴 대체 문법
<td><a href="item.html" th:href="@{|/basic/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
  • | | : 파이프 안에 문자와 표현식을 사용
  • 그냥 큰 따옴표 같은 느낌 원래 그냥 쓰면 다 + 로 붙여야하는데 간편하게 쓰기 가능
반복 출력 th:each
<tr th:each="item : ${items}">
  • 컨트롤러에서 받은 items를 반복문에 넣어서 처리
  • 변수는 ${} 로 사용 - 플러터랑 같네
내용변경 th:text
<td th:text="${item.price}">10000</td>
  • th:text에 있는 값이 표안에 들어가도록 10000의 값을 대체해서 드감

상품 상세

@GetMapping("/{itemId}")  
public String item(@PathVariable long itemId, Model model){  
    Item item = itemRepository.findById(itemId);  
    model.addAttribute("item",item);  
    return "basic/item";  
}
속성 변경
  • th:value="{item.id}"
  • 모델에 있는 item 정보를 받고 프로퍼티 접근법으로 값 가져옴 item.getId()

링크

  • 리터럴 표현을 사용
    th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"

상품 등록 폼

@GetMapping("/add")  
public String addForm() {  
    return "basic/addForm";  
}
<form action="item.html" th:action method="post">  
    <div>        <label for="itemName">상품명</label>  
        <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">  
    </div>    <div>
  • th:action
  • action에 값이 없으면 현재 url에 데이터 전송함

상품 등록 처리 - @ModelAttribute

상품등록폼의 전송 방식

POST- HTML Form
  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달
  • ex )회원 가입, 상품 주문 등등

실제 넘어가지는 값
html에 있는 name을 키값으로 값을 넘김

level1
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";  
}
  • html에서 전달한 값들을 @RequestParam으로 다 받은 다음 Item객체 만들어서 저장
level2
public String addItemV2(@ModelAttribute("item") Item item, Model model) {  
    itemRepository.save(item);  
    //model.addAttribute("item", item); //자동 추가, 생략 가능  
    return "basic/item";  
}
  • @ModelAttribute는 Item객체를 생성하고 요청 파라미터 값을 프로퍼티 접근법 setXXX으로 넣어서 만들어줌
  • 파라미터로 받은 Model에 @ModelAttribute가 만든 객체를 자동으로 넣어줌 그래서model.addAttribute("item", item); 생략 가능
  • 모델에 넣을 때 이름은 애노테이션에 지정된 이름으로 저장
level3
public String addItemV3(@ModelAttribute Item item) {  
    // 이름 지정 안 하면 클래스 첫글자 소문자로 바꾼 거로 모델에 담김  
    itemRepository.save(item);  
    return "basic/item";  
}
  • 이름 생략시 클래스의 앞글자를 소문자로 한 이름을 자동으로 등록
  • Model 객체가 없지만 Model은 전체 구조에서 사용하는 것이고 RequestMappingHandlerAdapter에서 생성해서주니까 없어도 괜찮은 듯
    https://www.inflearn.com/questions/287855

level4

@PostMapping("/add")  
public String addItemV4(Item item) {  
    itemRepository.save(item);  
    return "basic/item";  
}
  • @ModelAttribute 자체도 생략 가능
  • 대상 객체는 모델에서 자동 등록 됨 지난 시간에 배움

상품 수정

@GetMapping("/{itemId}/edit")  
public String editForm(@PathVariable Long itemId, Model model) {  
    Item item = itemRepository.findById(itemId);  
    model.addAttribute("item", item);  
    return "basic/editForm";  
}

@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
    itemRepository.update(itemId, item);
    return "redirect:/basic/items/{itemId}";
}

리다이렉트

  • 마지막 리턴시 return "redirect:/basic/items/{itemId}"; 문구로 상품 상세 화면으로 이동이 됨

PRG POST/Redirect/Get

  • 상품 등록한 상태로 새로고침하면 같은 값이 계속계속 들어가버림

get으로 상품저장 화면에서 가서 상품 정보 입력후 버튼을 누르면 post방식으로 상품저장을 하게 되고 여기서 새로고침을 누르면 아까 했던 post방식의 상품저장이 되돌이표 되는 것 = > id만 다르고 데이터값은 같은 데이터가 쌓임

새로고침 : 웹 브라우저에 마지막으로 서버에 전송한 데이터를 다시 전송 // 내가 마지막에 했던 행위를 다시 하는 것

문제 해결을 하기 위해 리다이렉트를 함

POST, Redirect GET


상품 저장 후에 리다이렉트로 상품 상세화면으로 넘어가면 주소자체도 바뀌고 마지막 행위가 3번이니까 새로고침해도 get /items/id 주소로 get방식으로 요청하는 걸 반복함
즉 리다이렉트로 url 이랑 마지막 남은 방식을 바꿔서 새로고침의 문제를 해결 가능

@PostMapping("/add")  //PRG  
public String addItemV5(Item item) {  
    itemRepository.save(item);  
    return "redirect:/basic/items/" + item.getId();  
}
  • 리다이렉트 할 때 주소를 item.getId() 이거는 숫자니까 괜찮은데 한글 , 띄어쓰기 등등 나오면 에러가 남 -> 다음장에서 RedirectAttributes로 해결 가능

RedirectAttributes

  • 고객 입장에선 상품 등록시 저장이 잘 되어 있는지 아닌지 확인을 위해 메세지 표시가 요구사항으로 새로 들어옴

새 컨트롤러 만듦

@PostMapping("/add")  
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {  
    Item savedItem = itemRepository.save(item);  
    redirectAttributes.addAttribute("itemId", savedItem.getId());  
    redirectAttributes.addAttribute("status", true);  
    return "redirect:/basic/items/{itemId}"; // 리다이렉트에 넣은 itemId가 리턴값 문자열에 들어감  
    // state는 ?status=true로 들어감  
}

컨트롤러에서 리턴하게 되면

http://localhost:8080/basic/items/3?status=true

다음과 같은 url로 리다이렉트 됨
해당 url은 담당하는 컨트롤러는 얘임

@GetMapping("/{itemId}")  
public String item(@PathVariable long itemId, Model model){  
    Item item = itemRepository.findById(itemId);  
    model.addAttribute("item",item);  
    return "basic/item";  
}

그럼 리턴값에 해당하는 basic/item html로 가서 처리를 해야겠지!

RedirectAttributes
  • url 인코딩 및 pathVarible , 쿼리 파라미터까지 처리 해줌
<h2 th:if="${param.status}" th:text="'저장 완료!'"></h2>
  • ${param.status}
    • 타임리프에서 쿼리 파라미터 조회하는 기능
    • 쿼리 파라미터에 있던 status값을 가져온 다음 참이면 뒤에 text 화면에 표시

출처
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

728x90

'Programing > Spring Boot' 카테고리의 다른 글

[MVC-2] 타임리프  (0) 2023.08.08
[MVC-1] 스프링 MVC - 기본 기능  (0) 2023.08.01
[MVC-1] 스프링 MVC - 구조 이해  (0) 2023.08.01
[MVC-1] MVC 프레임 워크 만들기  (0) 2023.08.01
[MVC-1] 서블릿 , JSP , MVC패턴  (0) 2023.07.25