예제에서 사용하는 기술
- 뷰: JSP, JSTL
- 웹 계층: 스프링 MVC
- 데이터 저장 계층: JPA, 하이버네이트
- 기반 프레임워크: 스프링 프레임워크
- 빌드: 메이븐
프로젝트 환경설정 진행순서
메뉴의 [Run] > [Run As] > [Maven Build…] 선택
Goals에 tomcat7:run을 입력하고 Run 버튼 선택
⇒ http://localhost:8080으로 서버가 실행됨
메이븐이 제공하는 표준 프로젝트 구조
jpashop (프로젝트 루트)
├── src (소스 폴더)
| ├── main(실행 코드)
| | ├── java (자바 소스코드)
| | ├── resources (리소스)
| | └── webapp (웹 폴더)
| └── test (테스트 코드)
├── target (빌드 결과)
└── pom.xml (메이븐 설정 파일)
jpashop.war 파일이 생성됨@Component, @Service, @Repository, @Controller 어노테이션이 붙어있는 클래스들을 스프링 빈으로 자동 등록함.@Transactional이 붙은 곳에 트랜잭션을 적용함jpashop (프로젝트 루트)
├── src (소스 폴더)
| ├── main(실행 코드)
| | ├── java (자바 소스코드)
| | | └── jpabook
| | | └── jpashop
| | | ├── domain //도메인 계층
| | | ├── repository //데이터 저장 계층
| | | ├── service //서비스 계층
| | | └── web //웹 계층
| | |
| | ├── resources (리소스)
| | | └── **appConfig.xml** //스프링 애플리케이션 관련 설정
| | | └── **webAppConfig.xml** //스프링 웹 관련 설정
| | └── webapp (웹 폴더)
| | └── WEB-INF
| | └── **web.xml** //웹 애플리케이션 환경설정 파일
| └── test (테스트 코드)
├── target (빌드 결과)
└── pom.xml (메이븐 설정 파일)
<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>
jdbc:h2:mem:...으로 설정해 JVM 안에서 동작하는 인 메모리 데이터베이스로 사용함<bean id="transactionalManager" class="org.springframework.orm.jpa.JpatransactionManager">
<property name="dataSource" ref="dataSource">
</bean>
org.springframework.orm.jpa.JpatransactionManager를 트랜잭션 관리자로 등록해양함<bean class="org.springframework.dao.annoation.PersistenceExceptionTranslationPostProcessor"/>
@Repository 어노테이션이 붙어있는 스프링 빈에 예외 변환 AOP를 적용함<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource">
<!--@Entity 탐색 시작 위치-->
<property name="packagesToScan" value="jpabook.jpashop.domain"/>
<property name="jpaVendorAdapter">
<!--하이버네이트 구현체 사용-->
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<property name="jpaProperties"> <!--하이버네이트 상세 설정-->
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.H2Dialect </prop> <!--방언-->
<prop key="hibernate.show_sql">true</prop> <!--SQL 보기-->
<prop key="hibernate.format_sql">true</prop> <!--SQL 정렬해서 보기-->
...
LocalContainerEntityManagerFactoryBean을 스프링 빈으로 등록해야함@Entity가 붙은 클래스를 자동으로 검색하기 위한 시작점을 지정

@ManyToMany를 사용해 매핑함...
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<Order>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name='" + name + '\'' +
", address=" + address +
'}';
}
}
!image.png
⇒ 개발순서: 비즈니스 로직을 수행하는 서비스(Service)와 레포지토리(Repository) 계층을 먼저 개발 → 테스트 케이스를 작성해 검증 → 검증 완료 후 컨트롤러와 뷰를 개발
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<Order>();
...
}
@Repository:@PersistenceContext:@PersistenceUnit@Repository
public class MemberRepository {
@PersistenceContext
EntityManager em;
public void save(Member member) {
em.persist(member);
}
public Member findOne(Long id) {
return em.find(Member.class, id);
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class).getResultList();
}
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
}
@Service: <context:component-scan>에 의해 스프링 빈으로 등록됨@Transactional: 트랜잭션을 적용해줌@Autowired: 스프링 컨테이너가 적절한 스프링 빈을 주입해줌.@Service
@Transactional
public class MemberService {
@Autowired
MemberRepository memberRepository;
public Long join(Member member) {
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
join())validateDuplicateMember()를 통해 중복 회원가입을 방지함 동일한 이름을 가진 회원이 존재한다면, 메시지와 함께 예외를 발생시키고 성공했을 경우에는 회원 식별자를 반환해줌@RunWith(SpringJUnit4ClassRunner.class)@Autowired 같은 기능을 사용할 수 있음@ContextConfiguration(locations = "classpath:appConfig.xml")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:appConfig.xml")
@Transactional
public class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
//Given
Member member = new Member();
member.setName("kim");
//When
Long saveId = memberService.join(member);
//Then
assertEquals(member, memberRepository.findOne(saveId));
}
@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//When
memberService.join(member1);
memberService.join(member2); //예외가 발생해야 한다.
//Then
fail("예외가 발생해야 한다.");
}
}