496p까지
11장_웹 애플리케이션 제작
사용할 기술
- 뷰 → JSP, JSTL
- 웹 계층 → Spring MVC
- 데이터 기반 저장 → JPA, 하이버네이트
- 기반 프레임워크 → 스프링
- 빌드 → 메이븐
프로젝트 환경설정
진행 순서
- 프로젝트 구조 분석
- 메이븐과 라이브러리 설정
- 스프링 프레임워크 설정
메이븐 build
처음에 mvn tomcat7:run
을 해도 계속 오류가 떴다.
여러가지 찾아봤었는데 자바 버전 문제나 플러그인 문제인가 싶었는데
결국은 프로젝트가 잘못설정되어 있어서 그런거였다.
mvn goal 실행할 때 프로젝트가 잘 설정되어있는지 꼭 확인하자..
pom.xml
- <modelVersion> : POM 모델 버전
- <groupId> : 프로젝트 그룹명
- <artifactId> : 프로젝트를 식별하는 아이디
- <version> : 프로젝트 버전
- <name> : 프로젝트 이름
- <packaging> : 빌드 패키징 방법을 지정한다. 웹 애플리케이션은 war, 자바 라이브러는 jar를 사용한다.
- <dependencies> : 사용할 라이브러리를 짖어한다.
- <build> : 빌드 관련 정보 설정
webAppConfig.mxl ⇒ 스프링 MVC 설정을 포함해서 웹 계층을 담당
appConfig.xml ⇒ 비즈니스 로직, 도메인 계층, 서비스 계층, 데이터 저장 계층을 담당
appConfig.xml
<tx:annotationn-driven/>
: 스프링 프레임워크가 제공하는 어노테이션 기반(@Transactional
)의 트랜잭션 관리자를 활성화한다.
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource">
<property name="driverClassName" value="org.h2.Driver">
<property name="url" value="jdbc:h2:mem:jpashop">
<property name="username" value="sa">
<property name="password" value="">
</bean>
위 코드는 데이터베이스 접근할 데이터소스를 등록한다.
여기서는 JVM 안에서 동작하는 인 메모리 데이터베이스를 사용하한다. 인 메모리 데이터베이스를 상요하면 별도의 데이터베이스 서버를 실행하지 않아도 된다.
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
일반적으로 org.springframework.jdbc.datasource.DataSourceTransactionManager
를 트랜잭션 관리자로 사용하지마 JPA를 사용하려면 org.springframework.orm.jpa.JpaTransactionManager
를 등록해야한다.
- hibernate-core-[버전정보].jar
- hibernate-jpa-2.1-api-{버전정보}.jar
프로젝트를 선택하고 mvn pacakge
를 입력하면 target 폴더에 설정한 프로젝트와 이름대로 war파일이 생성된다.
메이븐 의 설정
provided : 외부에서 라이브러리가 제공된다. 컴파일 시 상요하지만 빌드에 포함되지 않는다. 보통 JSP, Servlet 라이브러리들에 사용한다
test: 테스트 코드에만 사용
스프링 프레임워크 설정
프로젝트 환경설정 파일은 다음과 같다.
main/webapp/web.xml
:웹 애플리케이션 환경설정 파일main/resources/webAppConfig.xml
: 스프링 웹 관련 환경설정 파일main/resources/appConfig.xml
: 스프링 애플리케이션 관련 환경설정 파일
회원 기능
회원 리포지토리 분석
@Repository
가 붙어있으면 컴포넌트 스캔에 의해 스프링 빈으로 자동 등록된다. 또한, JPA 전용 예외가 발생하면 스프링이 추상화한 예외로 바꿔준다. 따라서, JPA에 의존적인 예외처리를 하지 않아도 된다.
@PersistenceContext
순수 자바 환경과 다르게 스프링이나 J2EE를 사용하면 컨테이너가 엔티티 매니저를 관리 및 제공해주고 이를 사용하게 된다. @PersistenceContext
는 컨테이너가 관리하는 엔티티 매니저를 주입하는 어노테이션이다. 이렇게 주입받은 엔티티 매니저를 사용해야 컨테이너가 제공하는 트랜잭션 기능과 연계해서 컨테이너의 다양한 기능을 사용할 수 있다.
@PersistenceUnit
@PersistenceContext
를 통해 엔티티 매니저를 주입받으므로 엔티티 매니저 팩토리를 직접 주입받아 사용할 일은 거의 없겠지만, 만약 엔티티 매니저 팩토리를 주입받고자 하면 @PersistenceUnit
을 사용하면 된다.
회원 서비스 분석
@Service
가 붙어있으면 컴포넌트 스캔에 의해 스프링 빈으로 등록된다.
@Transactional
스프링 프레임워크는 해당 어노테이션이 붙어있는 메소드에 트랜잭션을 적용한다. 해당 메소드의 모든 문장이 성공적으로 실행되면 트랜잭션을 커밋하고 예외가 발생하면 트랜잭션을 롤백한다.
@Transactional
은 RuntimeException과 그 자식들인 Unchecked 예외만 롤백한다. 만약 체크 예외도 롤백하고 싶다면@Transactional(rollbackFor = Exception.class)
처럼 롤백할 예외를 지정해야 한다.
회원 기능 테스트
Given, When, Then 문법
Given 절에서 테스트할 상황 설정하고 When에서 테스트를 실행한 뒤 Then 절에서 검증한다.
스프링 프레임워크와 테스트 통합
@RunWith(SpringJUnit4ClassRunner.class)
JUnit으로 작성한 테스트 케이스를 스프링 프레임워크로 통합하려면 위와같이 지정하면 된다. 이렇게 하면 테스트가 스프링 컨테이너에서 실행된다. 따라서, 스프링 프레임워크에서 제공하는 @Autowired
같은 기능들을 사용할 수 있다.
@COntextConfiguration(locations = "classpath:appConfig.xml")
테스트 케이스를 실행할 때 사용할 스프링 설정 정보를 지정한다. 웹과 관련한 정보가 필요하면 webAppConfig.xml도 지정해주면된다.
@Transactional
해당 어노테이션은 보통 비즈니스 로직이 있는 서비스 계층에서 사용된다. 이를 테스트에 적용하게 되면 매 테스트마다 트랜잭션을 시작하고 테스트가 종료되면 트랜잭션을 롤백한다. 이렇게 함으로써 테스트를 실행할 때마다 데이터가 저장되는 일 없이 안전하게 테스트를 진행할 수 있다.
@Test(expected = IllegalStateException.class)
expected 속성에 예외 클래스를 지정하면 테스트의 결과로 지정한 예외가 발생해야 테스트가 성공한다. 위 테스트에서는 fail()
메소드가 호출되거나 예외가 발생하지 않으면 실패한다.
상품 리포지토리 분석
@Repository
public class ItemRepository{
@PersistenceContext
EntityManager em;
public void save(Item item){
if(item.getId() == null){
em.persist(item);
} else{
em.merge(item);
}
}
...
}
save()
메소드를 살펴보면 식별자 값이 없으면 새 엔티티로 판단해 영속화하고 식별자 값이 있으면 이미 한 번 영속화했던 엔티티로 판단해 병합(수정)한다. 여기서 저장은 신규 데이터를 저장하는 것뿐만 아니라 변경된 데이터의 저장도 의미한다.
주문 기능
비즈니스 로직이 대부분 엔티티에 있고 서비스 계층이 필요한 요청을 엔티티에 위임하는 역할을 한다면 이를 도메인 모델 패턴이라고 한다. 이와 반대로 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 수행한다면 이를 트랜잭션 스크립트 패턴이라고 한다.
위와 같이 검색 조건을 설정하고 검색 버튼을 누르면 해당 조건이 담긴 OrderSearch 객체가 OrderRepository로 전달된다.
상품 등록 폼
@Controller
public class ItemController{
@Autowired ItemService itemService;
@RequestMapping(value = "/items/new", method = RequestMethod.GET)
public String createForm(){return "items/createItemForm";}
}
위 메서드는 단순히 문자열 "items/createForm"
을 반환한다. 이를 WebAppConfig.xml에 정의된 스프링 MVC의 뷰 리졸버는 이 정보를 바탕으로 실행할 뷰를 찾는다.
<!-- JSP, JSTL 사용하도록 뷰 리졸버 등록 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
위에서 반호나한 문자와 뷰 리졸버에 등록한 setPrefix(), setSuffix() 정보를 사용해 렌더링할 뷰를 찾는다. 결과적으로 WEB-INF/jsp/items/createItemForm.jsp
를 찾게된다. 마지막으로 해당 위치의 JSP를 실행한 결과 HTML을 클라이언트에 응답한다.
@RequestMapping(value = "/items/new", method = RequestMethod.POST)
public String create(Book item) {
itemService.saveItem(item);
return "redirect:/items";
}
상품 등록 시에 전달한 파라미터들은 Item으로 묶여 바인딩된다.(HttpServletRequest의 파라미터와 객체의 프로퍼티 이름을 비교해서 같으면 스프링 프레임워크가 값을 바인딩해준다.) 이후, 상품 저장 요청을 하고 상품 목록 화면(items)로 리다이렉트한다.
@RequestMapping(value = "/items", method = RequestMethod.GET)
public String list(Model model) {
List<Item> items = itemService.findItems();
model.addAttribute("items", items);
return "items/itemList";
}
list() 메서드는 itemService.findItems() 메서드를 실행하고 그 결과를 모델에 담는다. 마지막으로 실행할 뷰 이름을 반환한다.
상품 수정
상품을 수정 요청을 통해 전달받은 파라미터 Item은 준영속 상태의 엔티티이다. 따라서, 영속성 컨텍스트의 지원을 받을 수 없고, 데이터를 수정해도 변경 감지 기능은 동작하지 않는다.
변경 감지와 병합
이런 준영속 상태의 엔티티를 수정하는 방법은 두 가지가 있다.
- 변경 감지 기능 사용
- 병합
변경 감지 기능 사용
변경 감지 기능을 사용하는 방법은 영속성 컨텍스트에서 다시 엔티티를 조회한 후에 데이터를 수정하는 방법이다.
@Transactional
public void update(Item itemParam){
Item findItem = em.find(itemParam.getId(), Item.class);
findItem.setPrice(itemParam.getPrice()); // 데이터 수정
}
위와 같이 트랜잭션 안에서 준영속 상태의 엔티티 식별자 값으로 엔티티를 다시 조회하면 영속 상태의 엔티티를 얻을 수 있다. 이후, 엔티티의 값을 다시 파라미터 값으로 수정하면 영속성 컨텍스트의 변경 감지 기능을 이용해 수정사항이 데이터베이스에 반영된다.
병합 사용
@Transactional
public void update(Item itemParam){
Item mergeItem = em.merge(itemParam);
}
병합을 사용하면 거의 비슷한 방식으로 동작한다. 준영속 상태의 식별자로 영속 엔티티를 조회한다. 그리고 영속 엔티티의 값을 준영속 엔티티 값으로 채워넣는다.
변경 감지 기능을 사용하면 원하는 속성만 선택해서 수정할 수 있지만 병합을 사용하면 모든 속성이 변경된다.
아이템 서비스는 단순히 리포지토리에 요청을 위임한다. 아래의 코드는 아이템 리포지토리의 save() 메소드이다. save() 메서드에서는 위와 같이 준영속 상태의 엔티티가 전달되면 병합을 사용해 모든 속성을 변경한다.
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
'JPA' 카테고리의 다른 글
13장_웹 어플리케이션과 영속성 관리 (0) | 2024.04.05 |
---|---|
12장_스프링 데이터 JPA (0) | 2024.04.02 |
Java ORM 표준 - JPA 프로그래밍 10장 (0) | 2024.03.24 |
Java ORM 표준 - JPA 프로그래밍 9장 (0) | 2024.01.19 |