🌟 템플릿
: 고정된 작업 흐름을 가진 코드를 재사용한다
🌟 콜백
: 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트
🌟 스프링은 JDBC를 이용하는 DAO에서 사용할 수 있도록 준비된 다양한 템플릿과 콜백을 제공
🌟 JdbcTemplate
: JDBC 코드용 기본 템플릿
//JDBCTemplate의 초기화를 위한 코드
public class UserDao {
...
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.dataSource = dataSource;
}
JDBCTemplate
은 생성자의 파라미터로 DataSource
를 주입//JdbcTemplate을 적용한 deleteAll() 메소드
public void deleteAll() {
this.jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement("delete from users");
}
}
);
}
콜백
: PraparedStatementCreator
인터페이스의 createPreparedStatement()
메소드템플릿 메소드
: update()
//내장 콜백을 사용하는 update()로 변경한 deleteAll() 메소드
public void deleteAll() {
this.jdbcTemplate.update("delete from users");
}
//add() 메소드의 콜백 내부
PreparedStatemant ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
콜백
의 2가지 작업 : PreparedStatement
를 만드는 것, 파라미터를 바인딩//JdbcTemplate에서 제공하는 편리한 메소드로 바꿈
this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
user.getId(), user.getName, user.getPassword());
update()
메소드 이용//JdbcTemplate을 이용해 만든 getCount()
public int getCount() {
return this.jdbcTemplate.query(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement("select count(*) from users");
}
}, new ResultSetExtractor<Integer>() {
public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
return rs.getInt(1);
}
});
}
getCount()
: SQL 쿼리를 수행하고 ResultSet을 통해 결과값을 가져오는 코드첫 번째 콜백
: PreparedStatementCreator()
-> Connection
을 받고 PreparedStatement
를 돌려줌템플릿 메소드
: query()
두 번째 콜백
: ResultSetExtractor
-> PreparedStatement
의 쿼리를 실행해서 얻은 ResultSet
을 전달받는 콜백//queryForInt()를 사용하도록 수정한 getCount()
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
getCount()
가 한 줄로 바뀜get()
메소드에 JdbcTemplate
적용해보기get()
: SQL은 바인딩이 필요한 치환자를 갖고 있음 -> add()에서 사용했던 방법 적용, ResultSet의 결과를 User 오브젝트를 만들어 프로퍼티에 넣어주기//queryForInt()를 사용하도록 수정한 getCount()
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from users where id = ?",
new Object[] {id}, //SQL에 바인딩할 파라미터 값을 가변인자 대신 배열을 사용
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;
}
});
//ResultSet 한 로우의 결과를 오브젝트에 매핑해주는 RowMapper 콜백
}
RowMapper
를 좀 더 사용해보기getAll()
: 모든 사용자 정보를 가져오는 메소드, 테이블의 모든 로우 가져오기//getAll()에 대한 테스트
@Test
public void getAll() {
dao.deleteAll();
dao.add(user1); // Id: gyumee
List<User> users1 = dao.getAll();
assertThat(users1.size(), is(1));
checkSameUser(user1, users1.get(0));
dao.add(user2); // Id: leegw700
List<User> users2 = dao.getAll();
assertThat(users2.size(), is(2));
checkSameUser(user1, users2.get(0));
checkSameUser(user2, users2.get(1));
dao.add(user3); // Id: bumjin
List<User> users3 = dao.getAll();
assertThat(users3.size(), is(3));
checkSameUser(user3, users3.get(0));
checkSameUser(user1, users3.get(1));
checkSameUser(user2, users3.get(2));
}
//user3의 id 값이 알파벳순으로 가장 빠르므로 getAll( )의 첫 번째 엘리먼트여야 한다.
//User 오브젝트의 내용을 비교하는 검증 코드. 테스트에서 반복적으로 사용되므로 분리해놓았다.
private void checkSameUser(User user1, User user2) {
assertThat(user1.getId(), is(user2.getId()));
assertThat(user1.getName(), is(user2.getName()));
assertThat(user1.getPassword(), is(user2.getPassword()));
}
query()
: 여러 개의 로우가 결과로 나오는 일반적인 경우, 리턴 타입 List<T>//getAll() 메소드
public List<User> getAll() {
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;
}
});
}
//데이터가 없는 경우에 대한 검증 코드가 추가된 getAll() 테스트
public void getAll() {
dao.deleteAll();
List<User> users0 = dao.getAll();
assertThat(users0.size(), is(0));//데이터가 없을 때는 크기가 0인 리스트 오브젝트가 리턴돼야 한다.
...
//불필요한 DataSource 변수를 제거하고 남은 UserDao의 DI 코드
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
//DataSource 오브젝트는 JdbcTemplate을 만든 후에는 사용하지 않으니 저장 해두지 않아도 된다.
//재사용 가능하도록 독립시킨 RowMapper
public class UserDao {
private RowMapper<User> userMapper =
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;
}
};
}
//공유 userMapper를 사용하도록 수정한 get(), getAll()
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from users where id = ?", new Object[] {id}, this.userMapper);
}
public List<User> getAll() {
return this.jdbcTemplate.query("select * from users order by id", this.userMapper);
}
//JdbcTemplate을 적용한 UserDao 클래스
public class UserDao {
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private JdbcTemplate jdbcTemplate;
private RowMapper<User> userMapper =
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;
}
};
public void add(final User user) {
this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)", user.getId(), user.getName(), user.getPassword());
}
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from users where id = ?", new Object[] {id}, this.userMapper);
}
public void deleteAll() {
this.jdbcTemplate.update("delete from users");
}
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
public List<User> getAll() {
return this.jdbcTemplate.query("select * from users order by id",this.userMapper);
}
}
첫째
: userMapper
가 인스턴스 변수로 설정되어 있고, 한 번 만들어지면 변경되지 않는 프로퍼티와 같은 성격을 띠고 있으니 아예 UserDao
빈의 DI용 프로퍼티로 만들어 버리기둘째
: DAO
메소드에서 사용하는 SQL
문장을 UserDao
코드가 아니라 외부 리소스에 담고 이를 읽어와 사용하게 하기