Spring Boot; 훑어보기#3 – JDBCTemplate

🗓️

Spring 데이터 억세스

  • H2 데이터베이스
  • 순수 JDBC
  • 스프링 JdbcTemplate : JDBC 중복을 제거해서 만든 템플릿
  • JPA : 객체를 쿼리 없이 DB에 저장하는 방법
  • 스프링 데이터 JPA : JPA를 간단하게 쓰기 위한 스프링의 Wrapping

H2 데이터베이스

  • official site
  • bin/h2.sh 실행
    9f190e94ee0b476a899ebcfbf0a974de.png
  • JDBC URL : jdbc:h2:tcp://localhost/~/h2/test

파일로 직접 접근하지 말고 TCP로 접속.

  • build.gradle 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

JDBC 간단한 정리

  • Connection 객체 : DB연결 객체
  • PreparedStatement 객체 : 쿼리와 코드를 매칭해준다.
  • ResultSet 객체 : 쿼리 결과를 받는 객체
  • close() 구현 : DB사용 후 connection을 정리해야한다.
  • 스프링에서 JDBC를 붙일때 DataSourceUtils를 통해 getConnection한다.

JUnit과 AssertJ이 아닌 스프링 통합 테스트

  • @SpringBootTest :스프링 컨테이너와 테스트를 함께 실행한다.
  • @Transactional : 테스트코드에 붙이면 테스트가 끝나고 나서 작성된 DB데이터를 지운다. 테스트 케이스마다 작동해 다음 테스트에 영향을 미치지 않는다.
  • build.gradle 추가
testImplementation 'org.springframework.boot:spring-boot-starter-test'
  • 스프링 통합 테스트의 내용
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

    // 테스트 코드를 작성할때는 필드 주입방법으로 간단하게 진행한다.
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;
//......

그러면 왜 메모리DB같은 단위 테스트를 하는가?

  • 스프링 실행되는 시간만 해도 많은 비용이 발생한다. (테스트코드가 복잡해지면 부지기수로 증가함)
  • 스프링 컨테이너 없이 작은 단위에서 실행될 수 있는 테스트가 좋은 테스트.
  • 작은 단위의 테스트가 가능하다고 무조건 좋은 테스트는 아니지만 좋은 테스트일 가능성이 높음.

스프링 JdbcTemplate

  • JDBC API의 반복코드를 대부분 제거한다.
  • 쿼리는 직접 작성해야한다.
  • 많이 사용한다.

JdbcTemplate의 선언

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

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

JdbcTemplate의 select 구현

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate
        .query("select * from member where id = ?", memberRowMapper());
    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());
}

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

JdbcTemplate의 insert 구현

@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;
}
  • SimpleJdbcInsert 객체 : 테이블과 칼럼을 잡으면 쿼리를 작성할 필요가 없어진다.