Peony의 기록 창고 🌼
article thumbnail
반응형

애플리케이션 아키텍처

계층형 구조 사용

controller, web: 웹 계층

service: 비즈니스 로직, 트랜잭션 처리

repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용

domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

 

패키지 구조

  • jpabook.jpashop
    • domain
    • exception
    • repository
    • service
    • web

 

개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증, 마지막에 웹 계층 적용

 

회원 리포지토리

@Repository
@RequiredArgsConstructor
public class MemberRepository {
    private final 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();
    }
}
  • @Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
  • @PersistenceContext : EntityManager 주입
  • @PersistenceUnit : EntityManagerFactory 주입

 

회원 서비스

@Service
@Transactional(readOnly = true) //JPA의 모든 데이터 변경은 트랜잭션 안에서 실행되어야 함
@RequiredArgsConstructor //final이 있는 필드만 가지고 생성자를 만들어줌
public class MemberService {
    private final MemberRepository memberRepository;

    /* 회원 가입 */
    @Transactional
    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());
        //EXCEPTION
        if(!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }
    //회원 전체 조회
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}
  • @Service
  • @Transactional : 트랜잭션, 영속성 컨텍스트
    • readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 약간의 성능 향상 (읽기 전용에는 다 적용하기)
    • 데이터베이스 드라이버가 지원하면 DB에서 성능 향상
  • @Autowired :생성자 Injection 많이 사용, 생성자가 하나면 생략 가능

 

회원 기능 테스트

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional //테스트에 있으면 테스트 후에 DB 롤백
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("예외가 발생해야 한다.");
    }
}
  • @RunWith(SpringRunner.class) : 스프링과 테스트 통합
  • @SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
  • @Transactional : 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백 (이 어노테이션이 테스트 케이스에서 사용될 때만)

 

테스트 케이스를 위한 설정

테스트는 케이스 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런 면에서 메모리 DB를 사용하는 것이 가장 이상적이다.
추가로 테스트 케이스를 위한 스프링 환경과, 일반적으로 애플리케이션을 실행하는 환경은 보통 다르므로 설정 파일을 다르게 사용하자.

test/resources/application.yml

spring:
#  datasource:
#    url: jdbc:h2:mem:testdb
#    username: sa
#    password:
#    driver-class-name: org.h2.Driver
#  jpa:
#    hibernate:
#      ddl-auto: create
#    properties:
#
#
#hibernate:
#        show_sql: true
#  format_sql: true
#open-in-view: false
logging.level:
  org.hibernate.SQL: debug
#  org.hibernate.type: trace

여기서 datasource 설정이 없으면 기본적으로 메모리 DB를 사용하고 create-drop 모드로 동작한다. 즉, 다음과 같이 주석처리가 되어있어도 자동으로 설정해준다.

 

이 글은 김영한 님의 "실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발"을 듣고 정리한 내용입니다.

반응형
profile

Peony의 기록 창고 🌼

@myeongju