-
Notifications
You must be signed in to change notification settings - Fork 122
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주차 과제(1단계~3단계) #203
base: main
Are you sure you want to change the base?
Changes from 52 commits
999696c
bdf237c
bd50b26
352aecc
f9282b6
80e6352
d06d515
ff0e728
04a1fcd
ed6b29e
78bee3b
d1dd665
1aa5601
9aaa919
5610a97
62517f9
f9d49c3
a82f2e2
dbb9494
5264f0e
beb1782
677c141
d540c4b
5e5d08a
03341ba
2561f41
b40b8d2
355930f
3abb10d
3cabdd5
d516dac
92967b0
234fbfb
c450ce5
fe2d31c
7f1e12d
a910172
a6e717f
d6c3e48
86e28f9
21f0a4e
611b87e
bd260f1
fd1a317
5d2b030
cb07c8f
16cf41a
16fc2f4
07c17a8
ed99804
2ed0c50
92b470b
c1a67f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||||||||
package gift.controller; | ||||||||||||
|
||||||||||||
import gift.model.Product; | ||||||||||||
import gift.model.ProductDao; | ||||||||||||
|
||||||||||||
import java.util.List; | ||||||||||||
|
||||||||||||
import org.springframework.http.ResponseEntity; | ||||||||||||
import org.springframework.stereotype.Controller; | ||||||||||||
import org.springframework.web.bind.annotation.DeleteMapping; | ||||||||||||
import org.springframework.web.bind.annotation.GetMapping; | ||||||||||||
import org.springframework.web.bind.annotation.PathVariable; | ||||||||||||
import org.springframework.web.bind.annotation.PostMapping; | ||||||||||||
import org.springframework.web.bind.annotation.PutMapping; | ||||||||||||
import org.springframework.web.bind.annotation.RequestBody; | ||||||||||||
import org.springframework.web.bind.annotation.RequestParam; | ||||||||||||
|
||||||||||||
@Controller | ||||||||||||
public class ProductController { | ||||||||||||
|
||||||||||||
private final ProductDao productDao; | ||||||||||||
|
||||||||||||
public ProductController(ProductDao productDao) { | ||||||||||||
this.productDao = productDao; | ||||||||||||
} | ||||||||||||
|
||||||||||||
@GetMapping("/admin") | ||||||||||||
public String admin() { | ||||||||||||
return "adminPage"; | ||||||||||||
} | ||||||||||||
|
||||||||||||
@GetMapping("/products") | ||||||||||||
public ResponseEntity<List<ProductResponse>> getProducts() { | ||||||||||||
var products = productDao.findAll(); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이러한 부분은 inline 처리해도 좋을 것 같아요ㅎㅎ
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 알겠습니다! |
||||||||||||
var response = products.stream() | ||||||||||||
.map(ProductResponse::from) | ||||||||||||
.toList(); | ||||||||||||
return ResponseEntity.ok(response); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@GetMapping("/products/{id}") | ||||||||||||
public ResponseEntity<ProductResponse> getProduct(@PathVariable("id") Long id) { | ||||||||||||
var product = productDao.findById(id) | ||||||||||||
.orElseThrow(() -> new IllegalArgumentException("해당 상품이 존재하지 않습니다.")); | ||||||||||||
var response = ProductResponse.from(product); | ||||||||||||
return ResponseEntity.ok(response); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@PostMapping("/products") | ||||||||||||
public ResponseEntity<String> createProduct(@RequestBody ProductRequest request) { | ||||||||||||
productDao.save(request.toEntity()); | ||||||||||||
return ResponseEntity.ok().body("Product created successfully."); | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
||||||||||||
@PutMapping("/products/{id}") | ||||||||||||
public ResponseEntity<String> updateProduct(@PathVariable("id") Long id, | ||||||||||||
@RequestBody ProductRequest request) { | ||||||||||||
productDao.findById(id) | ||||||||||||
.orElseThrow(() -> new IllegalArgumentException("해당 상품이 존재하지 않습니다.")); | ||||||||||||
productDao.update(request.toEntity(id)); | ||||||||||||
return ResponseEntity.ok().body("Product updated successfully."); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@DeleteMapping("/products/{id}") | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 적절한 HTTP Method와 Status를 사용해주셨네요! 👍 |
||||||||||||
public ResponseEntity<Void> deleteProduct(@PathVariable("id") Long id) { | ||||||||||||
productDao.findById(id) | ||||||||||||
.orElseThrow(() -> new IllegalArgumentException("해당 상품이 존재하지 않습니다.")); | ||||||||||||
productDao.deleteById(id); | ||||||||||||
return ResponseEntity.noContent().build(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@GetMapping("/products/paging") | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 페이징 처리 관련하여 작업해주신 부분을 보고 몇 가지 코멘트를 통합하여 남겨드릴께요~
따라서 상품 목록 조회의 응답 클래스용 DTO가 다음과 같은 정보들을 담고 있으면 됩니다ㅎㅎ
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에 response에 count를 넣을까 고민했었는데, 전체 데이터의 개수를 받아오는 api, 페이징 조회 api 두 api의 용도가 다르다고 생각해서 나눴었는데, 정보를 합쳐서 보내는 군요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 궁금한점이 있습니다. count 쿼리를 날릴때 full scan을 한다고 알고 있는데 페이지 하나를 요청할 때 마다 count 를 받으면 리소스 낭비가 너무 심하지 않나요? 감사합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MySQL을 기준으로 설명드리면, PK가 하나의 프라이머리 인덱스처럼 동작하게 되는데요ㅎㅎ |
||||||||||||
public ResponseEntity<List<ProductResponse>> getProductsPaging(@RequestParam("page") int page, | ||||||||||||
@RequestParam("size") int size) { | ||||||||||||
var products = productDao.findPaging(page, size); | ||||||||||||
var response = products.stream() | ||||||||||||
.map(ProductResponse::from) | ||||||||||||
.toList(); | ||||||||||||
return ResponseEntity.ok(response); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@GetMapping("/products/count") | ||||||||||||
public ResponseEntity<Long> getProductsCount() { | ||||||||||||
return ResponseEntity.ok(productDao.count()); | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package gift.controller; | ||
|
||
|
||
import gift.model.Product; | ||
|
||
public record ProductRequest( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
String name, | ||
Integer price, | ||
String imageUrl | ||
) { | ||
|
||
public Product toEntity() { | ||
return Product.create(null, name(), price(), imageUrl()); | ||
} | ||
|
||
public Product toEntity(Long id) { | ||
return Product.create(id, name(), price(), imageUrl()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package gift.controller; | ||
|
||
import gift.model.Product; | ||
|
||
public record ProductResponse( | ||
Long id, | ||
String name, | ||
Integer price, | ||
String imageUrl | ||
) { | ||
|
||
public static ProductResponse from(Product product) { | ||
return new ProductResponse(product.getId(), product.getName(), product.getPrice(), | ||
product.getImageUrl()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package gift.model; | ||
|
||
|
||
public class Product { | ||
|
||
private Long id; | ||
private String name; | ||
private Integer price; | ||
private String imageUrl; | ||
|
||
public Product(Long id, String name, Integer price, String imageUrl) { | ||
this.id = id; | ||
this.name = name; | ||
this.price = price; | ||
this.imageUrl = imageUrl; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public Integer getPrice() { | ||
return price; | ||
} | ||
|
||
public String getImageUrl() { | ||
return imageUrl; | ||
} | ||
|
||
public static Product create(Long id, String name, Integer price, String imageUrl) { | ||
return new Product(id, name, price, imageUrl); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package gift.model; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터페이스는 해당 애노테이션을 등록해줄 필요가 없습니다ㅎㅎ |
||
public interface ProductDao { | ||
|
||
void save(Product product); | ||
|
||
Optional<Product> findById(Long id); | ||
|
||
List<Product> findAll(); | ||
|
||
void deleteById(Long id); | ||
|
||
void update(Product product); | ||
|
||
List<Product> findPaging(int page, int size); | ||
|
||
Long count(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package gift.model; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
import javax.sql.DataSource; | ||
import org.springframework.context.annotation.Primary; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.jdbc.core.RowMapper; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@Primary | ||
public class ProductJdbcTemplateDao implements ProductDao { | ||
|
||
private static final String SQL_INSERT = "INSERT INTO products(name, price, image_url) VALUES (?, ?, ?)"; | ||
private static final String SQL_SELECT_BY_ID = "SELECT id, name, price, image_url FROM products WHERE id = ?"; | ||
private static final String SQL_SELECT_ALL = "SELECT id, name, price, image_url FROM products"; | ||
private static final String SQL_DELETE_BY_ID = "DELETE FROM products WHERE id = ?"; | ||
private static final String SQL_UPDATE = "UPDATE products SET name = ?, price = ?, image_url = ? WHERE id = ?"; | ||
private static final String SQL_SELECT_PAGING = "SELECT id, name, price, image_url FROM products LIMIT ? OFFSET ?"; | ||
private static final String SQL_COUNT = "SELECT COUNT(*) FROM products"; | ||
|
||
|
||
private final JdbcTemplate jdbcTemplate; | ||
private final RowMapper<Product> productRowMapper = new ProductRowMapper(); | ||
|
||
public ProductJdbcTemplateDao(DataSource dataSource) { | ||
this.jdbcTemplate = new JdbcTemplate(dataSource); | ||
} | ||
|
||
@Override | ||
public void save(Product product) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 상황에서는 insert를 사용하는 것이 더욱 명시적일 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 또는 다음과 같이 내부 구현을 변경하여 save라는 네이밍을 유지해도 괜찮을 것 같습니다ㅎㅎ
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네이밍에 대한 지적 감사합니다! |
||
jdbcTemplate.update(SQL_INSERT, product.getName(), product.getPrice(), | ||
product.getImageUrl()); | ||
} | ||
|
||
@Override | ||
public Optional<Product> findById(Long id) { | ||
try { | ||
Product product = jdbcTemplate.queryForObject(SQL_SELECT_BY_ID, | ||
productRowMapper, id); | ||
return Optional.of(product); | ||
} catch (Exception e) { | ||
return Optional.empty(); | ||
} | ||
} | ||
|
||
@Override | ||
public List<Product> findAll() { | ||
return jdbcTemplate.query(SQL_SELECT_ALL, | ||
productRowMapper); | ||
} | ||
|
||
@Override | ||
public void deleteById(Long id) { | ||
jdbcTemplate.update(SQL_DELETE_BY_ID, id); | ||
} | ||
|
||
@Override | ||
public void update(Product product) { | ||
jdbcTemplate.update(SQL_UPDATE, product.getName(), product.getPrice(), | ||
product.getImageUrl(), product.getId()); | ||
} | ||
|
||
@Override | ||
public List<Product> findPaging(int page, int size) { | ||
int offset = (page) * size; | ||
return jdbcTemplate.query(SQL_SELECT_PAGING, productRowMapper, size, offset); | ||
} | ||
|
||
@Override | ||
public Long count() { | ||
return jdbcTemplate.queryForObject(SQL_COUNT, Long.class); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package gift.model; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class ProductMapDao implements ProductDao { | ||
|
||
private final Map<Long, Product> database = new ConcurrentHashMap<>(); | ||
|
||
@Override | ||
public void save(Product product) { | ||
Product newProduct = Product.create(Long.valueOf(database.size() + 1), product.getName(), | ||
product.getPrice(), product.getImageUrl()); | ||
database.put(newProduct.getId(), newProduct); | ||
} | ||
|
||
@Override | ||
public Optional<Product> findById(Long id) { | ||
return Optional.ofNullable(database.get(id)); | ||
} | ||
|
||
@Override | ||
public List<Product> findAll() { | ||
return List.copyOf(database.values()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 데이터를 복사하여 반환하는 부분 좋네요! 👍 |
||
} | ||
|
||
@Override | ||
public void deleteById(Long id) { | ||
database.remove(id); | ||
} | ||
|
||
@Override | ||
public void update(Product product) { | ||
database.replace(product.getId(), product); | ||
} | ||
|
||
@Override | ||
public List<Product> findPaging(int page, int size) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public Long count() { | ||
return Long.valueOf(database.size()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package gift.model; | ||
|
||
import org.springframework.jdbc.core.RowMapper; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
|
||
public class ProductRowMapper implements RowMapper<Product> { | ||
|
||
@Override | ||
public Product mapRow(ResultSet resultSet, int rowNum) throws SQLException { | ||
return new Product( | ||
resultSet.getLong("id"), | ||
resultSet.getString("name"), | ||
resultSet.getInt("price"), | ||
resultSet.getString("image_url") | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
spring.application.name=spring-gift | ||
# h2-console ??? ?? | ||
spring.h2.console.enabled=true | ||
# db url | ||
spring.datasource.url=jdbc:h2:mem:test | ||
spring.sql.init.schema-locations=classpath:/static/sql/schema.sql | ||
spring.sql.init.data-locations=classpath:/static/sql/data.sql | ||
spring.sql.init.mode=always |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 메서드처럼
뷰를 처리
하기 위한 API와 아래처럼데이터를 처리
하는 역할의 API가 하나의 클래스로 응집되어 있는 것 같네요! 이 둘을 분리하면 어떨까요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뷰를 처리하기 위한 api가 하나라서 같이 묶어놨었는데, 분리해서 추후 확장성을 고려하도록 하겠습니다!