스프링이 제공하는 JDBC를 이용하는 DAO에서 사용할 수 있는 템플릿과 콜백
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.dataSource = dataSource;
}
JDBC Template을 사용하기 위해 기존의 JdbcContext를 빼고 바꿔준다.
update
기존 전략 인터페이스의 메서드를 통해 전략을 적용했던 것과 똑같이
JDBC Template이 제공하는 콜백 중 PreparedStatementCreator 인터페이스의
createPreparedStatement 메서드를 사용하면 된다.
PreparedStatementCreator 타입의 콜백을 받아서 사용하는
JDBC Template의 메서드는 update()다
기존의 코드와 비교해보겠다.
public void deleteAll() throws SQLException {
workWithStatementStrategy(
new StatementStrategy() {
public PreparedStatement makePreparedStatement(Connection c)
throws SQLException {
return c.prepareStatement(executeSql());
}
}
);
}
public void deleteAll() throws SQLException {
this.jdbcTemplate.update("delete from users");
}
위가 기존의 코드고 아래가 JDBC 템플릿의 콜백 메서드를 사용한 예시다.
눈으로만 봐도 코드가 간결해진 것을 알 수 있는데
executeSql 메서드를 사용하여 쿼리문을 전달 받던거만 제외하면
코드의 구성은 똑같다고 볼 수 있다.
public void add(final User user) throws SQLException {
this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
user.getId(), user.getName(), user.getPassword());
}
add 작업도 마찬가지로 update 메서드를 사용하여 할 수 있다.
추가하고자 하는 양식에 맞게 순서대로 파라미터로 전달해주면 된다.
queryForInt
public int getCount() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select count(*) from users");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
ps.close();
c.close();
return count;
}
위의 메서드에서는 쿼리를 실행한 후에 결과를 ResultSet을 통해 값을 가져온다.
해당 작업에서도 사용할 수 있는 템플릿이 있다.
마찬가지로 PreparedStatementCreator 콜백과 ResultSetExtractor 콜백을
파라미터로 받는 query 메서드를 사용한다.
ResultSetExtractor 콜백은 PreparedStatement의 쿼리를 실행해 얻은
ResultSet을 전달 받는 콜백이다.
public int getCount() {
return this.jdbcTemplate.query(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection c) throws SQLException {
return c.preparedStatement("select count(*) from users");
}
}, new ResultSetExtractor<Integer>() {
public Integer extractorData(ResultSet rs) throws SQLException {
rs.next();
return rs.getInt(1);
}
}
}
위의 코드는 query 메서드를 적용한 코드다.
살펴보면 query 메서드의 파라미터로
PreparedStatementCreator 콜백과 ResultSetExtractor 콜백을 전달 받는다.
PreparedStatementCreator 콜백은 템플릿으로부터
커넥션을 받아 PreparedStatement(SQL문)을 돌려준다.
ResultSetExtractor 콜백은 템플릿으로부터
ResultSet을 받아 추출한 결과를 돌려준다.
즉, query(쿼리문, ResultSet에서 추출한 결과)
위의 query 메서드에서 사용되는 콜백 오브젝트들은 모두 재사용하기 좋은 구조를 가지고 있는데,
PreparedStatementCreator 콜백은 재사용 방법을 알아봤으니
ResultSetExtractor 콜백의 재사용 방법을 알아보겠다.
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
위의 코드처럼 JDBC 템플릿에서 제공하는 queryForInt 메서드를 사용하면
위의 긴 코드를 한 줄로 간단하게 사용할 수도 있다.
JDBC 템플릿은 스프링이 제공하는 클래스이지만 DI 컨테이너를 필요로 하지 않아서
사용하고자 하는 클래스에서 JdbcTemplate 오브젝트를 생성하고
필요한 DataSource를 전달해주기만 하면 된다.
queryForObject
public User get(String id) throws SQLException {
Connection c = this.dataSource.getConnection();
PreparedStatement ps = c
.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
}
rs.close();
ps.close();
c.close();
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
위의 코드는 기존의 get 메서드인데 코드가 아주 길고 복잡하다.
PreparedStatementCreator 콜백을 사용하여 SQL문도 처리해야하고
SQL문이 바인딩이 필요한 치환자(id)를 갖고 있으며,
쿼리 결과를 User 오브젝트로 만들어야 한다.
getCount 메서드처럼 특정 값을 리턴하는 것이 아닌
오브젝트를 리턴해야하기 때문에 ResultSetExtractor 콜백을 사용하는 것이 아니라
RowMapper 콜백을 사용해야 한다.
두 개의 콜백 모두 템플릿으로부터 ResultSet을 전달 받아 필요한 정보를 추출하지만
ResultSetExtractor은 한 번만 전달 받아 알아서 결과를 리턴해주지만
RowMapper은 로우 하나를 매핑할 때마다 호출된다.
get 메서드는 결국 사용자 정보를 하나 가져오는 메서드이기 때문에
하나의 사용자 정보만 매핑하면 되니 ResultSet의 첫 번째 로우만 매핑한다.
이러한 작업들을 해주는 템플릿 메서드는 queryForObject가 있다.
queryForObject(쿼리문, 조회할 조건(바인딩할 파라미터 값), RowMapper 콜백으로 매핑)
위와 같은 양식으로 코드를 작성하면 된다.
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from users where id = ?",
new Object[] {id},
new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum)
throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
코드가 엄청 혁신적으로 짧아지고 그런 것은 아니지만 어느정도 간결해졌다.
queryForObject 템플릿 메서드의 첫 번째 파라미터로
"select * from users where id = ?" 라는 쿼리문을 전달했고
두 번째 파라미터로 해당 쿼리문에 바인딩 하기 위한 기본키인 id를 전달 후에
쿼리문의 결과를 RowMapper 콜백을 통해 User 오브젝트로 매핑한다.
query()
queryForObject 템플릿 메서드는 하나의 사용자만 가져오는 경우에 사용했다면,
모든 사용자의 정보를 가져오는 기능에 사용하는 템플릿 메서드도 있다.
기존에는 한 명의 사용자 정보만 가져왔으나 이번엔 모든 사용자를 가져오니
User 오브젝트 타입의 List를 사용하면 된다.
public List<User> getAll() { }
모든 사용자의 정보를 가져오는 메서드의 시그니처는 위와 같이 선언한다.
queryForObject와 비슷하지만 모든 값을 가져오는 메서드이니
파라미터로 바인딩할 조건을 받을 필요가 없이
쿼리문과 RowMapper 콜백만 받는다.
조건이 있는 경우에는 바인딩할 파라미터를 추가한다.
return this.jdbcTemplate.query("select * from users order by id",
new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum)
throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
});
모든 사용자 정보를 id순으로 얻는 쿼리문의 수행 결과인 ResultSet의
모든 로우를 열람하면서 로우마다 RowMapper 콜백을 호출하여
User 오브젝트로 변환하여 리스트에 추가한다.