springboot_validation : init
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package hello.itemservice;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ItemServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ItemServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package hello.itemservice;
|
||||
|
||||
import hello.itemservice.domain.item.Item;
|
||||
import hello.itemservice.domain.item.ItemRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TestDataInit {
|
||||
|
||||
private final ItemRepository itemRepository;
|
||||
|
||||
/**
|
||||
* 테스트용 데이터 추가
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
itemRepository.save(new Item("itemA", 10000, 10));
|
||||
itemRepository.save(new Item("itemB", 20000, 20));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hello.itemservice.domain.item;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Item {
|
||||
|
||||
private Long id;
|
||||
private String itemName;
|
||||
private Integer price;
|
||||
private Integer quantity;
|
||||
|
||||
public Item() {
|
||||
}
|
||||
|
||||
public Item(String itemName, Integer price, Integer quantity) {
|
||||
this.itemName = itemName;
|
||||
this.price = price;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package hello.itemservice.domain.item;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package hello.itemservice.web.validation;
|
||||
|
||||
import hello.itemservice.domain.item.Item;
|
||||
import hello.itemservice.domain.item.ItemRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/validation/v1/items")
|
||||
@RequiredArgsConstructor
|
||||
public class ValidationItemControllerV1 {
|
||||
|
||||
private final ItemRepository itemRepository;
|
||||
|
||||
@GetMapping
|
||||
public String items(Model model) {
|
||||
List<Item> items = itemRepository.findAll();
|
||||
model.addAttribute("items", items);
|
||||
return "validation/v1/items";
|
||||
}
|
||||
|
||||
@GetMapping("/{itemId}")
|
||||
public String item(@PathVariable long itemId, Model model) {
|
||||
Item item = itemRepository.findById(itemId);
|
||||
model.addAttribute("item", item);
|
||||
return "validation/v1/item";
|
||||
}
|
||||
|
||||
@GetMapping("/add")
|
||||
public String addForm(Model model) {
|
||||
model.addAttribute("item", new Item());
|
||||
return "validation/v1/addForm";
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
|
||||
Item savedItem = itemRepository.save(item);
|
||||
redirectAttributes.addAttribute("itemId", savedItem.getId());
|
||||
redirectAttributes.addAttribute("status", true);
|
||||
return "redirect:/validation/v1/items/{itemId}";
|
||||
}
|
||||
|
||||
@GetMapping("/{itemId}/edit")
|
||||
public String editForm(@PathVariable Long itemId, Model model) {
|
||||
Item item = itemRepository.findById(itemId);
|
||||
model.addAttribute("item", item);
|
||||
return "validation/v1/editForm";
|
||||
}
|
||||
|
||||
@PostMapping("/{itemId}/edit")
|
||||
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
|
||||
itemRepository.update(itemId, item);
|
||||
return "redirect:/validation/v1/items/{itemId}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#logging.level.org.apache.coyote.http11=debug
|
||||
16
springboot_validation/src/main/resources/messages.properties
Normal file
16
springboot_validation/src/main/resources/messages.properties
Normal file
@@ -0,0 +1,16 @@
|
||||
hello=안녕
|
||||
hello.name=안녕 {0}
|
||||
|
||||
label.item=상품
|
||||
label.item.id=상품 ID
|
||||
label.item.itemName=상품명
|
||||
label.item.price=가격
|
||||
label.item.quantity=수량
|
||||
|
||||
page.items=상품 목록
|
||||
page.item=상품 상세
|
||||
page.addItem=상품 등록
|
||||
page.updateItem=상품 수정
|
||||
|
||||
button.save=저장
|
||||
button.cancel=취소
|
||||
@@ -0,0 +1,16 @@
|
||||
hello=hello
|
||||
hello.name=hello {0}
|
||||
|
||||
label.item=Item
|
||||
label.item.id=Item ID
|
||||
label.item.itemName=Item Name
|
||||
label.item.price=price
|
||||
label.item.quantity=quantity
|
||||
|
||||
page.items=Item List
|
||||
page.item=Item Detail
|
||||
page.addItem=Item Add
|
||||
page.updateItem=Item Update
|
||||
|
||||
button.save=Save
|
||||
button.cancel=Cancel
|
||||
7
springboot_validation/src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
7
springboot_validation/src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
19
springboot_validation/src/main/resources/static/index.html
Normal file
19
springboot_validation/src/main/resources/static/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>상품 관리
|
||||
<ul>
|
||||
<li><a href="/validation/v1/items">상품 관리 - 검증 v1</a></li>
|
||||
<li><a href="/validation/v2/items">상품 관리 - 검증 v2</a></li>
|
||||
<li><a href="/validation/v3/items">상품 관리 - 검증 v3</a></li>
|
||||
<li><a href="/validation/v4/items">상품 관리 - 검증 v4</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
7
springboot_validation/src/main/resources/templates/css/bootstrap.min.css
vendored
Normal file
7
springboot_validation/src/main/resources/templates/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
|
||||
<!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 th:text="#{page.addItem}">상품 등록</h2>
|
||||
</div>
|
||||
|
||||
<form action="item.html" th:action th:object="${item}" method="post">
|
||||
<div>
|
||||
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
|
||||
<input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
|
||||
</div>
|
||||
<div>
|
||||
<label for="price" th:text="#{label.item.price}">가격</label>
|
||||
<input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
|
||||
</div>
|
||||
<div>
|
||||
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
|
||||
<input type="text" id="quantity" th:field="*{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" th:text="#{button.save}">상품 등록</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="w-100 btn btn-secondary btn-lg"
|
||||
onclick="location.href='items.html'"
|
||||
th:onclick="|location.href='@{/validation/v1/items}'|"
|
||||
type="button" th:text="#{button.cancel}">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div> <!-- /container -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!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 th:text="#{page.updateItem}">상품 수정</h2>
|
||||
</div>
|
||||
|
||||
<form action="item.html" th:action th:object="${item}" method="post">
|
||||
<div>
|
||||
<label for="id" th:text="#{label.item.id}">상품 ID</label>
|
||||
<input type="text" id="id" th:field="*{id}" class="form-control" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
|
||||
<input type="text" id="itemName" th:field="*{itemName}" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
<label for="price" th:text="#{label.item.price}">가격</label>
|
||||
<input type="text" id="price" th:field="*{price}" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
|
||||
<input type="text" id="quantity" th:field="*{quantity}" class="form-control">
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">저장</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="w-100 btn btn-secondary btn-lg"
|
||||
onclick="location.href='item.html'"
|
||||
th:onclick="|location.href='@{/validation/v1/items/{itemId}(itemId=${item.id})}'|"
|
||||
type="button" th:text="#{button.cancel}">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div> <!-- /container -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!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 th:text="#{page.item}">상품 상세</h2>
|
||||
</div>
|
||||
|
||||
<!-- 추가 -->
|
||||
<h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
|
||||
|
||||
<div>
|
||||
<label for="itemId" th:text="#{label.item.id}">상품 ID</label>
|
||||
<input type="text" id="itemId" name="itemId" class="form-control" value="1" th:value="${item.id}" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
|
||||
<input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="price" th:text="#{label.item.price}">가격</label>
|
||||
<input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
|
||||
<input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}" readonly>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="w-100 btn btn-primary btn-lg"
|
||||
onclick="location.href='editForm.html'"
|
||||
th:onclick="|location.href='@{/validation/v1/items/{itemId}/edit(itemId=${item.id})}'|"
|
||||
type="button" th:text="#{page.updateItem}">상품 수정</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="w-100 btn btn-secondary btn-lg"
|
||||
onclick="location.href='items.html'"
|
||||
th:onclick="|location.href='@{/validation/v1/items}'|"
|
||||
type="button" th:text="#{button.cancel}">목록으로</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /container -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!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 th:text="#{page.items}">상품 목록</h2>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="btn btn-primary float-end"
|
||||
onclick="location.href='addForm.html'"
|
||||
th:onclick="|location.href='@{/validation/v1/items/add}'|"
|
||||
type="button" th:text="#{page.addItem}">상품 등록</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th th:text="#{label.item.id}">ID</th>
|
||||
<th th:text="#{label.item.itemName}">상품명</th>
|
||||
<th th:text="#{label.item.price}">가격</th>
|
||||
<th th:text="#{label.item.quantity}">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="item : ${items}">
|
||||
<td><a href="item.html" th:href="@{/validation/v1/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
|
||||
<td><a href="item.html" th:href="@{|/validation/v1/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>
|
||||
@@ -0,0 +1,13 @@
|
||||
package hello.itemservice;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ItemServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package hello.itemservice.domain.item;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class ItemRepositoryTest {
|
||||
|
||||
ItemRepository itemRepository = new ItemRepository();
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
itemRepository.clearStore();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save() {
|
||||
//given
|
||||
Item item = new Item("itemA", 10000, 10);
|
||||
|
||||
//when
|
||||
Item savedItem = itemRepository.save(item);
|
||||
|
||||
//then
|
||||
Item findItem = itemRepository.findById(item.getId());
|
||||
assertThat(findItem).isEqualTo(savedItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAll() {
|
||||
//given
|
||||
Item item1 = new Item("item1", 10000, 10);
|
||||
Item item2 = new Item("item2", 20000, 20);
|
||||
|
||||
itemRepository.save(item1);
|
||||
itemRepository.save(item2);
|
||||
|
||||
//when
|
||||
List<Item> result = itemRepository.findAll();
|
||||
|
||||
//then
|
||||
assertThat(result.size()).isEqualTo(2);
|
||||
assertThat(result).contains(item1, item2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateItem() {
|
||||
//given
|
||||
Item item = new Item("item1", 10000, 10);
|
||||
|
||||
Item savedItem = itemRepository.save(item);
|
||||
Long itemId = savedItem.getId();
|
||||
|
||||
//when
|
||||
Item updateParam = new Item("item2", 20000, 30);
|
||||
itemRepository.update(itemId, updateParam);
|
||||
|
||||
Item findItem = itemRepository.findById(itemId);
|
||||
|
||||
//then
|
||||
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
|
||||
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
|
||||
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package hello.itemservice.message;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
public class MessageSourceTest {
|
||||
|
||||
@Autowired
|
||||
MessageSource ms;
|
||||
|
||||
@Test
|
||||
void helloMessage() {
|
||||
String result = ms.getMessage("hello", null, null);
|
||||
assertThat(result).isEqualTo("안녕");
|
||||
}
|
||||
|
||||
@Test
|
||||
void notFoundMessageCode() {
|
||||
assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
|
||||
.isInstanceOf(NoSuchMessageException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void notFoundMessageCodeDefaultMessage() {
|
||||
String result = ms.getMessage("no_code", null, "기본 메시지", null);
|
||||
assertThat(result).isEqualTo("기본 메시지");
|
||||
}
|
||||
|
||||
@Test
|
||||
void argumentMessage() {
|
||||
String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
|
||||
assertThat(result).isEqualTo("안녕 Spring");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultLang() {
|
||||
assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
|
||||
assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
|
||||
}
|
||||
|
||||
@Test
|
||||
void enLang() {
|
||||
assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user