본문 바로가기
dev-ing/spring

[Springboot] 스프링부트 기초 (13) JDBC 스프링 컨테이너와 DB연동해서 통합테스트 하기 JDBC Template

by ota-min 2022. 10. 27.
반응형

springboot-썸네일
spring-boot

 
저번에는 실제 H2라는 데이터베이스를 설치하고, JDBC API를 이용해 직접 입력한 정보들을 새로 설치한 데이터베이스에 저장하고 조회하는 것까지 했었던 것 기억하시나요?? 그러면 만들었던 부분들이 잘 동작하는지 확인하는 작업이 필요하겠죠?? 그래서 이번에는 스프링 컨테이너와 DB까지 연결한 통합테스트를 진행해보도록 하겠습니다.
 

스프링 부트 기초, 스프링 컨테이너와 DB 연동해서 통합 테스트해보기

 
1. 이번 테스트는 기존에 작성했던 MemberServiceTest를 이용해서 해보겠습니다. MemberServiceTest를 복사 후 test/service 경로에 MemberServiceIntegrationTest라는 이름으로 생성하고, 코드를 아래와 같이 작성합니다.
 
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;


@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

//    MemberService memberService = new MemberService();
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
    // DI는 생성자 주입을 권장하지만 테스트의 경우에는 간편한 방법으로 진행해도 무방하다.

//    @BeforeEach
//    public void beforeEach() {
//        memberRepository = new MemoryMemberRepository();
//        memberService = new MemberService(memberRepository);
//    }

//    @AfterEach
//    public void afterEach() {
//        memberRepository.clearStore();
//    }
// --> beforeEach와 afterEach는 이제 @Transactional로 인해 필요가 없어졌다.

    @Test
    public void 회원가입() { // 회원가입 테스트.

        // given
        Member member = new Member();
        member.setName("pingku");

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // given
        Member member1 = new Member();
        member1.setName("pingku");

        Member member2 = new Member();
        member2.setName("pingku");

        memberService.join(member1);

        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member1));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }


    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

 

  • @SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행합니다.
  • @Transactional : 테스트 케이스에 이 어노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백합니다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않습니다.

 

2. 그다음은 스프링 JDBC Template이라는 라이브러리를 이용해서 전에 작성했던 순수 JDBC의 코드보다 간결하게 작성해보겠습니다. jdbcTemplate이라는 라이브러리는 디자인 패턴 중에 Template method 패턴을 이용해 관련 코드를 줄인 라이브러리입니다. (그래서 이름에 template이 들어가는 것이라고들 합니다..)

 

 

3. repository 하위에 JdbcTemplateMemberRepository라는 이름의 클래스를 생성하고 아래와 같이 코드를 작성합니다.

 

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

//    @Autowired
    // 생성자가 딱 하나만 있을 경우에는 스프링 빈이 해당 생성자로 빈을 생성하기 때문에 @Auotwired가 없어도 무방하다.
    // 단, 생성자가 여러개일 경우에는 @Autowired는 무조건 있어야 한다.
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());

        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());

        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }

    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }
}

 

  • 순수 Jdbc와 동일한 환경설정을 하면 되며, 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해주기는 하지만, SQL은 직접 작성해야 합니다.

 

5. 그리고 위에서 작성한 JdbcTemplate를 사용하기 위해 SpringConfig에서 코드를 아래와 같이 변경합니다.

 

@Bean
    public MemberRepository memberRepository() {
//        return new MemoryMemberRepository();
//        return new JdbcMemberRepository(dataSource);
        return new JdbcTemplateMemberRepository(dataSource);
    }

 

다음에는 SQL의 최소화 하는 JPA에 대해 간단하게 알아보겠습니다.

 

 

반응형

댓글