본문 바로가기
스프링

[JPA 활용 3]

by 메이02 2024. 12. 24.

[상품 도메인 개발]

(1) 비지니스 로직

  • addStock() 메서드는 파라미터로 넘어온 수만큼 재고를 늘린다.
  • removeStock()  메서드는 파라미터로 넘어온 수만큼 재고를 줄인다.

 

(2) 상품 리포지토리

  • save() 
    • id 가 없으면 신규로 보고  persist() 실행
    • id가 있으면, 이미 데이터베이스에 저장된 엔티티를 수정한다고 보고, merge() 실행

 

 

(2) 상품 서비스


[주문 도메인 개발]

(1) 주문(Order) 엔티티 

  • 생성 메서드 createOrder() : 주문 엔티티를 생성할 때 사용한다.
    • 주문 회원, 배송정보, 주문상품의 정보를 받아서 실제 주문 엔티티를 생성한다.
  • 주문 취소 cancel() : 주문 취소시 사용한다.
    • 주문 상태를 취소로 변경하고 주문상품에 주문 취소를 알린다. 만약 이미 배송을 완료한 상품이면 주문을 취소하지 못하도록 예외를 발생시킨다.
  • 전체 주문 가격 조회: 주문 시 사용한 전체 주문 가격을 조회한다.
    • 전체 주문 가격을 알려면 각각의 주문상품 가격을 알아야 한다. 로직을 보면 연관된 주문상품들의 가격을 조회해서 더한 값을 반환한다. (실무에서는 주로 주문에 전체 주문 가격 필드를 두고 역정규화 한다.)

 

 

(2) 주문상품(OrderItem) 엔티티

  • 생성 메서드 createOrderItem() : 주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성한다.
    • 그리고  item.removeStock(count) 를 호출해서 주문한 수량만큼 상품의 재고를 줄인다
  • 주문 취소  cancel() : getItem().addStock(count) 를 호출해서 취소한 주문 수량만큼 상품의 재고를 증가시킨다.
  • 주문 가격 조회  getTotalPrice() : 주문 가격에 수량을 곱한 값을 반환한다.

 

 

(3) 주문 리포지토리

 

 

(4) 주문 서비스

  • 주문 order() : 주문하는 회원 식별자, 상품 식별자, 주문 수량 정보를 받아서 실제 주문 엔티티를 생성한 후 저 장한다.
  • 주문 취소 cancelOrder()  : 주문 식별자를 받아서 주문 엔티티를 조회한 후 주문 엔티티에 주문 취소를 요청 한다.
  • 주문 검색  findOrders() : ` OrderSearch ` 라는 검색 조건을 가진 객체로 주문 엔티티를 검색한다. 
⚠️@NoArgsConstructor로 protected 만들기
: @NoArgsConstructor로 지정하면, 그냥 new Order()로 직접 생성이 안된다. 주어진 생성 메서드를 사용하여 만들어야한다

 

 

✅참고

주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다.

서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다.

이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴이라고 한다

↔️ 반대로 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트 랜잭션 스크립트 패턴이라고 한다

 

 

(5) 주문 기능 테스트 (ctrl + shift + T )

상품 주문 테스트
Given 절에서 테스트를 위한 회원과 상품을 만들고
When 절에서 실 제 상품을 주문하고
Then 절에서 주문 가격이 올바른지, 주문 후 재고 수량이 정확히 줄었는지 검증한다

 

재고수량 초과 테스트
orderCount = 11 로 재고보다 1권 더 많은 수량을 주문했다. 주문 초과로 예외가 발생한다

 

주문 취소 테스트
주문을 취소하려면 먼저 주문을 해야 한다. Given 절에서 주문하고 When 절에서 해당 주문을 취소했다.
Then 절에서 주문상태가 주문 취소 상태인지( CANCEL ) 취소한 만큼 재고가 증가했는지 검증한다.

 

 

(6) 주문 검색 기능

 

JPA에서 동적 쿼리를 어떻게 해결해야 할까?

 

JPQL로 처리
JPQL 쿼리를 문자로 생성하기는 번거롭고, 실수로 인한 버그가 충분히 발생할 수 있다

 

JPA Criteria로 처리
JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 너무 복잡하다. 결국 다른 대안이 필요하다
➡️ Querydsl로 해결가능하지만, 지금은 설명하진 않음

 


[웹 계층 개발]

 

(1)홈 화면과 레이아웃

 

✅ 홈컨트롤러를 등록한후, html코드 만들기

 

 

💥오류발생


영상에서는 bodyHeader과 footer을 만듦으로서 문제를 해결했지만, 나의 컴퓨터에서는 해결되지 않고 같은 문제 발생함

 

 

➕ 예쁜 화면을 위해 bootstrap 다운받고, 파일에 추가해주기

 

💡 오류 해결후 정상적 화면

 

(2) 회원 등록

✅ MemberController 만들고, createMemberForm.html 코드 만들기

  •  @Valid를 적으면 Validation이 가능해짐 ➡️ javax.validation 표준을 쓸 수 있기 때문

 

 

 

(3) 회원 목록 조회

MemberController에 코드 추가후, memberList.html 생성

  • 조회한 상품을 뷰에 전달하기 위해 스프링 MVC가 제공하는 모델(Model) 객체에 보관
  • 실행할 뷰 이름을 반환
📍참고
폼 객체 vs 엔티티 직접 사용
요구사항이 정말 단순할 때는 폼 객체(MemberForm) 없이 엔티티(Member)를 직접 등록과 수정 화면에서 사용해도 됨!
하지만 화면 요구사항이 복잡해지기 시작하면, 엔티티에 화면을 처리하기 위한 기능이 점점 증가한다.
결과적으로 엔티티는 점점 화면에 종속적으로 변하고, 이렇게 화면 기능 때문에 지저분해진 엔티티는 결국 유지보수하기 어려워진다.
실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다. 화면이나 API에 맞는 폼객체나 DTO를 사용하자. 그래서 화면이나 API 요구사항을 이것들로 처리하고, 엔티티는 최대한 순수하게 유지 하자

 

 

(4) 상품 등록

✅itemController 생성후, createItemForm.html 생성

 

 

(5) 상품 목록

✅itemController에 코드 추가 후, itemList.html 생성

  • model 에 담아둔 상품 목록인 items 를 꺼내서 상품 정보를 출력

(6) 상품 수정

 

📍상품 수정 폼 이동

  1. 수정 버튼을 선택하면 /items/{itemId}/edit URL을 GET 방식으로 요청
  2. 그 결과로  updateItemForm()  메서드를 실행 ➡️  itemService.findOne(itemId) 를 호출해서 수정할 상품을 조회
  3. 조회 결과를 모델 객체에 담아서 뷰(  items/updateItemForm  )에 전달

📍상품 수정 실행

  1. 상품 수정 폼에서 정보를 수정하고 Submit 버튼을 선택 
  2. /items/{itemId}/edit URL을 POST 방식으로 요청하고 updateItem() 메서드를 실행
  3. 이때 컨트롤러에 파라미터로 넘어온  item 엔티티 인스턴스는 현재 준영속 상태다.
    • 따라서 영속성 컨텍스트의 지원을 받을 수 없고 데이터를 수정해도 변경 감지 기능은 동작X

 

(7) 변경 감지와 병합

 

📍 준영속 엔티티?
영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
(여기서는 itemService.saveItem(book) 에서 수정을 시도하는  Book 객체다. 
Book 객체는 이미 DB에 한번 저장되어서 식별자가 존재한다. 이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.)

⚠️ JPA가 관리하는 영속상태 엔티티는 변경감지가 일어나서 트랜잭션 커밋 시점에 바꿔줌
↔️ 준영속 엔티티는 JPA가 관리하지 않아서, 값을 바꿔도 DB업데이트가 일어나지 않음

 

 

💡준영속 엔티티를 수정하는 2가지 방법

① 변경 감지 기능 사용

  • 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법
  • 트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 → 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해서 데이터베이스에 UPDATE SQL 실행

 

② 병합(merge) 사용

: 병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능

 

📍 병합 동작 방식
(1) merge를 실행한다
(2) 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다
    (2-1) 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다
(3) 조회한 영속 엔티티에 member엔티티의 값을 채워넣는다.
(이때, mergeMember의 “회원1”이라는 이름이 “회원명변경”으로 바뀐다)
(4) 영속 상태인 mergeMember를 반환한다

[간단 정리]
➡️준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
➡️ 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다.(병합한다.)
➡️ 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행

⚠️주의
:
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
병합시 값이 없으면 ` null ` 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
⚠️ 실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다

 

 

✅ 가장 좋은 해결 방법

: 엔티티를 변경할 때는 항상 변경 감지를 사용하세요 

  • 컨트롤러에서 어설프게 엔티티를 생성하지 마세요.
  • 트랜잭션이 있는 서비스 계층에 식별자( id  )와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto)
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하세요.
  • 트랜잭션 커밋 시점에 변경 감지가 실행됩니다.

코드 수정해주기

 

(8) 상품 주문

 

📍주문 폼 이동

  1. 메인 화면에서 상품 주문을 선택하면 /order 를 GET 방식으로 호출
  2. OrderController 의 createForm() 메서드
  3. 주문 화면에는 주문할 고객정보와 상품 정보가 필요하므로  model 객체에 담아서 뷰에 넘겨줌 

📍주문 실행

  1. 주문할 회원과 상품 그리고 수량을 선택해서 Submit 버튼을 누르면 /order URL을  POST 방식으로 호출
  2. 컨트롤러의 order() 메서드를 실행
  3. 이 메서드는 고객 식별자( memberId ), 주문할 상품 식별자( itemId ) ,수량 (count) 정보를 받아서 주문 서비스에 주문을 요청
  4. 주문이 끝나면 상품 주문 내역이 있는 /orders URL로 리다이렉트

 

(9) 주문 목록 검색, 취소

 

💥오류
OrderList를 만드는 과정에서 오류 발생 ➡️ 해결 X

'스프링' 카테고리의 다른 글

[JPA 활용 2]  (2) 2024.12.03
[스프링 핵심5 & JPA활용 1]  (1) 2024.11.20
[HTTP] 웹 기본 지식 3  (1) 2024.11.16
[HTTP] 웹 기본 지식 2  (0) 2024.11.12
[HTTP] 웹 기본 지식 1  (1) 2024.11.10