[Spring] 입문-6. Jdbc / 통합테스트 / H2 데이터베이스

Jina·2023년 6월 19일
0

Spring

목록 보기
6/9

다음의 모든 내용은 김영한님의 스프링 입문 강의에서 가져온 것임을 밝힙니다.

스프링의 DB 접근 기술로는 크게 Jdbc와 Jpa가 있다!

이번 포스팅에서는 Jdbc를, 다음 포스팅에서 Jpa를 다루었다.

하지만 순수 Jdbc는 20년도 더 전의 이야기로... 너무나 복잡하고 반복 코드가 많으므로 현재 거의 사용되지 않는다.
대신 JdbcTemplate은 여전히 사용되고 있으니 이를 중심으로 알아보자!


📍 H2 데이터베이스

먼저 로컬용 데이터베이스를 설치하자.
실무에서는 mySQL 계열을 많이 사용하나, 강의에서는 가벼운 H2를 사용했다.

실무에서도 로컬에서는 H2를 사용하고 운영시에만 mySQL 계열을 사용하는 경우도 많다!

0. 설치

다음 링크에 들어가서 1.4.200 버전을 설치했다.

1. 실행

cmd 창에서 해당 파일경로로 이동 후 H2 실행시키면, 다음과 같은 화면을 볼 수 있다.

$ cd C:\Program Files (x86)\H2\bin

$ h2.bat

2. 최초의 파일 생성

처음의 디폴트값 그대로 연결을 클릭하여 ~/test.mv.db 파일이 잘 생성되었다면, 이후부터는 jdbc:h2:tcp://localhost/~/test 으로 접속하면 된다.

3. Meber 테이블 생성

다음의 SQL문을 사용하여, 이전의 회원관리 예제에서 사용되던 Member 테이블을 생성했다.

SQL문은 src 디렉토리 외부에 (ex. hello-spring 하위)
sql 패키지를 만들어서 ddl.sql파일로 저장하면, 추후 깃허브로 버전관리시 용이하다!


📍 Jdbc 환경설정

1. Jdbc 관련 & H2 데이터베이스 관련 라이브러리 추가

build.gradle 파일의 dependencies 부분에 다음 코드를 추가하면 된다.

	implementation 'org.springframework.boot:spring-boot-starter-jdbc'	//Jdbc 관련 라이브러리
	runtimeOnly 'com.h2database:h2'	//H2 DB 관련 라이브러리

2. 스프링부트에 DB연결 설정 추가

resources 하위의 application.properties 파일에 다음 코드를 추가하자.

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

- 스프링 통합 테스트

이전 포스팅의 단위 테스트 하나하나를 잘 만드는 게 훨씬 더 중요하다. 하지만 스프링 컨테이너와 DB까지 모두 통합한 통합테스트가 필요한 경우, 다음을 참고하자.

3. @SpringBootTest 애노테이션

@SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행하도록 한다.

기존 MemberServiceTest 대신,
MemberServiceIntegrationTest.java를 생성하고 @SpringBootTest애노테이션을 추가한다.

4. @Transactional 애노테이션

@Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고 테스트 완료 후에 항상 롤백한다. 즉 DB에 데이터가 남지 않으므로 DB를 지우는 코드가 필요 없고, 다음 테스트에 영향을 주지 않는다!

  • 최종 MemberServiceIntegrationTest.java
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
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.*;
import static org.junit.jupiter.api.Assertions.*;


@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    /**
    @BeforeEach
    public void beforeEach(){
        //테스트 실행 전마다 리포지토리를 생성해서 MemberService에 넣어준다
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
     **/

    /**
    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }
     **/

    @Test
    void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("홍길동");

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

        //then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());

    }

    @Test
    void 중복_회원_예외() throws Exception{
        //given
        Member member1 = new Member();
        member1.setName("홍길동");

        Member member2 = new Member();
        member2.setName("홍길동");

        //when
        memberService.join(member1);

        //중복 회원이 join하려 하면 IllegalStateException이 터져야 한다
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        //then
    }
    
}

📍 JdbcTemplate / MyBatis

JdbcTemplate: JDBC API에서의 반복 코드를 대부분 제거해준다! SQL은 직접 작성해야 한다.
이는 실무에서도 많이 쓰는 방식이다.

1. JdbcTemplate을 사용하는 Repository 생성

repository 디렉토리에 JdbcTemplate을 사용하는 JdbcTemplateMemberRepository 리포지토리를 생성하자.

이 리포지토리는 기존에 만들었던 MemberRepository를 implement하며,
다음의 코드를 추가하여 Jdbctemplate을 사용할 수 있다.

    private final JdbcTemplate jdbcTemplate;
    
    //@Autowired
    public JdbcTemplateMemberRepository(DataSource dataSource){
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

[참고] 생성자가 1개뿐이면 @Autowired 생략 가능하다.

  • 최종 JdbcTemplateMemberRepository.java
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
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
    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 List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }
    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }
    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }
}

2. JdbcTemplate 사용하도록 스프링 설정 변경

  • SpringConfig.java
@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    @Autowired
    public SpringConfig(DataSource dataSource){
        this.dataSource = dataSource;
    }
    
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository();
        return new JdbcTemplateMemberRepository(dataSource);    // Jdbc template 사용
    }

}

- 스프링 통합 테스트

앞서 만들었던 통합 테스트 진행시, 모두 성공적으로 체크표시가 떴다!

0개의 댓글