Skip to content
Open

Step1 #172

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ out/

### Mac OS ###
.DS_Store

study/
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand All @@ -28,4 +29,4 @@ dependencies {

tasks.named('test') {
useJUnitPlatform()
}
}
2 changes: 1 addition & 1 deletion src/main/java/gift/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/gift/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package gift;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, List<String>>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, List<String>> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
System.out.println(" 처리 중인 오류: " + error.getDefaultMessage() +
" (필드: " + (error instanceof FieldError ? ((FieldError) error).getField() : "전역 오류") +
", 코드: " + error.getCode() + ")");
if (error instanceof FieldError) {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.computeIfAbsent(fieldName, k -> new ArrayList<>()).add(errorMessage);
} else {
errors.computeIfAbsent(error.getObjectName(), k -> new ArrayList<>()).add(error.getDefaultMessage());
}
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
68 changes: 68 additions & 0 deletions src/main/java/gift/controller/AdminProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package gift.controller;

import gift.model.Product;
import gift.repository.ProductDao;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/admin/products")
public class AdminProductController {
private final ProductDao productDao;

public AdminProductController(ProductDao productDao) {
this.productDao = productDao;
}

@GetMapping
public String list(Model model) {
model.addAttribute("products", productDao.getAllProducts());
return "product/list";
}

@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("product",new Product(null,null,null,null));
return "product/form";
}

@PostMapping("/add")
public String add(@Valid @ModelAttribute Product product, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
System.out.println("상품 추가 유효성 검사 실패! 폼 재렌더링.");
bindingResult.getAllErrors().forEach(error -> System.out.println("오류: " + error.getDefaultMessage()));
return "product/form";
}
productDao.insertProduct(product);
return "redirect:/admin/products";
}

@GetMapping("/edit/{id}")
public String editForm(@PathVariable Long id, Model model) {
Product product = productDao.getProductById(id);
model.addAttribute("product", product);
return "product/form";
}

@PostMapping("/edit")
public String edit(@Valid @ModelAttribute Product product, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
System.out.println("상품 추가 유효성 검사 실패! 폼 재렌더링.");
bindingResult.getAllErrors().forEach(error -> System.out.println("오류: " + error.getDefaultMessage()));
return "product/form";
}

System.out.println("상품 수정 성공: " + product.getName());
productDao.updateProduct(product.getId(), product, product);
return "redirect:/admin/products";
}

@PostMapping("/delete/{id}")
public String delete(@PathVariable Long id) {
productDao.removeProduct(id);
return "redirect:/admin/products";
}
}
43 changes: 43 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gift.controller;

import gift.model.Product;
import gift.repository.ProductDao;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/api")
@RestController
public class ProductController {
private final ProductDao productDao;

public ProductController(ProductDao productDao) {
this.productDao = productDao;
}

@GetMapping("/products")
public List<Product> getAllProducts() {
return productDao.getAllProducts();
}

@GetMapping("/products/{id}")
public Product getProductById(@PathVariable int id) {
return productDao.getProductById(id);
}

@PostMapping("/products")
public void addProduct(@Valid @RequestBody Product product) {
productDao.insertProduct(product);
}

@DeleteMapping("products/{id}")
public void deleteProduct(@PathVariable Long id) {
productDao.removeProduct(id);
}

@PatchMapping("/products/{id}")
public void updateProduct(@Valid @PathVariable Long id, @RequestBody Product product) {
productDao.updateProduct(id, productDao.getProductById(id), product);
}
}
57 changes: 57 additions & 0 deletions src/main/java/gift/model/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package gift.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public class Product {
private Long id;

@NotBlank(message = "상품명은 필수 입력 값입니다.")
@Size(min = 2, max = 15, message = "상품명은 2자 이상 15자 이하로 입력해주세요.")
@Pattern(regexp = "^((?!카카오).)*$", message = "상품명에 '카카오'를 포함할 수 없습니다.")
private String name;

private Integer price;
private String image;

public Product(Long id, String name, Integer price, String imageUrl) {
this.id = id;
this.name = name;
this.price = price;
this.image = imageUrl;
}

public Long getId() { return id; }
public String getName() { return name; }
public Integer getPrice() { return price; }
public String getImage() { return image; }

public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(Integer price) {
this.price = price;
}
public void setImage(String image) {
this.image = image;
}

public void updateFields(Product partialProduct){
if (partialProduct == null) {
return;
}
if (partialProduct.name != null) {
this.name = partialProduct.name;
}
if (partialProduct.price != null) {
this.price = partialProduct.price;
}
if (partialProduct.image != null) {
this.image = partialProduct.image;
}
}
}
51 changes: 51 additions & 0 deletions src/main/java/gift/repository/ProductDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package gift.repository;

import gift.model.Product;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Repository
public class ProductDao {
private final JdbcTemplate jdbcTemplate;
private static final ProductRowMapper PRODUCT_ROW_MAPPER = new ProductRowMapper();

public ProductDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }

public void insertProduct(Product product) {
final var sql = "insert into product(name, price, image) values(?, ?, ?)";
jdbcTemplate.update(sql, product.getName(), product.getPrice(), product.getImage());
}
public List<Product> getAllProducts() {
final var sql = "select * from product";
return jdbcTemplate.query(sql, PRODUCT_ROW_MAPPER);
}
public Product getProductById(long id) {
final var sql = "select * from product where id = ?";
return jdbcTemplate.queryForObject(sql, PRODUCT_ROW_MAPPER,id);
}
public void removeProduct(long id) {
final var sql = "delete from product where id = ?";
jdbcTemplate.update(sql, id);
}
public void updateProduct(Long id, Product product, Product product_changed) {
final var sql = "UPDATE product SET name = ?, price = ?, image = ? WHERE id = ?";
product.updateFields(product_changed);
jdbcTemplate.update(sql, product.getName(), product.getPrice(), product.getImage(),id);
}
public static class ProductRowMapper implements RowMapper<Product> {
@Override
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Product(
rs.getLong("id"),
rs.getString("name"),
rs.getInt("price"),
rs.getString("image")
);
}
}
}
6 changes: 6 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
spring.application.name=spring-gift
spring.h2.console.enabled=true
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:/scheme.sql
spring.datasource.url = jdbc:h2:mem:test
spring.datasource.user = sa
spring.datasource.password =
7 changes: 7 additions & 0 deletions src/main/resources/scheme.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE product
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
price INT,
image VARCHAR(255)
);
61 changes: 61 additions & 0 deletions src/main/resources/templates/product/form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>상품 등록/수정</title>
<meta charset="UTF-8">

</head>
<body>
<h1 th:text="${product.id == null} ? '상품 추가' : '상품 수정'"></h1>

<form th:if="${product.id == null}"
th:action="@{/admin/products/add}"
th:object="${product}"
method="post">
<div>
<label>이름:</label>
<input type="text" th:field="*{name}" required />
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color:red"></div>
</div>
<div>
<label>가격:</label>
<input type="number" th:field="*{price}" required />
<div th:if="${#fields.hasErrors('price')}" th:errors="*{price}" style="color:red"></div>
</div>
<div>
<label>이미지 URL:</label>
<input type="text" th:field="*{image}" required />
<div th:if="${#fields.hasErrors('image')}" th:errors="*{image}" style="color:red"></div>
</div>
<div>
<button type="submit">저장</button>
<a th:href="@{/admin/products}">취소</a>
</div>
</form>

<form th:if="${product.id != null}"
th:action="@{/admin/products/edit}"
th:object="${product}"
method="post">
<input type="hidden" th:field="*{id}" />
<div>
<label>이름:</label>
<input type="text" name="name" th:value="${product.name}" required />
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color:red"></div>
</div>
<div>
<label>가격:</label>
<input type="number" name="price" th:value="${product.price}" required />
</div>
<div>
<label>이미지 URL:</label>
<input type="text" name="image" th:value="${product.image}" required />
</div>
<div style="margin-top: 10px;">
<button type="submit">저장</button>
<a href="/admin/products">취소</a>
</div>
</form>

</body>
</html>
Loading