본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring basic

스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 스프링 JdbcTemplate

by 방구석 대학생 2021. 11. 11.

"인프런의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 글 입니다."

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

 

스프링 JdbcTemplate

- 환경설정은 순수 JDBC 와 동일하다.

스프링 JdbcTemplate 과 MyBatis 같은 라이브러리는 JDBC API 에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL 은 직접 작성해야 한다.

(pro.gg 프로젝트 때도 마찬가지로 MyBatis 를 활용하면서 SQL 은 전부 다 직접 작성해 주었다.)

 

다음과 같이 코드를 작성해주자.

- JdbcTemplateMemberRepository.java

public class JdbcTemplateMemberRepository implements MemberRepository{

    private final JdbcTemplate jdbcTemplate;

    @Autowired // 생성자가 하나만 있을 경우 어노테이션 생략 가능
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

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

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

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

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

 

DataSource 객체 의존성을 주입 받고 추후에 설정파일 까지 변경하여 JdbcTemplate 클래스 객체를 스프링 빈으로 등록해야 한다.

JdbcTemplate 의 경우 조회부터 먼저 실습해보자.

 

- JdbcTemplateMemberRepository.java

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

//  return new RowMapper<Member>() { // alt + enter 를 통해 위와 같이 람다 식으로 변환해줄 수 있다.
//      @Override
//      public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
//          Member member = new Member();
//          member.setId(rs.getLong("id"));
//          member.setName(rs.getString("name"));
//          return member;
//
//      }
//  };
}

* 여기서 RowMapper 클래스는 SQL 의 실행 결과를 객체에 맞게 매핑해주는 역할을 해준다.

* Resultset object -> DTO Object

 

지금 위에서 작성한 코드와 순수 JDBC 를 사용했을 때의 코드를 비교해보면 코드의 길이에 있어서 굉장히 큰 차이가 있는 것을 발견할 수 있다.

데이터 저장 메소드의 경우 코드가 좀 복잡하다.

아래의 코드를 보자.

- JdbcTemplateMemberRepository.java

@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 는 테이블 명과 컬럼 명을 명시해주면 개발자가 쿼리를 작성해줄 필요 없이 쉽게 insert 쿼리를 만들 수 있게끔 해주는 클래스이다.

위의 코드를 보면 SimpleJdbcInsert 클래스로 만들어진 객체에 테이블 명(member)과 컬럼 명(id)을 명시하여 insert 쿼리를 만듬과 동시에, member 테이블에서 키 값에 설정해둔 auto_increment 설정으로 인해 값이 자동으로 커진 기본 키 값을 컬럼 명에 자동으로 입력 시켜 주고 있다.

 

이후 삽입해야 하는 name 데이터를 Map 클래스 객체에 name 키 값의 value 로 적재해준 다음, 해당 객체를 파라미터로 하는 MapSqlParameterSource 객체를 만들어준다.

그렇게 만들어준 객체를 다시 파라미터로 하는 executeAndReturnKey 메소드를 실행시킬 때 해당 객체에 SQL 파라미터 값으로 실려있는 name 데이터 값을 생성한 insert 쿼리에 적재한다.

 

insert 쿼리에 name 데이터가 적재되고 나면 executeAndReturnKey 메소드로 인해 해당 쿼리가 데이터베이스로 전달되어 실행되고, 실행 결과를 반환받을 때 삽입된 데이터의 키 값을 반환받아서 key 변수에 적재해주고, 

해당 변수를 long 타입으로 캐스팅 한 다음 Member 클래스 객체에 세팅해주고 난 뒤 해당 객체를 리턴해준다.

(만약 실무에서 이런것들을 써야 하는 상황이 와도 관련 문서를 확인해보면 어떻게 사용하면 되는지 친절하게 잘 나와있다고 하니 참고 하도록 하자.)

 

사실 JdbcTemplate 도 상세한 것들을 보자면 어마어마 하다. 이번 강의 동안은 대충 이런거다 하고 감만 잡는 수준으로 넘어가도 된다.

 

이제 나머지 메소드들도 코드를 작성해주자.

- JdbcTemplateMemberRepository.java

@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 memeber", memberRowMapper());
}

 

Repository 에서 필요한 코드들은 모두 작성해 줬으니 이제 설정파일 에서 스프링 빈에 등록하는 Repository 클래스 객체를 바꿔주자.

- SpringConfig.java

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

 

그럼 이제 어플리케이션을 동작시켜 코드가 잘 돌아가는지 확인만 하면 되는걸까?

그러기에 앞서 이전 글에서 스프링 통합 테스트를 해봤던 것을 기억할 것이다.

Repository 는 바뀌었으나 여전히 여기서 작성했던 테스트 코드를 돌리는 것 만으로도 코드들이 잘 동작하는지 확인이 가능하다.

왜냐, MemberServiceIntegrationTest 에서 주입받은 의존성에 JdbcTemplateMemberRepository 로 변경된 객체가 그대로 주입되기 때문이다.

(SpringConfig 에서 해당 객체를 스프링 빈으로 등록하도록 바꾸었기 때문에 가능하다.)

 

그럼 이번엔 어플리케이션이 아니라 앞전에 작성했던 테스트 코드들을 전체적으로 수행시켜서 테스트가 잘 통과되는지를 확인해보자.

테스트가 잘 통과되었다.

그 결과, 테스트가 잘 통과되는 것을 확인해 볼 수 있을 것이다.

 

* 번외

현업에서는 작은 버그가 수억, 수십억의 피해로 돌아올 수 있다.

그렇기에 테스트 코드를 잘 작성하는 것이 정말 중요하고, 테스트 코드를 잘 작성하기 위한 노력을 많이 기울여야 한다.

실제 현업을 봐도 개발하는 시간을 100 이라고 놓았을 때, 60은 테스트 코드를 작성하는 경우가 많다고 한다.

개발하고 있는 프로젝트가 크면 클수록 테스트 코드를 잘 작성하는 것이 정말 중요해진다.

 

다음 시간부터는 JPA 에 대해 알아보자.(작년에 JPA 에 대해 많은 공부를 했었는데 스스로 얼마나 기억을 하고 있을지가 궁금해진다.)