[스프링부트] 10. Store 만들기 v1 : Prototype

문정준's avatar
Mar 19, 2025
[스프링부트] 10. Store 만들기 v1 : Prototype
 

1. Extension 다운로드

  • Cursor 사용
    • Extension Pack for Java
    • Spring Boot Extension Pack
    • Lombok
    • Mustache
    • Junit
notion image
notion image
notion image
notion image
notion image
 

2. Spring Boot Project 생성

  • 위의 콘솔 창에 >spring 입력 : Create a Gradle Project 클릭
notion image
 
  • Spring Boot 버전 : 3.4.3
notion image
 
  • 개발 언어 : Java
notion image
 
  • 그룹 ID : com.metacoding
notion image
 
  • 프로젝트 이름 : 소문자로 작성 (Convention)
notion image
 
  • Packaging Type : Jar
notion image
 
  • Java Version : 21
notion image
 
  • Dependencies
    • Spring Boot DevTools : 코드 변경 시 자동 재시작
    • Lombok : Annotation으로 getter, setter 생성
    • Spring Web : Apache Tomcat 서버
    • Mustache : Template Engine
    • H2 Database : In-Memory Database
    • JPA : 자바로 DB 코드 관리 가능
notion image
 
  • 생성 폴더 지정
notion image
 
  • Cursor 추가 설정
    • console msg : Terminal → Console 변경
notion image
 
  • 검색에 console 입력 → Java > Debug > Settings : Console에서 internalConsole 선택
notion image
 
notion image

3. Business

  • store 제작
    • 테이블 2개 생성 : store_tb, log_tb
    • store_tb : log_tb = 1 : n → log_tb에서 store_tb의 외래 키(id) 소유
notion image
 

4. 간단한 기능 구현

 

화면 구현

  • header
  • 상품 리스트
  • 상품 상세내역
  • 상품 주문
  • 주문내역 수정
  • 주문 확인 (log)
 
  • header
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>blog</title> </head> <body> <nav> <ul> <li> <a href="#">상품목록</a> </li> <li> <a href="#">상품등록</a> </li> <li> <a href="#">구매목록</a> </li> </ul> </nav> <hr>
 
  • detail
{{> layout/header}} <section> <a href="#">수정화면가기</a> <form action="#"> <button type="submit">삭제</button> </form> <div> 번호 : 1 <br> 상품명 : 바나나 <br> 상품가격 : 3000원 <br> 상품재고 : 100개 <br> </div> <form action="#"> <input type="hidden" value="1"> <input type="text" placeholder="당신은 누구인가요?"> <input type="text" placeholder="Enter 개수"> <button type="submit">구매</button> </form> </section> </body> </html>
 
  • store/list
{{>layout/header}} <section> <table border="1"> <tr> <th>번호</th> <th>상품명</th> <th></th> </tr> <tr> <td>1</td> <td>바나나</td> <td><a href="#">상세보기</a></td> </tr> <tr> <td>2</td> <td>딸기</td> <td><a href="#">상세보기</a></td> </tr> </table> </section> </body> </html>
 
  • save-form
{{> layout/header}} <section> <form action="#"> <input type="text" placeholder="상품명"><br> <input type="text" placeholder="수량"><br> <input type="text" placeholder="가격"><br> <button type="submit">상품등록</button> </form> </section> </body> </html>
 
  • update-form
{{> layout/header}} <section> <form action="#"> <input type="text" value="바나나"><br> <input type="text" value="100"><br> <input type="text" value="3000"><br> <button type="submit">상품수정</button> </form> </section> </body> </html>
 
  • log/list
{{>layout/header}} <section> <table border="1"> <tr> <th>주문번호</th> <th>상품명(조인)</th> <th>구매개수</th> <th>총가격</th> <th>구매자이름</th> </tr> <tr> <td>1</td> <td>바나나</td> <td>5개</td> <td>15000원</td> <td>ssar</td> </tr> <tr> <td>2</td> <td>바나나</td> <td>5개</td> <td>15000원</td> <td>ssar</td> </tr> <tr> <td>3</td> <td>딸기</td> <td>5개</td> <td>10000원</td> <td>cos</td> </tr> </table> </section> </body> </html>
 

기능 구현

  • HelloController : 각 페이지로 이동하는 GET 요청
package com.metacoding.storev1; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HelloController { @GetMapping("/log/list") public String t1() { return "log/list"; } @GetMapping("/store/list") public String t2() { return "store/list"; } @GetMapping("/store/detail") public String t3() { return "store/detail"; } @GetMapping("/store/save-form") public String t4() { return "store/save-form"; } @GetMapping("/store/update-form") public String t5() { return "store/update-form"; } }
 

결과

✏️

시작 주소에 주의!

이전 블로그 실습과 다르게 메인 주소가 /store/list로 정해진 프로젝트이므로,
localhost:8080으로 바로 접속 시 에러 발생 (welcome page 없음)
 
  • localhost:8080/store/list
notion image
 
  • localhost:8080/store/detail
    • 틀만 만들어 놓은 상태라, id를 추적하여 따라갈 수 없음 : 직접 주소 입력
notion image
 
  • localhost:8080/store/save-form
notion image
 
  • localhost:8080/store/update-form
notion image
 
  • localhost:8080/log/list
notion image
 
 

5. DB 설계

  • store_tb, log_tb 생성 - JPA로 자동 create
    • dummy data도 DB 데이터의 일관성을 지키도록 설계
    • log에 총 구매 stock이 10개면, 원래 계획했던 총 갯수에서 -10을 해서 store에 저장
 
  • store_tb
package com.metacoding.storev1.store; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor // JPA가 Object Mapping 시 사용 @Table(name = "store_tb") @Entity // 설정 파일에서 테이블을 생성해줌 public class Store { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private Integer price; private Integer stock; }
  • log_tb
package com.metacoding.storev1.log; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor // JPA가 Object Mapping 시 사용 @Table(name = "log_tb") @Entity // 설정 파일에서 테이블을 생성해줌 public class Log { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private Integer storeId; // 상품 ID (FK) private Integer qty; // 구매 수량 private Integer totalPrice; // qty * price private String buyer; // 구매자 이름 }
  • db/data.sql : set dummy data
-- store_tb insert into store_tb(name, stock, price) values ('바나나', 40, 3000); insert into store_tb(name, stock, price) values ('딸기', 45, 2000); -- log_tb insert into log_tb(store_id, qty, total_price, buyer) values (1, 5, 15000, 'ssar'); insert into log_tb(store_id, qty, total_price, buyer) values (1, 5, 15000, 'ssar'); insert into log_tb(store_id, qty, total_price, buyer) values (2, 5, 10000, 'cos');
 

테이블 결과

store_tb
notion image
log_tb
notion image
 

6. 기능 구현

메인 화면 = store/list

notion image
Codes
StoreController
@GetMapping("/") // mvc pattern public String list(HttpServletRequest request) { // 1. 조회 List<Store> storeList = storeService.상품목록(); // 2. req에 담기 request.setAttribute("models", storeList); // 3. 이동 return "store/list"; }
StoreService
public List<Store> 상품목록() { return storeRepository.findAll(); }
StoreRepository
public List<Store> findAll() { // 조건 : @Entity가 붙어있어야 가능 (디폴트 생성자 호출) // Setter를 만들지 않아도 대입되는 이유 : Reflection Query query = em.createNativeQuery("select * from store_tb order by id desc", Store.class); return query.getResultList(); }
store/list.mustache
{{>layout/header}} <section> <table border="1"> <tr> <th>번호</th> <th>상품명</th> <th></th> </tr> {{#models}} <tr> <td>{{id}}</td> <td>{{name}}</td> <td><a href="/store/{{id}}">상세보기</a></td> </tr> {{/models}} </table> </section> </body> </html>
결과
  • id를 기준으로 내림차순 정렬하여 출력
notion image

Header

  • 상품 목록 = 메인 화면

상품 등록

notion image
Codes
save
StoreController
@PostMapping("/store/save") public String save(@RequestParam("name") String name, @RequestParam("stock") int stock, @RequestParam("price") int price) { // 1. 저장 // System.out.println(name + ", " + stock + ", " + price); storeService.상품등록(name, stock, price); // 2. 리다이렉션 return "redirect:/"; }
StoreService
@Transactional public void 상품등록(String name, int stock, int price) { storeRepository.save(name, stock, price); }
StoreRepository
public void save(String name, int stock, int price) { Query query = em.createNativeQuery("insert into store_tb(name, stock, price) values (?, ?, ?)"); query.setParameter(1, name); query.setParameter(2, stock); query.setParameter(3, price); query.executeUpdate(); }
store/save-form.mustache
{{> layout/header}} <section> <form action="/store/save" method="POST"> <input type="text" placeholder="상품명"><br> <input type="text" placeholder="수량"><br> <input type="text" placeholder="가격"><br> <button type="submit">상품등록</button> </form> </section> </body> </html>
결과
  • 용과 항목 정보 작성 후 ‘상품등록’ 클릭 시 메인 화면 이동 및 DB 데이터 추가
    • notion image
      notion image

구매 목록

notion image
Codes
list
LogController
@GetMapping("/log") public String list(HttpServletRequest request) { List<Log> logList = logService.구매내역(); request.setAttribute("models", logList); return "log/list"; }
LogService
public List<Log> 구매내역() { return logRepository.findAll(); }
LogRepository
public List<Log> findAll() { Query query = em.createNativeQuery( "select * from log_tb order by id desc", Log.class); return query.getResultList(); }
log/list.mustache
{{>layout/header}} <section> <table border="1"> <tr> <th>주문번호</th> <th>상품명(조인)</th> <th>구매개수</th> <th>총가격</th> <th>구매자이름</th> </tr> {{#models}} <tr> <td>{{id}}</td> <td>{{storeId}}</td> <td>{{qty}}개</td> <td>{{totalPrice}}원</td> <td>{{buyer}}</td> </tr> {{/models}} </table> </section> </body> </html>

상세 보기

notion image

수정화면 가기

notion image
상품수정
Codes
StoreController
@PostMapping("/store/{id}/update") public String update(@PathVariable("id") int id, @RequestParam("name") String name, @RequestParam("stock") int stock, @RequestParam("price") int price) { // 1. 수정 storeService.상품수정(id, name, stock, price); // 2. 리다이렉션 return "redirect:/store/" + id; }
StoreService
@Transactional public void 상품수정(int id, String name, int stock, int price) { storeRepository.update(id, name, stock, price); }
StoreRepository
public void update(int id, String name, int stock, int price) { Query query = em.createNativeQuery("update store_tb set name = ?, stock = ?, price = ? where id = ?"); query.setParameter(1, name); query.setParameter(2, stock); query.setParameter(3, price); query.setParameter(4, id); query.executeUpdate(); }
결과
  • 바나나 항목의 가격과 품목 수정 후 상품수정 클릭 시 반영
notion image
notion image
Codes
StoreController
@GetMapping("/store/{id}/update-form") public String updateForm(@PathVariable("id") int id, HttpServletRequest request) { // 1. 조회 Store store = storeService.상품수정화면(id); // 2. req에 담기 request.setAttribute("model", store); // 3. 이동 return "store/update-form"; }
StoreService
public Store 상품수정화면(int id) { return storeRepository.findbyId(id); }
StoreRepository
public Store findbyId(int id) { Query query = em.createNativeQuery("select * from store_tb where id = ? order by id desc", Store.class); query.setParameter(1, id); // return : Object 객체 >> Downcasting 필요 return (Store) query.getSingleResult(); }
store/update-form.mustache
{{> layout/header}} <section> <form action="/store/{{model.id}}/update" method="POST"> <input type="text" name="name" value="{{model.name}}"><br> <input type="text" name="stock" value="{{model.stock}}"><br> <input type="text" name="price" value="{{model.price}}"><br> <button type="submit">상품수정</button> </form> </section> </body> </html>
Codes
StoreController
@GetMapping("/store/{id}") public String detail(@PathVariable("id") int id, HttpServletRequest request) { // 1. 조회 Store store = storeService.상세보기(id); // 2. req에 담기 request.setAttribute("model", store); // 3. 이동 return "store/detail"; }
StoreService
public Store 상세보기(int id) { return storeRepository.findbyId(id); }
StoreRepository
public Store findbyId(int id) { Query query = em.createNativeQuery("select * from store_tb where id = ? order by id desc", Store.class); query.setParameter(1, id); // return : Object 객체 >> Downcasting 필요 return (Store) query.getSingleResult(); }
store/detail.mustache
{{> layout/header}} <section> <a href="/store/{{model.id}}/update-form">수정화면가기</a> <form action="/store/{{model.id}}/delete" method="POST"> <button type="submit">삭제</button> </form> <div> 번호 : {{model.id}} <br> 상품명 : {{model.name}} <br> 상품가격 : {{model.price}}원 <br> 상품재고 : {{model.stock}}개 <br> </div> <form action="#"> <input type="hidden" value="{{model.id}}"> <input type="text" placeholder="당신은 누구인가요?"> <input type="text" placeholder="Enter 개수"> <button type="submit">구매</button> </form> </section> </body> </html>
결과
  • id의 값을 받아 상품명, 가격, 재고를 표시
notion image
삭제
Codes
StoreController
@PostMapping("/store/{id}/delete") public String delete(@PathVariable("id") int id) { // 1. 삭제 storeService.상품삭제(id); // 2. 리다이렉션 return "redirect:/"; }
StoreService
@Transactional public void 상품삭제(int id) { storeRepository.delete(id); }
StoreRepository
public void delete(int id) { Query query = em.createNativeQuery("delete from store_tb where id = ?"); query.setParameter(1, id); query.executeUpdate(); }
결과
  • 바나나 항목 삭제 버튼 클릭 시 삭제, 메인 화면 이동
notion image
구매
Codes
Controller

Full Code

StoreController
  • Annotation 필수 추가 (@Controller)
  • DI (Dependency Injection) : StoreService
package com.metacoding.storev1.store; import org.springframework.stereotype.Controller; 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.RequestParam; @Controller // IoC (Inversion of Control) -> hashset public class StoreController { private StoreService storeService; public StoreController(StoreService storeService) { this.storeService = storeService; } @GetMapping("/") public String list() { // 1. 조회 // 2. req에 담기 // 3. 이동 return "store/list"; } @GetMapping("/store/{id}") public String detail(@PathVariable("id") int id) { // 1. 조회 // 2. req에 담기 // 3. 이동 return "store/detail"; } @GetMapping("/store/save-form") public String saveForm() { return "store/save-form"; } @GetMapping("/store/{id}/update-form") public String updateForm(@PathVariable("id") int id) { // 1. 조회 // 2. req에 담기 // 3. 이동 return "store/update-form"; } @PostMapping("/store/{id}/delete") public String delete(@PathVariable("id") int id) { // 1. 삭제 // 2. 리다이렉션 return "redirect:/"; } @PostMapping("/store/save") public String save(@RequestParam("name") String name, @RequestParam("stock") int stock, @RequestParam("price") int price) { // 1. 저장 // System.out.println(name + ", " + stock + ", " + price); storeService.상품등록(name, stock, price); // 2. 리다이렉션 return "redirect:/"; } @PostMapping("/store/{id}/update") public String update(@PathVariable("id") int id) { // 1. 수정 // 2. 리다이렉션 return "redirect:/store/" + id; } }
StoreService
  • Annotation 필수 추가 (@Service)
  • DI (Dependency Injection) : StoreRepository
package com.metacoding.storev1.store; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class StoreService { private StoreRepository storeRepository; public StoreService(StoreRepository storeRepository) { this.storeRepository = storeRepository; } @Transactional public void 상품등록(String name, int stock, int price) { storeRepository.save(name, stock, price); } }
StoreRepository
  • Annotation 필수 추가 (@Repository)
  • DI (Dependency Injection) : EntityManager
package com.metacoding.storev1.store; import org.springframework.stereotype.Repository; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; @Repository public class StoreRepository { private EntityManager em; public StoreRepository(EntityManager em) { this.em = em; } public void save(String name, int stock, int price) { Query query = em.createNativeQuery("insert into store_tb(name, stock, price) values (?, ?, ?)"); query.setParameter(1, name); query.setParameter(2, stock); query.setParameter(3, price); query.executeUpdate(); } }
LogController
package com.metacoding.storev1.log; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class LogController { @GetMapping("/log") public String logList() { return "log/list"; } }
LogService
package com.metacoding.storev1.log; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class LogController { @GetMapping("/log") public String logList() { return "log/list"; } }
LogRepository
package com.metacoding.storev1.log; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class LogController { @GetMapping("/log") public String logList() { return "log/list"; } }
 
 

7. 테이블 Join

  • 구매 목록의 상품명은 두 테이블의 Join이 필요
    • 기존 방법으로는 클래스 내에 Mapping 불가능 : DTO 사용
 

쿼리 확인

  • 두 테이블의 pk와 fk를 join하여 필요한 컬럼만을 projection
  • SELECT lt.id, st.name, lt.qty, lt.total_price, lt.buyer FROM LOG_TB lt INNER JOIN STORE_TB st ON lt.store_id = st.id;
notion image
 

Unit Test

  • Junit 사용, test 폴더 사용
  • log 폴더 생성 (storev1 폴더 아래에)
notion image
  • test할 파일의 클래스 생성 (이름은 무조건 다르게, test를 뒤에 붙이기 : Convention)
notion image
 
  • 파일 작성 후 Run
package com.metacoding.storev1.log; import org.junit.jupiter.api.Test; public class LogRepositoryTest { @Test public void helloWorld() { System.out.println("HelloWorld"); } }
 
  • Debug Console에서 Test thread 선택 후 결과 확인
notion image
 

Unit Test 2

  • 실제 테스트할 코드 작성
    • 원본 파일에서 작성
public void findAllWithStore() { String q = "SELECT lt.id, st.name, lt.qty, lt.total_price, lt.buyer FROM LOG_TB lt INNER JOIN STORE_TB st ON lt.STORE_ID = st.ID"; Query query = em.createNativeQuery(q); List<Object[]> obsList = (List<Object[]>) query.getResultList(); // Object[] = Row for (Object[] obs : obsList) { System.out.println(obs[0] + ","); System.out.println(obs[1] + ","); System.out.println(obs[2] + ","); System.out.println(obs[3] + ","); System.out.println(obs[4]); System.out.println("================================="); } }
 
  • test 파일에서 해당 함수가 들어있는 클래스를 import 및 DI
    • Annotation으로 대체 가능
package com.metacoding.storev1.log; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; @DataJpaTest // em IoC 등록 @Import(LogRepository.class) public class LogRepositoryTest { @Autowired // DI Annotation private LogRepository logRepository; @Test public void findAllWithStore_test() { // test 용 함수는 언더바로 구분 : Convention + 매개변수에는 아무것도 넣을 수 없음 logRepository.findAllWithStore(); } }
 
  • 수행 결과
notion image
 

DTO 생성

  • DTO (Data Transfer Object) : 화면에 필요한 데이터들만 있는 Object
package com.metacoding.storev1.log; import lombok.Data; // DTO : Data Transfer Object -> 화면에 필요한 데이터만 담는 Object public class LogResponse { @Data // getter, setter, toString() public static class ListPage { private int id; private String name; private int qty; private int totalPrice; private String buyer; public ListPage(int id, String name, int qty, int totalPrice, String buyer) { this.id = id; this.name = name; this.qty = qty; this.totalPrice = totalPrice; this.buyer = buyer; } } }
 
  • DTO를 사용해서, 쿼리에서 조회한 데이터를 담은 컬렉션을 return
    • Object[] → ROW, List<Object[]> → Table (Row의 모임)
public List<LogResponse.ListPage> findAllJoinStore() { List<LogResponse.ListPage> logList = new ArrayList<>(); String q = "SELECT lt.id, st.name, lt.qty, lt.total_price, lt.buyer FROM log_tb lt INNER JOIN store_tb st ON lt.store_id = st.id"; Query query = em.createNativeQuery(q); List<Object[]> obsList = (List<Object[]>) query.getResultList(); // Object[] -> ROW for (Object[] obs : obsList) { LogResponse.ListPage log = new LogResponse.ListPage( (int) obs[0], (String) obs[1], (int) obs[2], (int) obs[3], (String) obs[4]); logList.add(log); } return logList; }
 
  • Test 메서드 하나 더 생성 : ListPage에 잘 Mapping 되었는지 확인
@Test public void findAllWithStore_test() { // test 용 함수는 언더바로 구분 : Convention + 매개변수에는 아무것도 넣을 수 없음 List<LogResponse.ListPage> logList = logRepository.findAllJoinStore(); for (ListPage listPage : logList) { System.out.println(listPage); } }
 
  • 수행 결과
    • @Data Annotation으로 인해 toString() 자동 생성
notion image
 

8. 기능 완성

구매 목록

notion image
Codes
LogController
@GetMapping("/log") public String list(HttpServletRequest request) { List<LogResponse.ListPage> logList = logService.구매목록(); request.setAttribute("models", logList); return "log/list"; }
LogService
public List<LogResponse.ListPage> 구매목록() { return logRepository.findAllJoinStore(); }
LogRepository
public List<LogResponse.ListPage> findAllJoinStore() { List<LogResponse.ListPage> logList = new ArrayList<>(); String q = "SELECT lt.id, st.name, lt.qty, lt.total_price, lt.buyer FROM log_tb lt INNER JOIN store_tb st ON lt.store_id = st.id order by lt.id desc"; Query query = em.createNativeQuery(q); List<Object[]> obsList = (List<Object[]>) query.getResultList(); // Object[] -> ROW for (Object[] obs : obsList) { LogResponse.ListPage log = new LogResponse.ListPage( (int) obs[0], (String) obs[1], (int) obs[2], (int) obs[3], (String) obs[4]); logList.add(log); } return logList; }
LogResponse
package com.metacoding.storev1.log; import lombok.Data; // DTO : Data Transfer Object -> 화면에 필요한 데이터만 담는 Object public class LogResponse { @Data // getter, setter, toString() public static class ListPage { private int id; private String name; private int qty; private int totalPrice; private String buyer; public ListPage(int id, String name, int qty, int totalPrice, String buyer) { this.id = id; this.name = name; this.qty = qty; this.totalPrice = totalPrice; this.buyer = buyer; } } }
log/list.mustache
{{>layout/header}} <section> <table border="1"> <tr> <th>주문번호</th> <th>상품명(조인)</th> <th>구매개수</th> <th>총가격</th> <th>구매자이름</th> </tr> {{#models}} <tr> <td>{{id}}</td> <td>{{name}}</td> <td>{{qty}}개</td> <td>{{totalPrice}}원</td> <td>{{buyer}}</td> </tr> {{/models}} </table> </section> </body> </html>
결과
  • log_tb, store_tb를 join하여 상품명을 받아와서 DTO에 Mapping
notion image

구매

Codes
StoreController
@PostMapping("/store/{id}/buy") public String buy(@PathVariable("id") int id, @RequestParam("buyer") String buyer, @RequestParam("qty") int qty) { // 1. 재고 갱신 (Update) storeService.재고갱신(id, qty); // 2. 구매 등록 (Insert) storeService.구매등록(id, qty, buyer); // 3. 리다이렉트 return "redirect:/log"; }
StoreService
  • 재고 갱신, 구매 등록의 2개의 서비스가 필요
@Transactional public void 재고갱신(int id, int qty) { // 1. 재고 조회 Integer stock = storeRepository.findStockbyId(id); // 2. 재고 비교 (부족하면 error) if (stock - qty < 0) throw new RuntimeException("재고가 부족합니다."); else { // 3. 재고 업데이트 int updatedStock = stock - qty; storeRepository.update(id, updatedStock); } } @Transactional public void 구매등록(int id, int qty, String buyer) { // 1. 상품 가격 조회 Integer price = storeRepository.findPricebyId(id); // 2. 가격 계산 int totalPrice = qty * price; // 3. 구매 등록 storeRepository.saveLog(id, qty, totalPrice, buyer); }
StoreRepository
// 1. 재고 계산 후 반영 public Integer findStockbyId(int id) { Query query = em.createNativeQuery("select stock from store_tb where id = ? order by id desc"); query.setParameter(1, id); // return : Object 객체 >> Downcasting 필요 try { return (Integer) query.getSingleResult(); } catch (Exception e) { // NoResultException return null; } } public void update(int id, int qty) { Query query = em.createNativeQuery("update store_tb set stock = ? where id = ?"); query.setParameter(1, qty); query.setParameter(2, id); query.executeUpdate(); } // 2. 가격 계산 후 구매이력 추가 public Integer findPricebyId(int id) { Query query = em.createNativeQuery("select price from store_tb where id = ? order by id desc"); query.setParameter(1, id); // return : Object 객체 >> Downcasting 필요 try { return (Integer) query.getSingleResult(); } catch (Exception e) { // NoResultException return null; } } public void saveLog(int storeId, int qty, int totalPrice, String buyer) { Query query = em.createNativeQuery("insert into log_tb(store_id, qty, total_price, buyer) values (?, ?, ?, ?)"); query.setParameter(1, storeId); query.setParameter(2, qty); query.setParameter(3, totalPrice); query.setParameter(4, buyer); query.executeUpdate(); }
결과
  • 용과를 추가 (25개, 5000원)
notion image
  • 상세화면에서 이름과 개수를 입력하고 구매를 클릭하면, 구매목록이 갱신
notion image
notion image
  • 구매 후 상세화면에서 개수가 업데이트됨
notion image
 
Share article

sxias