MemberRepository 구현 - JdbcTemplate

guswls·2023년 1월 30일

스프링 입문

목록 보기
10/13
post-thumbnail

이 포스트는 김영한 이사님의 스프링 입문 강의를 듣고 작성하였습니다.

JdbcTemplatejdbc의 반복적인 코드를 제거해준다. 하지만 SQL문은 직접 작성해주어야 한다. 참고로 JdbcTemplate은 실무에서도 많이 쓰인다고 한다.

1. JdbcTemplate을 이용한 구현

1-1. 기본 세팅

package memberpractice.memberpractice.repository;

import memberpractice.memberpractice.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

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

public class JdbcTemplateMemberRepository implements MemberRepository{

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
		return null;
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

다른 구현체들과 같이 MemberRepository인터페이스를 상속받아 구현한다. Jdbc와 동일하게 DataSource를 주입받아야 한다. 참고로 생성자가 하나일 경우 스프링 빈으로 등록된 것에 대해서는 @AutoWired어노테이션을 생략해도 된다.

1-2. findById 구현

package memberpractice.memberpractice.repository;

import memberpractice.memberpractice.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

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

public class JdbcTemplateMemberRepository implements MemberRepository{

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public Member save(Member member) {
        return null;
    }

    @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) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }

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

코드의 밑단에 보면 RowMapper라는 것을 찾아볼 수 있다. 값을 조회하는 쿼리를 날려서 얻은 결과 값에 대해서 RowMapper를 통해서 맵핑을 시켜주어야 한다. RowMapper안을 들여다보면 rs, 즉 ResultSet으로 값을 받아오는 것을 확인할 수 있다.

그 후 findById에서는 List로 가져온 데이터를 스트림으로 변환하여 값을 return하게 된다. 이 부분에 대해선 앞서 다뤘었기 때문에 생략한다.

기존에 우리가 jdbc로 구현한 findById와 비교하면 코드의 양이 확연하게 줄어든 것을 확인할 수 있다.

1-3. save 구현

package memberpractice.memberpractice.repository;

import memberpractice.memberpractice.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;

    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());
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }

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

save를 살펴보면 신기하게 SQL문이 없다. 대신에 SimpleJdbcInsert라는 것을 사용한 것을 확인할 수 있다.

대략적인 원리로는 테이블 명pk로 사용할 값, 저장할 값을 지정하면 쿼리문을 직접 만들어준다고 한다. 이에 대해서는 공식문서를 비롯한 다른 자료를 참고하여도 된다.

1-4. 전체 코드 구현

package memberpractice.memberpractice.repository;

import memberpractice.memberpractice.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;

    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;
        };
    }
}

findByNamefindAll은 쿼리문만 수정하면 된다.

1-5. SpringConfig 수정

우리가 새롭게 구현한 JdbcTemplateMemberRepositorySpingConfig를 수정해주자.

package memberpractice.memberpractice;

import memberpractice.memberpractice.repository.JdbcMemberRepository;
import memberpractice.memberpractice.repository.JdbcTemplateMemberRepository;
import memberpractice.memberpractice.repository.MemberRepository;
import memberpractice.memberpractice.repository.MemoryMemberRepository;
import memberpractice.memberpractice.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@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 JdbcMemberRepository(dataSource);
        return new JdbcTemplateMemberRepository(dataSource);
    }
}

2. 실행

통합 테스트가 정상적으로 실행된 것을 확인할 수 있다.

3. 총정리

이렇게 해서 JdbcTemplate을 이용하여 MemberRepository를 구현하고 통합 테스트까지 진행해 보았다. 여기서 다루진 않았지만 강의 마지막에 사소한 오류가 발생하여 김영한 이사님이 테스트 코드의 중요성을 더욱 강조하셨었다.

이렇게 강의를 들을 때 이외에 실제 프로젝트를 할 때도 테스트 코드를 작성하는 것에 대해서 잘 연습을 해두어야겠다.

profile
안녕하세요

0개의 댓글