JdbcTemplate는 Jdbc를 이용한 DB 연동 프로그램을 좀 더 쉽게 작성하도록 해주는 라이브러리다.
단, SQL은 여전히 직접 작성해야 한다.
순수 Jdbc 방식과 달리 JdbcTempalte는 실무에서도 자주 쓰인다.
환경설정
기존 Jdbc와 같은 환경으로 진행한다.
// build.gradle에 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
// application.properties에 추가
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
Repository 폴더에 JdbcTemplateMemberRepository 클래스를 생성하여 다음과 같이 작성한다.
JdbcTemplateMemberRepository.java
package hdxian.hdxianspring.repository;
import hdxian.hdxianspring.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;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member2").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) {
// jdbcTemplate.query()의 리턴 타입은 List<T>
List<Member> result = jdbcTemplate.query("select * from member2 where id = ?", memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member2 where name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member2", memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
코드를 간략히 살펴보자.
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
JdbcTemplate은 JdbcTemplate 타입 객체를 사용한다.
스프링으로부터 DataSource를 인젝션받아 JdbcTemplate 객체를 생성한다.
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member2").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;
}
데이터 insert는 조금 복잡하다. jdbcTemplate 객체를 이용해 jdbcInsert 객체를 생성하고,
정해진 문법에 따라 데이터를 삽입한다.
JdbcTemplate의 자세한 내용은 따로 학습해야 할 정도로 자세하기 때문에, 지금 단계에서는 대략적인 생김새만 파악한다.
@Override
public Optional<Member> findById(Long id) {
// jdbcTemplate.query()의 리턴 타입은 List<T>
List<Member> result = jdbcTemplate.query("select * from member2 where id = ?", memberRowMapper(), id);
return result.stream().findAny();
}
데이터 조회는 간단하다.
jdbcTemplate의 query() 메서드를 이용해 직접 쿼리를 작성하여 DB에 전달한다.
쿼리의 ? 부분에 매핑되는 파라미터는 query() 메서드 뒤의 id 변수다.
이전의 순수 Jdbc와는 비교도 안되게 코드가 간단해졌다. 그전까지 직접 작성했던 execute 작성, 예외처리 등을 번거롭게 할 필요가 없어졌다.
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
람다식으로 표기되었지만, 원래는 아래와 같은 형태의 RowMapper<>타입 익명 객체를 리턴하는 형태다.
private RowMapper<Member> memberRowMapper() {
return new RowMapper<Member>() {
@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;
}
}
}
쿼리 실행 결과의 각 행을 Member 객체로 매핑해주는 메서드다.
jdbcTemplate.query()에서 이 메서드의 실행 결과를 List<Member> 형태로 리턴한다.
작성한 JdbcTemplateMemberRepository가 스프링에서 동작하도록 SpringConfig를 수정한다.
SpringConfig.java
package hdxian.hdxianspring;
import hdxian.hdxianspring.repository.JdbcMemberRepository;
import hdxian.hdxianspring.repository.JdbcTemplateMemberRepository;
import hdxian.hdxianspring.repository.MemberRepository;
import hdxian.hdxianspring.repository.MemoryMemberRepository;
import hdxian.hdxianspring.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);
}
}
동작하는 리포지토리를 바꿨지만, 실제 스프링을 띄워서 확인해볼 필요는 없다.
이전에 통합 테스트 코드를 작성했으므로 간단하게 테스트 가능하다.
'[inflearn] 스프링 입문' 카테고리의 다른 글
16. 스프링 데이터 JPA (0) | 2023.04.30 |
---|---|
15. 스프링 JPA (Java Persistence API) (0) | 2023.04.30 |
13. 스프링 통합 테스트 (0) | 2023.04.30 |
12. 순수 JDBC (0) | 2023.04.29 |
11. H2 데이터베이스 설치 (0) | 2023.04.29 |