Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

부산대 BE_박수빈_1주차 과제(3단계) #192

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d507b5a
commit test by intellij
kuma4188 Jun 24, 2024
88a8c34
feat() : create getter , setter
kuma4188 Jun 25, 2024
98364e4
feat() : create ProductController class
kuma4188 Jun 25, 2024
2b0b99d
feat() : get product
kuma4188 Jun 25, 2024
408d83d
feat() : add product
kuma4188 Jun 25, 2024
0a5204a
fix : remove unimportant code
kuma4188 Jun 25, 2024
f9002a7
fix : create constructor and object
kuma4188 Jun 25, 2024
c398921
initial
kuma4188 Jun 25, 2024
1c0fe9f
initialize
kuma4188 Jun 26, 2024
5975ea0
start step2
kuma4188 Jun 27, 2024
49f08e2
feat : ProductWebController으로 타임리프를 이용하기 위한 클래스를 생성
kuma4188 Jun 27, 2024
7984606
feat : ProductList 메인 화면 생성
kuma4188 Jun 27, 2024
782460f
feat : ProductDetail 상품 조회
kuma4188 Jun 27, 2024
a9fc3ec
feat : addProduct 상품 추가
kuma4188 Jun 27, 2024
f143517
feat : editProduct 상품 수정
kuma4188 Jun 27, 2024
5f2b48e
feat : 404 오류 페이지 생성
kuma4188 Jun 27, 2024
41746f6
doc : readme.md 생성
kuma4188 Jun 27, 2024
431407d
refactor : ProducWebController 수정
kuma4188 Jun 28, 2024
cff1d4a
refactor : Application 수정
kuma4188 Jun 28, 2024
75d86ee
refactor : product 수정
kuma4188 Jun 28, 2024
40baf95
feat : ProductRepository 생성
kuma4188 Jun 28, 2024
ecf9a78
feat : ProductService 생성
kuma4188 Jun 28, 2024
c6958fe
feat : data.sql 생성
kuma4188 Jun 28, 2024
07be71a
feat : schema.sql 생성
kuma4188 Jun 28, 2024
9bca0b5
refacor : propertise 수정
kuma4188 Jun 28, 2024
4e0e9d9
remove ProductController
kuma4188 Jun 28, 2024
fd85414
doc : readme 생성
kuma4188 Jun 28, 2024
3d41a71
fix : 스네이크케이스를 카멜케이스로 변경
kuma4188 Jun 28, 2024
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# spring-gift-product
# spring-gift-product

1. database로 연결하는 과정에서
gradle 버전 , jdk 버전 등등에 의해 상당히 영향을 많이 받았고
또한 의존성 충돌 , propertise 충돌에 의해서 실행되지 않는 일이
많았습니다. 그래서 더 과제가 힘들었던것 같습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버전 충돌때문에 많은 일들이 있으셨군요 ㅠ_ㅠ 1주차 고생 많으셨어요
저는 보통 버전 문제가 있을 때 이렇게 찾아보곤 합니다.

  1. 자바 버전과 스프링, 사용한 라이브러리의 호환성을 구글링해서 체크
  2. ./gradlew dependencies으로 의존성 트리를 체크해서 충돌되는 라이브러리를 파악해서 버전 조정하기
  3. 클린 빌드 ./gradlew clean build 해보기
  4. IntelliJ 캐시 삭제 후 재부팅
  5. PC 재부팅...



4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.data:spring-data-jpa'
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 @@ -26,6 +28,8 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}



tasks.named('test') {
useJUnitPlatform()
}
5 changes: 5 additions & 0 deletions src/main/java/gift/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;


@SpringBootApplication
@EnableJpaRepositories(basePackages = "gift")
@EntityScan("gift")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/gift/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gift;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "product")
public class Product {
kuma4188 marked this conversation as resolved.
Show resolved Hide resolved
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
@Column(name = "image_url", nullable = false)
private String imageUrl;

public Product() {

}


// create getter
public Long getId() {
return id;
}

public String getImageUrl() {
return imageUrl;
}

public int getPrice() {
return price;
}

public String getName() {
return name;
}

// create setter
public void setId(Long id) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 id 필드는 데이터베이스에서 자동으로 생성(auto_increment) & 관리되고 있어요.
이를 수동으로 설정하는 것은 데이터 무결성에 문제를 일으킬 수 있습니다.
따라서 id 필드에 대한 setter 메서드는 만들지 않는 것이 좋습니다.

this.id = id;
}

public void setImageUrl(String imageUrl) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하지 않는 getter, setter는 지우는 것이 좋습니다.!

this.imageUrl = imageUrl;
}

public void setPrice(int price) {
this.price = price;
}

public void setName(String name) {
this.name = name;
}
}
6 changes: 6 additions & 0 deletions src/main/java/gift/ProductRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gift;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}
45 changes: 45 additions & 0 deletions src/main/java/gift/ProductService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gift;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional // 서비스 메서드에 대한 트랜잭션 관리

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

트랜잭션 관리를 위해 @Transactional을 사용하셨네요! 👍

public class ProductService {

private final ProductRepository productRepository;

@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

@Transactional(readOnly = true) // 읽기 전용 트랜잭션

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

읽기 전용 트랜잭션은 성능 최적화와 코드의 의도를 명확히 하는 데 도움이 될 수 있지만 반드시 필요한 것은 아닙니다.
코드의 단순성을 유지하려면 사용하지 않을 수도 있습니다! (빠져도 기능적으로 문제가 없답니다 :D)

public List<Product> getAllProducts() {
return productRepository.findAll();
}

@Transactional(readOnly = true)
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid product Id:" + id));
}

@Transactional

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveProduct, updateProduct, deleteProduct 메서드는 JpaRepository의 메서드를 호출하고 있는데,
JpaRepository의 save()deleteById() 메서드는 이미 트랜잭션을 관리합니다.

따라서 이 메서드들에 별도로 @Transactional을 붙일 필요가 없습니다. 😀

public void saveProduct(Product product) {
productRepository.save(product);
}

@Transactional
public void updateProduct(Long id, Product product) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

존재하지 않는 제품의 id로 요청하는 경우 어떻게 될까요?

product.setId(id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앞서 말씀드린 것 처럼, 객체에 수동으로 id를 설정하고 저장하는 것은 권장하지 않습니다.
기존 엔티티를 데이터베이스에서 가져오지 않고 새로 생성한 객체를 저장하면, 데이터베이스와의 일관성이 깨질 수 있습니다.
(데이터베이스에 저장된 다른 필드 값이 덮어씌워질 위험)

데이터베이스에서 기존 엔티티를 가져온 다음에 수정해볼까요?

productRepository.save(product);
}

@Transactional
public void deleteProduct(Long id) {
productRepository.deleteById(id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 마찬가지로 존재하지 않는 제품의 id로 삭제 요청하는 경우 어떻게 될까요?

}
}
65 changes: 65 additions & 0 deletions src/main/java/gift/ProductWebController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gift;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/web/products")
public class ProductWebController {

private final ProductService productService;

@Autowired

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자가 하나만 있는 경우, @Autowired 어노테이션을 생략해도 자동으로 주입됩니다!

public ProductWebController(ProductService productService) {
this.productService = productService;
}

@GetMapping("/list")
public String getAllProducts(Model model) {
List<Product> products = productService.getAllProducts();
model.addAttribute("products", products);
return "productList"; // product list view의 이름을 반환
}

@GetMapping("/detail/{id}")
public String getProductById(@PathVariable("id") Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product", product);
return "productDetail"; // product detail view의 이름을 반환
}

@GetMapping("/add")
public String addProductForm(Model model) {
model.addAttribute("product", new Product());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터베이스 엔티티이자 도메인 객체인 Product 를 직접 뷰에 노출시키고 있습니다.
이는 보안상의 문제가 발생할 수 있고, 코드 유지보수성도 떨어집니다.
컨트롤러단에서 엔티티를 받지 말고, 대신 DTO(Data Transfer Object)를 만들어 사용해볼까요?

return "addProduct"; // add product form view의 이름을 반환
}

@PostMapping("/add")
public String addProduct(@ModelAttribute Product product) {
productService.saveProduct(product);
return "redirect:/web/products/list"; // 상품 목록 페이지로 리다이렉트
}

@GetMapping("/edit/{id}")
public String editProductForm(@PathVariable("id") Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product", product);
return "editProduct"; // edit product form view의 이름을 반환
}

@PostMapping("/edit/{id}")
public String updateProduct(@PathVariable("id") Long id, @ModelAttribute Product product) {
productService.updateProduct(id, product);
return "redirect:/web/products/list"; // 상품 목록 페이지로 리다이렉트
}

@GetMapping("/delete/{id}")
public String deleteProduct(@PathVariable("id") Long id) {
productService.deleteProduct(id);
return "redirect:/web/products/list"; // 상품 목록 페이지로 리다이렉트
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google Java Style Guide 규칙 중 하나가 적용되지 않았네요!

  • 모든 소스 파일은 빈 줄로 끝나야 합니다.

IntelliJ 설정으로 쉽게 처리할 수 있으니 활성화하는 것을 추천드립니다.

프로젝트 전체적으로 코드 컨벤션이 적용 안된 것 같아요 (들여쓰기, 줄 띄우기, 공백 등)
한 번 체크 부탁드릴게요!

16 changes: 16 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
spring.application.name=spring-gift

spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.initialization-mode=always
spring.sql.init.schema-locations=classpath:/model/schema.sql
spring.sql.init.data-locations=classpath:/model/data.sql
logging.level.org.springframework.transaction=DEBUG

spring.jpa.hibernate.ddl-auto=update
3 changes: 3 additions & 0 deletions src/main/resources/model/data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INSERT INTO product (name, price, image_url) VALUES ('아이스 카페 아메리카노 T', 4500, 'https://st.kakaocdn.net/product/gift/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg');
INSERT INTO product (name, price, image_url) VALUES ('아이스 카페 라테 T', 5500, 'https://item.elandrs.com/upload/prd/orgimg/088/2005488088_0000001.jpg?w=750&h=&q=100');
INSERT INTO product (name, price, image_url) VALUES ('뜨거운 아이스 아메리카노 T', 6500, 'https://dimg.donga.com/wps/NEWS/IMAGE/2017/02/06/82727038.1.jpg');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

초기 데이터 설정 👏

8 changes: 8 additions & 0 deletions src/main/resources/model/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AUTO_INCREMENT 👍

name VARCHAR(255) NOT NULL,
price INT NOT NULL,
image_url VARCHAR(1000)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image_url에 NOT NULL 제약 조건이 빠졌네요.
엔티티와 일치하도록 둘 중 하나를 수정해볼까요?

);


11 changes: 11 additions & 0 deletions src/main/resources/templates/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>404 - Not Found</title>
</head>
<body>
<h1>404 - Not Found</h1>
<p>The requested resource was not found on this server.</p>
<a href="/products">Back to List</a>
</body>
</html>
16 changes: 16 additions & 0 deletions src/main/resources/templates/addProduct.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add Product</title>
</head>
<body>
<h1>Add Product</h1>
<form th:action="@{/web/products/add}" th:object="${product}" method="post">
<p>Name: <input type="text" th:field="*{name}"/></p>
<p>Price: <input type="text" th:field="*{price}"/></p>
<p>Image URL: <input type="text" th:field="*{imageUrl}"/></p>
<button type="submit">Add Product</button>
</form>
<a href="/web/products/list">Back to List</a>
</body>
</html>
17 changes: 17 additions & 0 deletions src/main/resources/templates/editProduct.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Edit Product</title>
</head>
<body>
<h1>Edit Product</h1>
<form th:action="@{/web/products/edit/{id}(id=${product.id})}" th:object="${product}" method="post">
<input type="hidden" th:field="*{id}"/>
<p>Name: <input type="text" th:field="*{name}"/></p>
<p>Price: <input type="text" th:field="*{price}"/></p>
<p>Image URL: <input type="text" th:field="*{imageUrl}"/></p>
<button type="submit">Update Product</button>
</form>
<a href="/web/products/list">Back to List</a>
</body>
</html>
15 changes: 15 additions & 0 deletions src/main/resources/templates/productDetail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Product Detail</title>
</head>
<body>
<h1>Product Detail</h1>
<p>ID: <span th:text="${product.id}">1</span></p>
<p>Name: <span th:text="${product.name}">Name</span></p>
<p>Price: <span th:text="${product.price}">Price</span></p>
<img th:src="${product.imageUrl}" alt="product image" width="200"/>
<br>
<a href="/web/products/list">Back to List</a>
</body>
</html>
34 changes: 34 additions & 0 deletions src/main/resources/templates/productList.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Product List</title>
</head>
<body>
<h1>Product List</h1>
<a href="/web/products/add">Add New Product</a>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Image</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${products}">
<td th:text="${product.id}">1</td>
<td th:text="${product.name}">Name</td>
<td th:text="${product.price}">Price</td>
<td><img th:src="${product.imageUrl}" alt="product image" width="100"/></td>
<td>
<a th:href="@{/web/products/detail/{id}(id=${product.id})}">View</a>
<a th:href="@{/web/products/edit/{id}(id=${product.id})}">Edit</a>
<a th:href="@{/web/products/delete/{id}(id=${product.id})}">Delete</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>