728x90
0. 부트스트랩
- html form 이쁘게 보이도록 하려고 사용
- https://getbootstrap.com
- 동작
- https://getbootstrap.com/docs/5.0/getting-started/download/
- Compiled CSS and JS 항목을 다운로드
- 압축을 풀고 bootstrap.min.css 를 복사
- 폴더에 추가 : resources/static/css/bootstrap.min.css
- intellij css 작동 안할시 해결방안
- 'out' 이란 build folder(=compile folder) 삭제후 서버 재시작
- 정적 리소스가 공개되는 /resources/static folder에 HTML 두면, 실제 서비스에서도 공개된다.
= 실제 서비스 운영시 해당 위치 html를 아무나 접근 가능해짐
- resource folder 내부에 css, js, img 등등 저장이 가능하다.
1. 상품 목록 - 타임리프
- 참고
- @RequestArgsConstructor : 생성자 주입 자동으로 해주는 lombok의 기능
final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다. - @PostConstruct : DI가 끝난 후 초기화 용도로 사용
- @RequestArgsConstructor : 생성자 주입 자동으로 해주는 lombok의 기능
- 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>
- 정리
- <html xmlns:th="http://www.thymeleaf.org">
- 타임리프 사용 선언
- 타임리프 사용 선언
- 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
반복문
- 타임리프는 th:로 타임리프 문법이 시작된다.
- <html xmlns:th="http://www.thymeleaf.org">
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 기능
- 요청 파라미터 처리
- 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 객체 값을 사용할 수 있는 변수명이 된다.
- @ModelAttribute("item") Item item 의 "item" 이 model.addAttribute("item", xxx)의 key 값과 동일
@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
- @ModelAttribute 의 name 생략시 (name은 이전 "item" 위치의 string) 요청 params로 받는 Item type에서 I를 i로 변경 후 name으로 설정 된다.
@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 방법
- "redirect:/... "
- @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 정보로 사용된다.
- url 인코딩 기능 수행
redirectAttributes.addAttribute("itemId", savedItem.getId());
- 추가 기능까지 수행
redirectAttributes.addAttribute("status", true);
- url 인코딩 기능 수행
- 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은 다음과 같은 특징이 있습니다.
- Page: 페이지 단위로 데이터를 저장하는 저장소입니다. 대부분의 경우, Spring MVC에서는 Controller에서 View로 데이터를 전달할 때 모델(Model)을 사용하므로 Page 저장소를 사용할 일이 많지 않습니다.
- Request: HTTP 요청 단위로 데이터를 저장하는 저장소입니다. HTTP 요청이 끝나면 데이터가 삭제됩니다. 주로 같은 요청에서 여러 개의 Controller나 View에서 데이터를 공유해야 할 때 사용합니다.
- Session: HTTP 세션 단위로 데이터를 저장하는 저장소입니다. 세션이 끝나기 전까지 데이터가 유지됩니다. 주로 로그인 상태나 사용자 정보 등을 저장하는 데 사용합니다.
- Application: 어플리케이션 단위로 데이터를 저장하는 저장소입니다. 어플리케이션이 종료될 때까지 데이터가 유지됩니다. 주로 어플리케이션 전체에서 공유해야 하는 설정 정보나 상수 등을 저장하는 데 사용합니다.
- Page: 페이지 단위로 데이터를 저장하는 저장소입니다. 대부분의 경우, Spring MVC에서는 Controller에서 View로 데이터를 전달할 때 모델(Model)을 사용하므로 Page 저장소를 사용할 일이 많지 않습니다.
- 특징
- Page와 Application 저장소는 Spring에서는 사용 빈도가 낮기 때문에, 사용을 권장하지 않습니다.
- Request와 Session 저장소는 사용 빈도가 높고 유용한 기능이 많기 때문에, 필요한 경우에는 적극적으로 사용하는 것이 좋습니다.
- 단, Session 저장소를 사용할 때에는 서버의 메모리를 차지하고 있기 때문에, 과도한 사용은 서버 성능에 영향을 미칠 수 있으므로, 사용 시에는 주의가 필요합니다.
- Page와 Application 저장소는 Spring에서는 사용 빈도가 낮기 때문에, 사용을 권장하지 않습니다.