3 분 소요

이 포스트는 인프런 김영한님의 강의 및 자료를 사용합니다

이번 강의는 기존에 회원가입을 했을 때 메모리에 저장했던 것을, 데이터베이스를 이용하여 데이터베이스에 저장할 수 있도록 하는 강의이다.

먼저, spring에서 데이터베이스를 이용하기 위해서는 gradle파일과 properties파일에 라이브러리와 설정을 추가해주어야한다.

예를 들어, 강의에서는 h2 데이터베이스를 이용하기 때문에 아래와 같이 설정한다.

다른 데이터베이스를 사용하면 그에 따른 설정을 추가해주면 된다.

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

resources/application.properties

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

jdbc 리포지토리 구현

아래는 jdbc api로 직접 코딩을 한 것의 일부분이다. 아주 옛날에 사용했던 것이기 때문에 딥하게 공부하는 것보다는 이런게 있었구나라는 생각으로 강의자료에 있는 것을 한번씩 보면 될 것 같다.

중요한 것은, 아래와 같이 JdbcMemberRepository의 코드를 짜면 h2 DB에 결과가 잘 입력되게 된다.

따라서 기존에 메모리에 저장을 했을 때 재실행을 데이터가 사라지는 것과는 달리 재실행을 해도 데이터가 저장되어 있는 것을 확인할 수 있다.

public class JdbcMemberRepository implements MemberRepository {
  private final DataSource dataSource;
  public JdbcMemberRepository(DataSource dataSource) {
    this.dataSource = dataSource;
  }
  @Override
  public Member save(Member member) {
    String sql = "insert into member(name) values(?)";
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      conn = getConnection();
      pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
      pstmt.setString(1, member.getName());
      pstmt.executeUpdate();
      rs = pstmt.getGeneratedKeys();
      if (rs.next()) {
        member.setId(rs.getLong(1));
      } else {
        throw new SQLException("id 조회 실패");
      }
      return member;
    } catch (Exception e) {
      throw new IllegalStateException(e);
    } finally {
      close(conn, pstmt, rs);
    }
  }
  @Override
  public Optional<Member> findById(Long id){
    .........
  }
.
.
.
.

이 코드를 사용하기 위해서는 또 한가지 설정을 해주어야 한다.

기존에 MemberRepository를 스프링 빈으로 등록을 할 때는 아래와 같이 등록을 하였다.

@Bean
public MemberRepository memberRepository() {
  return new MemoryMemberRepository();
}

하지만, 이제는 새로 만든 JdbcMemberRepository를 사용하기 때문에 코드를 수정해준다.

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

그림으로 보게 되면,

jdbc

이를 통해서, 의존성 주입과 객체 지향 프로그래밍을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다는 엄청난 장점을 알 수 있다.

스프링 컨테이너와 DB를 연결한 통합 테스트

이전에는 unit단위로 세세하게 테스트를 했지만, 이제는 DB를 연동해놨기 때문에, 스프링 컨테이너, 그리고 DB를 연결한 통합 테스트에 대해서 다뤄볼 예정이다.

@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
  @Autowired MemberService memberService;
  @Autowired MemberRepository memberRepository;
  @Test
  public void 회원가입() throws Exception {
    //Given
    Member member = new Member();
    member.setName("hello");
    //When
    Long saveId = memberService.join(member);
    //Then
    Member findMember = memberRepository.findById(saveId).get();
    assertEquals(member.getName(), findMember.getName());
  }
  @Test
  public void 중복_회원_예외() throws Exception {
    //Given
    Member member1 = new Member();
    member1.setName("spring");
    Member member2 = new Member();
    member2.setName("spring");
    //When
    memberService.join(member1);
    IllegalStateException e = assertThrows(IllegalStateException.class,
    () -> memberService.join(member2));//예외가 발생해야 한다.
    assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
  }
}

이 코드는 이전 테스트 관련 강의에서 다뤘기 때문에 따로 설명하지 않아도 될 것 같다.

하지만 가장 위에 있는 두 가지 어노테이션에 대해서 정리할 것이다.

@SpringBootTest : 이 어노테이션이 있으면 스프링 컨테이너와 테스트를 함께 실행한다.

@Transactional : 테스트 케이스에 이 어노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 좋은 테스트는 여러번을 반복해도 성공을 해야하는데, 이 어노테이션이 없으면 한 번 실행했을 때 데이터가 남아있어서 두번째 실행을 했을때 테스트에 실패하게 된다. 즉, 이 어노테이션을 사용하면 롤백이 되어 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.

JdbcTemplate

JdbcTemplate은 스프링 프레임워크에서 제공하는 jdbc 라이브러리 중 하나이며, jdbc를 좀 더 쉽게 사용할 수 있도록 도와주는 클래스이다.

따라서 이를 사용하면 반복적인 코드를 줄이고, 간결하게 데이터베이스와 상호작용할 수 있다.

아래의 코드를 보면 위에 있는 JdbcMemberRepository와 기능은 같지만 훨씬 간결하여 코드의 가독성이 좋아진다는 것을 알 수 있다.

public class JdbcTemplateMemberRepository implements MemberRepository {
  private final JdbcTemplate jdbcTemplate;
  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;
    };
  }
}

마찬가지로 MemberRepository를 스프링 빈으로 등록을 할 때 코드를 수정해주어야한다.

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

카테고리:

업데이트: