스프링이 제공하는 JDBC코드용 템플릿인 JdbcTemplate를 사용하자.
앞에서 만들엇던 JdbcContext와 유사하다.
지금까지 만들었던 JdbcContext을 버리고
스프링의 JdbcTemplate로 바꿔보자.
JdbcTemplate의 초기화 코드
public class UserDao {
//...
private JdbcTemplate jdbcTemplate;
//...
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
이제 템플릿을 사용할 준비가 되었다.
#update
PreparedStatementCreateor 인터페이스
createPreparedStatement()메소드가 정의되어있다.
이 메소드는 템플릿으로부터 Connection을 제공받아
PreparedStatement를 만들어서 돌려준다.
deleteAll()메소드에 적용시켜보자.
public void deleteAll() throws SQLException {
this.jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement("truncate users");
}
});
}
테스트를 수행하여 확인한다.
이번에는 앞서 만든 executeSql()메소드와 유사한
내장 콜백을 사용하여 deleteAll()메소드를 바꿔보자.
public void deleteAll() throws SQLException {
this.jdbcTemplate.update("truncate users");
}
테스트를 수행하여 확인한다.
이번에는 마찬가지로 update()메소드를 사용하여
UserDao의 add()메소드를 바꿔보자.
public void add(final User user) throws ClassNotFoundException, SQLException {
String query = "insert into users values(?,?,?)";
this.jdbcTemplate.update(query,user.getId(),user.getName(),user.getPassword());
//두 번째부터 파라미터부터 순서대로 치환자(?)에 바인딩 된다.
}
# query()
query()메소드를 이용하여 getCount()메소드를 바꿔보자.
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 users");
}
}, new ResultSetExtractor<Integer>() {
@Override
public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
return rs.getInt(1);
}
}
);
}
테스트를 수행하여 확인한다.
query()메소드는 두 개의 파라미터를 콜백으로 받는다.
첫 번째 파라미터는 PreparedStatementCreator 이고
두 번째 파라미터는 ResultSetExtractor 이다.
PreparedStatementCreator은 템플릿으로 부터
Connection을 받고 PreparedStatement를 돌려준다.
(SQL 쿼리가 작성된다.)
ResultSetExtractor 템플릿으로부터 ResultSet을 받고
거기서 추출한 결과를 돌려준다.
ResultSet에서 추출할 수 있는 값의 타입은 다양하기 때문에
이것은 제네릭스 타입 파라미터를 갖는다.
이런 기능을 가진 콜백을 저장하고 있는 queryForObejct() 사용해보자.
(queryForInt는 deprecated되었다.)
public int getCount() throws SQLException {
String query = "select count(*) from users";
return this.jdbcTemplate.queryForObject(query,Integer.class);
}
두 번째 파라미터는 반환타입을 지정한다.
테스트를 수행하여 확인한다.
#queryForObject
get()메서드에 JdbcTemplate를 적용해보자.
ResultSet의 결과를 User오브젝트를 만들어 프로퍼티에 넣어줘야한다.
getCount()에 적용했던 ResultSetExtractor콜백 대신에
RowMapper콜백을 사용하겠다.
queryForObject()와 RowMapper를 적용한 get()메소드
public User get(String id) throws ClassNotFoundException, SQLException {
String query = "select * from users where id= ?";
return this.jdbcTemplate.queryForObject(query, new Object[] {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;
}
});
}
queryForObject() 메소드
쿼리의 결과가 하나의 로우일때 사용한다.
첫 번째 파리미터 : PreparedStatement를 만들기 위한 SQL
두 번째 파라미터 : 치환자에 바인딩 할 값
세 번째 파라미터 : 콜백
RowMapper콜백이 호출되는 시점에
ResultSet은 첫 번째 로우를 가리코 있으므로
다시 rs.next()를 호출할 필요는 없다.
queryForObject()는 SQL을 실행해서 받은 로우의 개수가
하나가 아니라면 예외를 던지도록 만들어져있다.
이때 던져 지는 예외는 EmptyResultDataAccessException이다.
#query()템플릿을 이용하는 getAll()구현
public List<User> getAll(){
return this.jdbcTemplate.query("select * from users oder 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;
}
});
}
#query()
여러개 개의 로우가 결과로 나오는 일반적인 경우에 사용한다.
리턴타입 : List<T>
타입은 파라미터로 넘기는 RowMapper<T>콜백 오브젝트에서 결정된다.
첫번째 파라미터에는 실행할 SQL쿼리를 넣는다.
바인딩할 파라미터가 있다면 두번째 파라미터에 추가한다.
없으면 생략하고 마지막 파라미터는 RowMapper콜백이다.
# 테스트 작성
@Test
public void getAll() {
dao.deleteAll();
dao.add(user1); // gyumee
List<User> users1 = dao.getAll();
assertEquals(users1.size(), 1);
checkSameUser(user1, users1.get(0));
dao.add(user2);// leegw700
List<User> users2 = dao.getAll();
assertEquals(users2.size(), 2);
checkSameUser(user1, users2.get(0));
checkSameUser(user2, users2.get(1));
dao.add(user3); //bumjin
List<User> users3 = dao.getAll();
assertEquals(users3.size(), 3);
checkSameUser(user3, users3.get(0));
checkSameUser(user1, users3.get(1));
checkSameUser(user2, users3.get(2));
}
// User 오브젝트의 내용을 비교
private void checkSameUser(User user1, User user2) {
assertEquals(user1.getId(), user2.getId());
assertEquals(user1.getName(), user2.getName());
assertEquals(user1.getPassword(), user2.getPassword());
}
테스트 보완
데이터가 없는 경우에 대한 검증 커드를 추가하여야 한다.
@Test
public void getAll() {
dao.deleteAll();
List<User> users0 = dao.getAll();
assertEquals(users0.size(), 0);
// 생략 ...
}
테스트를 수행하면 성공한다.
JdbcTemplate의 query()메소드는 예외적인 경우에는
크기가 0인 리스트 오브젝트를 리턴하기 때문이다.
# 재사용 가능한 콜백의 분리
이제 필요없어진 DataSource 인스턴스 변수를 제거하자.
public class UserDao {
//주석처리된 부분을 모드 삭제한다.
// private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
// public UserDao(){};
// public UserDao(DataSource dataSource) {
// this.dataSource = dataSource;
// }
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// 생략 ...
}
중복제거
get()메소드와 getAll()메소드를 살펴보면
사용한 RowMapper의 내용이 같다.
User용 RowMapper콜백을 메소드에서 분리해
중복을 없애고 재사용되게 만들자.
private RowMapper<User> userMapper =
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;
}
};
public User get(String id){
String query = "select * from users where id= ?";
return this.jdbcTemplate.queryForObject(query, new Object[] {id},this.userMapper);
}
public List<User> getAll(){
return this.jdbcTemplate.query("select * from users order by id", this.userMapper);
}
[5.2] 트랜잭션 서비스 추상화 (0) | 2020.10.31 |
---|---|
[5.1] 사용자 레벨 관리 기능 추가 (0) | 2020.10.30 |
[3.5] 템플릿과 콜백 (0) | 2020.10.29 |
[3.4] 컨텍스트와 DI (0) | 2020.10.29 |
[3.3] JDBC 전략 패턴의 최적화 (0) | 2020.10.28 |
댓글 영역