1. Extension 다운로드
- Cursor 사용
- Extension Pack for Java
- Spring Boot Extension Pack
- Lombok
- Mustache
- Junit





2. Spring Boot Project 생성
- 위의 콘솔 창에 >spring 입력 : Create a Gradle Project 클릭

- Spring Boot 버전 : 3.4.3

- 개발 언어 : Java

- 그룹 ID : com.metacoding

- 프로젝트 이름 : 소문자로 작성 (Convention)

- Packaging Type : Jar

- Java Version : 21

- Dependencies
- Spring Boot DevTools : 코드 변경 시 자동 재시작
- Lombok : Annotation으로 getter, setter 생성
- Spring Web : Apache Tomcat 서버
- Mustache : Template Engine
- H2 Database : In-Memory Database
- JPA : 자바로 DB 코드 관리 가능

- 생성 폴더 지정

- Cursor 추가 설정
- console msg : Terminal → Console 변경

- 검색에 console 입력 → Java > Debug > Settings : Console에서 internalConsole 선택

- application.properties 에서 다음처럼 입력
- Console에 색깔 입히기

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

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"; } }
결과
- localhost:8080/store/list

- localhost:8080/store/detail
- 틀만 만들어 놓은 상태라, id를 추적하여 따라갈 수 없음 : 직접 주소 입력

- localhost:8080/store/save-form

- localhost:8080/store/update-form

- localhost:8080/log/list

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

log_tb

6. 기능 구현
메인 화면 = store/list

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를 기준으로 내림차순 정렬하여 출력

Header
- 상품 목록 = 메인 화면
상품 등록

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 데이터 추가


구매 목록

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>
상세 보기

수정화면 가기

상품수정
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();
}
결과
- 바나나 항목의 가격과 품목 수정 후 상품수정 클릭 시 반영


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의 값을 받아 상품명, 가격, 재고를 표시

삭제
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();
}
결과
- 바나나 항목 삭제 버튼 클릭 시 삭제, 메인 화면 이동

구매
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;

Unit Test
- Junit 사용, test 폴더 사용
- log 폴더 생성 (storev1 폴더 아래에)

- test할 파일의 클래스 생성 (이름은 무조건 다르게, test를 뒤에 붙이기 : Convention)

- 파일 작성 후 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 선택 후 결과 확인

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();
}
}
- 수행 결과

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() 자동 생성

8. 기능 완성
구매 목록

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

구매
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원)

- 상세화면에서 이름과 개수를 입력하고 구매를 클릭하면, 구매목록이 갱신


- 구매 후 상세화면에서 개수가 업데이트됨

Share article