Today I Learned_230710
토비의 스프링 3.1
제네릭스 타입을 사용하여 템플릿/콜백을 범용적으로 만들면 여러 기능들을 쉽게 구현할 수 있다.
변하는 부분이 콜백이 된다!
JdbcTemplate
스프링이 제공하는 JDBC 코드용 기본 템플릿
생성자의 파라미터로 DataSource를 주입받아 사용
update()
- PreparedStatementCreator타입의 콜백을 받아서 사용
- 콜백을 받지 않고 내장 콜백을 사용할 수도 있음
- SQL과 함께 가변인자로 선언된 파라미터를 제공하면 파라미터 바인딩 가능
// 콜백 받아 사용
public void deleteAll() throws SQLException {
this.jdbcTemplate.update(
new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement("delete from users");
}
}
);
}
// 내장 콜백 사용
public void deleteAll() throws SQLException {
this.jdbcTemplate.update("delete from users");
}
// 가변인자 파라미터 받아 사용
public void add(final User user) throws SQLException {
this.jdbcTemplate.update("insert into USER(id, name, password) values (?, ?, ?)",
user.getId(), user.getName(), user.getPassword());
}
query()
- PreparedStatementCreator 콜백과 ResultSetExtractor 콜백을 파라미터로 받아 사용
- PreparedStatementCrator는 connection을 받아 PreparedStatement를 돌려줌
- ResultSetExtractor는 템플릿을 수행해 ResultSet을 받아 수행, 그 결과를 돌려줌
- 두 번의 템플릿/콜백이 동작한다고 보면 된다.
public int getCount() throws SQLException {
return this.jdbcTemplate.query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement("select count(*) from USER");
}
}, new ResultSetExtractor<Integer>() {
@Override
public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
return rs.getInt(1);
}
});
}
queryForObject()
- ResultSetExtractor는 ResultSet을 한 번 전달받아 알아서 추출작업을 진행하고 최종 결과만 리턴
- RowMapper는 ResultSet의 로우 하나를 매핑하기 위해 사용
- queryForObject(String, Object[], RowMapper
)는 deprecated됨
public User get(String id) throws SQLException {
return this.jdbcTemplate.queryForObject("select * from USER where id = ?",
new Object[] {id}, // SQL에 바인딩할 파라미터 값. 가변인자 대신 배열을 사용
new RowMapper<User>() {
@Override
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;
}
});
}
- 한개의 Row만 return할 것을 기대한 함수
- row의 개수가 하나가 아니라면 예외 발생
- EmptyResultDataAccessException
query()
public List<User> getAll() {
return this.jdbcTemplate.query("select * from USER order by id",
new RowMapper<User>() {
@Override
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;
}
});
}
- RowMapper는 쿼리의 결과인 ResultSet의 모든 row를 열람하면서 로우마다 RowMapper 콜백을 호출
- 현재 row의 내용을 User타입 오브젝트에 매핑해서 돌려줌
테스트를 작성할 때 미리 예외상황에 대한 일관성 있는 기준을 정해두고 이를 테스트로 만들어서 검증하자.
로드 존슨 아저씨는 테스트를 작성할 때 항상 네거티브 테스트부터 만들었다고 한다.
템플릿/콜백 패턴을 잘 활용하면 응집도가 높은 코드를 만들 수 있다.
데이터를 어떻게 조작할지 / DB에 어떻게 붙을지 분리할 수 있다.