프로젝트 개요 : 이것이 취업을 위한 백엔드 개발이다 클론코딩 - 상품관리 애플리케이션
프로젝트 환경 : IntelliJ, SpringBoot, MySQL
프로젝트 코드 : https://github.com/smkim9202/ProductManagement
DB 리포지토리 생성
DatabaseProductRepository.java
package kr.co.api.product.management.infrastructure;
import org.springframework.stereotype.Repository;
@Repository
public class DatabaseProductRepository {
private JdbcTemplate jdbcTemplate;
@Autowired
public DatabaseProductRepository(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
public Product add(Product product) {
return product;
}
public Product findById(Long id){
return null;
}
public List<Product> findAll() {
return Collections.EMPTY_LIST;
}
public List<Product> findByNameContaining(String name){
return Collections.EMPTY_LIST;
}
public Product update(Product product){
return product;
}
public void delete(Long id){
}
}
데이터베이스 리포지토리 생성
ListProductRepository가 하는 일 다 가져오기 => 모든 메서드 추가
JdbcTemplate 의존성 주입
데이터베이스에 SQL 전송
상품 추가
상품 추가 쿼리
INSERT INTO products (name, price, amount) VALUES ('상품이름', 수량, 가격);
Product.java : getter 추가
public String getName() {
return name;
}
public Integer getPrice() {
return price;
}
public Integer getAmount() {
return amount;
}
getter 추가 이유
Product 인스턴스의 값을 가져와야 한다. 단, 서비스에도 getter 사용하는 코드를 넣으면 안되고 꼭 필요한 곳에서만 사용해야 한다.
DatabaseProductRepository.java : add 메서드
public Product add(Product product) {
jdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (?, ?, ?)",
product.getName(), product.getPrice(), product.getAmount());
return product;
}
JdbcTemplate.update()
값이 들어가는 부분에 물음표(?)로 표시하고, 쿼리 뒤쪽에 오는 인자들이 순서대로 물음표 안에 들어가서 데이터베이스에 저장된다.
SimpleProductService.java : 리포지토리 List에서 Database로 변경
//private ListProductRepository listProductRepository;
private DatabaseProductRepository databaseProductRepository;
ListProductRepository 주석처리 후 listProductRepository 부분 databaseProductRepository로 변경
DatabaseProductRepository.java : NamedParameterJdbcTemplate로 변경
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
public DatabaseProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate){
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
public Product add(Product product) {
SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);
namedParameterJdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)",
namedParameter);
return product;
}
NamedParameterJdbcTemplate로 변경
SQL 쿼리 보낼 때 물음표로 매개변수를 매핑하지 않고, 매개변수 이름을 통해 SQL 쿼리와 값을 매핑
순서가 바뀌거나 매개변수 수가 많은 경우에도 헷갈리지 않음
BeanPropertySqlParameterSource
인자 객체의(Product) getter를 통해 SQL 쿼리의 매개변수를 매핑시켜주는 객체
DatabaseProductRepository.java : id추가 위한 KeyHolder 추가
public Product add(Product product) {
KeyHolder keyHolder = new GeneratedKeyHolder();
SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);
namedParameterJdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)",
namedParameter, keyHolder);
Long generatedId = keyHolder.getKey().longValue();
product.setId(generatedId);
return product;
}
KeyHolder
해당 객체를 생성 후 NamedParameterJdbcTemplate의 update 메서드 매개변수로 넘겨주면 id가 담겨 온다.
Long 타입의 id 값을 가져오고, Product에 해당 id를 지정하는 코드가 담아 있는 객체다.
테스트
상품 조회
id로 조회
상품 id 조회 쿼리
SELECT id, name, price, amount FROM products WHERE id=id번호;
DatabaseProductRepository.java : findById 메서드
public Product findById(Long id){
SqlParameterSource namedParameter = new MapSqlParameterSource("id", id);
Product product = namedParameterJdbcTemplate.queryForObject(
"SELECT id, name, price, amount FROM products WHERE id="id,
namedParameter, new BeanPropertyRowMapper<>(Product.class)
);
return product;
}
MapSqlParameterSource
Product 같은 객체를 Map 형태로 Key-Value 형태를 매핑할 수 있다.
namedParameterJdbcTemplate.queryForObject()
첫 번째 인자 : SQL 쿼리
두 번째 인자 : namedParameter
세 번째 인자 : 조회된 상품 정보를 Product 인스턴스로 변환해주는 Mapper
Product.java : setter 추가
public void setName(String name) {
this.name = name;
}
public void setPrice(Integer price) {
this.price = price;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
BeanPropertyRowMapper 적상 작동 조건
Product 인자가 없는 생성자로 Product 인스턴스 생성(인자 없는 생성자가 반드시 필요)
생성된 Product 인스턴스의 setter로 필드를 초기화(setter가 반드시 필요)
테스트
상품 전체 조회
상품 이름 조회 쿼리
SELECT * FROM products;
DatabaseProductRepository.java : findAll 메서드
public List<Product> findAll() {
List<Product> products = namedParameterJdbcTemplate.query(
"SELECT * FROM products", new BeanPropertyRowMapper<>(Product.class)
);
return products;
}
테스트
이름 부분 검색으로 조회
상품 이름 조회 쿼리
SELECT * FROM products WHERE name LIKE '%상품명%';
DatabaseProductRepository.java : findByNameContaining 메서드
public Product findById(Long id){
SqlParameterSource namedParameter = new MapSqlParameterSource("id", id);
try{
Product product = namedParameterJdbcTemplate.queryForObject(
"SELECT id, name, price, amount FROM products WHERE id=:id"
, namedParameter, new BeanPropertyRowMapper<>(Product.class));
return product;
} catch (EmptyResultDataAccessException e){
throw new EntityNotFoundException("Product를 찾지 못했습니다.");
}
}
테스트
상품 수정
상품 수정 쿼리
UPDATE products SET name=수정할상품명, price=수정할가격, amount=수정할수량 WHERE id=id번호;
DatabaseProductRepository.java : udate 메서드
public Product update(Product product){
SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);
namedParameterJdbcTemplate.update(
"UPDATE products SET name=:name, price=:price, amount=:amount WHERE id=:id",
namedParameter);
return product;
}
Product.java : getter 추가
public Long getId() {
return id;
}
getId() 추가 이유
BeanPropertySqlPrarmeterSource는 getter를 통해 값을 매핑하고, 이번에는 id값을 매핑해줘야 하기 때문에 추가
추가 하지 않을 경우 Bean property 'id' is not readable or has an invalid getter method라는 에러 발생
테스트
상품 삭제
상품 삭제 쿼리
DELETE FROM products WHERE id=id번호;
DatabaseProductRepository.java : delete 메서드
public void delete(Long id){
SqlParameterSource namedParameter = new MapSqlParameterSource("id", id);
namedParameterJdbcTemplate.update(
"DELETE FROM products WHERE id=:id",
namedParameter);
}
테스트
상품을 삭제한다고 비어 있는 id 값이 채워지는 것은 아니고, 필요시 AUTO_INCREMENT로 자동 생성되는 id 역시 1씩 줄어들게 로직을 구현해야 한다. 그러나 데이터가 많이 쌓여 있을 경우 id값을 조절하는 건 엄청난 리소스가 든다. 또한 실무에서는 Soft Delete 방법을 사용하여 내부적으로 삭제를 안하고 있을 수 도 있다.
'프로젝트 > clone coding' 카테고리의 다른 글
[백엔드 개발 : ProductManagement] 리팩토링과 테스트 코드 (0) | 2025.04.11 |
---|---|
[백엔드 개발 : ProductManagement] 클래스 추상화 (0) | 2025.04.10 |
[백엔드 개발 : ProductManagement] 상품 관리 애플리케이션 DB 연동(MySQL) (0) | 2025.04.04 |
[백엔드 개발 : ProductManagement] 전역 예외 핸들러 추가 (0) | 2025.03.27 |
[백엔드 개발 : ProductManagement] 유효성 검사 추가 (0) | 2025.03.20 |