JPA 프로그래밍 학습 정리 (8) - 웹 어플리케이션 제작(3)
https://taehoon9393.tistory.com/373
JPA 프로그래밍 학습 정리 (8) - 웹 어플리케이션 제작(2)
https://taehoon9393.tistory.com/372 JPA 프로그래밍 학습 정리 (8) - 웹 어플리케이션 제작(1) 웹 어플리케이션 만들기 진행 순서 프로젝트 환경 설정 도메인 모델과 테이블 설계 애플리케이션 기능 구현 사
taehoon9393.tistory.com
웹 어플리케이션 만들기 진행 순서
- 프로젝트 환경 설정
- 도메인 모델과 테이블 설계
- 애플리케이션 기능 구현
사용할 기술
- 뷰 : JSP, JSTL
- 웹 계층 : 스프링 MVC
- 데이터 저장 계층 : JPA, Hibernate
- 기반 프레임워크 : 스프링 프레임워크 ( https://spring.io/ )
- 빌드 : Maven
애플리케이션 구현
교재에 소개되어 있는 회원, 상품, 주문 기능을 가지고 있는 애플리케이션을 들여다보자
- 요구사항 분석
- 도메인 모델 설계
- 테이블 설계
- 연관관계 정리
- 앤티티 클래스 구현
- 애플리케이션 구현
- 회원 기능
- 회원 등록
- 회원 조회 - 상품 기능
- 상품 등록
- 상품 수정
- 상품 조회 - 주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소 - 기타 요구사항
- 상품의 종류는 도서, 음반, 영화가 있다.
- 상품을 카테고리로 구분할 수 있다.
- 상품 주문 시 배송 정보를 입력할 수 있다.
UML 상세정보
개발방법
- 컨트롤러 : 서비스 계층을 호출하고 결과를 뷰로 전달
- 서비스 : 비즈니스 로직이 있고 트랜잭션을 시작한다. 데이터 접근 계층인 리포지토리를 호출한다.
- 리포지토리 : JPA를 직접 사용하고 엔티티 매니저를 사용해서 엔티티를 저장하고 조회한다.
- 도메인 : 엔티티가 모여 있는 계층. 모든 계층에서 사용한다.
자바 위치
회원기능
- 등록
- 목록 조회
회원 레퍼지토리
- @Repository : <context:component-scan>에 의해 스프링 빈에 자동 등록된다.
- @PersistenceContext : 스프링이나 J2EE 컨테이너를 사용하면 컨테이너가 엔티티 매니저를 관리하고 제공해준다.
회원 서비스
- @Service : <context:component-scan>에 의해 스프링 빈으로 등록된다.
- @Transactional : 클래스나 메소드에 트랜잭션을 적용한다. 외부에서 호출할 때 트랜잭션을 시작하고 메소드를 종료할 때 트랜잭션을 커밋한다. 만약 예외가 발생하면 트랜잭션을 롤백한다.
- @Autowired을 사용하면 스프링 컨테이너가 적잘한 스프링 빈을 주입해준다.
기능 테스트 Junit
- @RunWith(SpringJUnit4ClassRunner.class) : 스트링 프레임워크와 테스트를 통합해주는 어노테이션이다. @AutoWired같은 기능들을 사용할 수 있게 해준다.
- @ContextConfiguration(locations = "classpath:appConfig.xml") : 테스트 케이스를 실행할 때 사용할 스프링 설정 정보를 지정한다. 설정 정보로 appConfig.xml를 사용한다. 웹과 관련된 정보는 필요하지 않으므로 webAppConfig.xml은 지정하지 않았다.
- @Transactional : 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백한다.
- @Test : 테스트 결과로 지정한 예외가 발생해야 테스트가 성공한다.
상품 기능
- 상품 등록
- 상품 목록 조회
- 상품 수정
2장에서 엔티티 및 연관관계 설정한 것에 추가로 비즈니스 로직을 개발한다.
상품 엔티티에 비즈니스 로직 추가
- Item의 재고 수를 관리하는 addStock, removeStock 함수를 개발한다.
상품 레퍼지토리
save() 함수를 통해서 수정과 병합을 모두 처리하도록 개발한다.
서비스
주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
주문 엔티티
주문엔티티에는 CRUD외에도 의미있는 비즈니스 로직이 들어가야 한다.
주문 엔티티를 생성할 때 사용되는 주문 회원, 배송정보, 주문상품의 정보를 받아서 실제 주문 엔티티를 생성하는 생성메서드가 필요하다.
주문 취소 시, 주문 상태를 취소로 업데이트하고 주문 상품에 취소를 알린다. 이미 배송이 완료된 상품이라면 예외를 발생시킨다.
전체 주문 가격을 조회한다.
추가로 연관된 주문상품 엔티티를 살펴보자
주문상품 엔티티
마찬가지로 생성메서드가 있다. 주문 상품, 가격, 수량 정보를 사용해서 주문상품 엔티티를 생성한다. 그리고 주문 수량만큼 상품의 재고를 줄이는 removeStock()함수를 사용한다.
- 주문취소 : 취소한 주문 수량만큼 상품의 재고를 증가시킨다.
- 주문 가격에 수량을 곱한 값을 반환한다.
주문 레퍼지토리
앞에서 개발한 방향과 같다.
주문 서비스
주문 : 주문하는 회원 식별자, 상품 식별자, 주문 수량 정보를 받아서 실제 주문 엔티티를 생성한 후 저장한다.
- 취소 : 주문 식별자로 주문 엔티티를 조회한 후 주문 엔티티에 주문 취소를 요청
- 검색 : 검색 조건을 가진 객체로 주문 엔티티를 검색한다.
주문 검색 기능
주문검색 객체를 POJO형태의 클래스로 만들어준다.
검색 조건에 따라 Criteria를 동적으로 생성해서 주문 엔티티를 조회한다.
주문 기능 테스트
- 상품 주문이 성공해야 한다.
- 상품을 주문할 때 재고 수량을 초과하면 안 된다.
- 주문 취소가 성공해야 한다.
회원과 상품을 만들고, 실제 상품을 주문한다면 주문 결과가 올바르게 나왔는지 검증하는 상품주문 함수
회원과 상품을 생성해주고 재고 보다 많은 수량을 세팅해준다. 그리고 주문을 하게 되었을 때, NotEnoughStockException 예외가 떨어지게 되면 테스트를 성공하도록 만들어준다.
회원과 상품으로 주문을 실제 생성한다.
그리고 When 검증에서 주문을 취소하고 Then절에서 실제 주문이 취소되었는지를 확인한다. 주문상태와 재고수량을 검증해준다.
Test할 때에는, Given(조건 세팅), When(실제 실행), Then(검증 결과) 순으로 세팅해서 테스트하는 편을 추천한다.
웹 계층 구현
웹 계층 구현부분도 프론트단 말고 서버단에서만 정리할 예정이다.
상품등록
컨트롤러에 상품을 등록해줄 URL들을 등록해준다. 이때, method를 GET,POST방식으로 나누어 구분해준다.
문자로 return하는 경우 스프링 MVC의 뷰 리졸버는 이정보를 바탕으로 실행할 뷰를 찾는다.
- GET방식으로 요청하게 되면 등록 폼을 갖는 뷰를 실행하도록 실행한다.
- POST방식으로 요청하게 되면 전달받은 파라미터를 통해 saveItem함수를 실행하고 items URL화면으로 리다이렉트된다.
webAppConfig.xml 설정 파일
prefix + {retuen String} + suffix 정보를 사용해서 렌더링 할 뷰를 찾는다.
위의 설정정보로 예시를 들면 : /WEB-INF/jsp/{items/createItemForm}.jsp 가 된다.
상품 목록
findItems()함수를 통해 상품들을 전부 조회하고 모델에 세팅해준 후, items/itemList 뷰에 던져준다.
상품수정
등록과 마찬가지로 GET, POST로 나누어 개발해준다.
- GET함수에서는 상품 식별자를 받아 상품을 조회하고 모델에 넣고 업데이트 폼으로 리턴해준다.
- POST함수에서는 전달받은 파라미터를 통해 saveItem함수를 사용한다. 그리고 업데이트 후 /items 화면으로 리다이렉트 된다.
변경 감지와 병합
- 변경 감지 기능 사용 : 원하는 속성만 선택해서 변경 가능
@Transactinal
//itemParam : 파라미터로 넘어온 준영속 상태의 엔티티
void update(Item itemParam)
{
//같은 엔티티를 조회한다.
Item findItem = em.find(Item.class, itemParam.getId());
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}
준영속 엔티티를 통해 영속 엔티티를 다시 조회하고 데이터를 수정해주면 변경감지가 작동해서 데이트베이스에 수정사항이 반영된다.
- 병합 사용 : 모든 속성이 변경됨
@Transactional
//itemParam : 파라미터로 넘어온 준영속 상태의 엔티티
void update(Item itemParam)
{
Item mergeItem = em.merge(itemParam);
}
영속 엔티티의 값을 병합을 통해 준영속 엔티티의 값으로 채워 넣는다.
상품 주문
마찬가지로 GET, POST방식으로 분기했다.
- GET에서는 회원과 상품을 모두 조회해서 모델에 넘겨 뷰를 실행한다.
- POST에서는 각 식별자와 수량을 받아와서 order() 함수를 실행하고 /orders화면으로 리다이렉트 시켜준다.