템플릿과 콜백의 기본적인 원리와 동작방식, 만드는 방법을 알아보았으니, 지금 부터는 Spring에서 제공하는 템플릿/콜백 기술을 살펴보자. 스프링은 JDBC를 이용하는 DAO에서 사용할 수는 다양한 템플릿과 콜백을 제공한다.
스프링이 제공하는 JDBC코드용 기본 템플릿은 JdbcTemplate이다. 앞에서 만든 JdbcContext와 비슷하지만 더 강력한 기능을 제공한다. 아래는 기존에 작성 JdbcContext를 이용하여 작성했던 코드이다. 지금 부터는 아래 코드를 JdbcTemplate을 이용하는 코드로 변경 해보자.
public class UserDao{
private DataSource dataSource;
JdbcContext jdbcContext;
public void setDataSource(DataSource dataSource) {
this.jdbcContext = new JdbcContext();
this.jdbcContext.setDataSource(dataSource);
this.dataSource = dataSource;
}
//반복되는 부분 분리
public void executeSQL(final String strSQL) throws SQLException{
this.jdbcContext.workWithStatementStrategy(
new StatementStrategy() {
public PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps=c.prepareStatement(strSQL);
return ps;
}
}
);
}
public void deleteAll() throws SQLException {
executeSQL("delete from users");
}
public void add(final User user) throws SQLException {
this.jdbcContext.workWithStatementStrategy(
new StatementStrategy() {
public PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement 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());
return ps;
}
}
);
}
//아직 템플릿 / 콜백 방식을 적용하지 않은 함수들
/*public User get(String id) throws SQLException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs =null;
User user = null;
try {
c = this.dataSource.getConnection();
ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
rs = ps.executeQuery();
if (rs.next()) {
user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
}
}catch(SQLException e) {
throw e;
}finally{
if(rs != null) {
try {
// try/catch문 사용하지 않았을때 rs.close()시 exception발생시 그다음 코드는 실행이 안된다.
// ps,c가 close가 안되는 문제가 발생하게 됨.
rs.close();
}catch(SQLException e) {
}
}
if(ps != null) {
try {
ps.close();
}catch(SQLException e) {
}
}
if(c != null) {
try {
c.close();
}catch(SQLException e) {
}
}
}
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
public int getCount() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
int count =0;
try {
c= dataSource.getConnection();
ps=c.prepareStatement("select count(*) from users");
rs=ps.executeQuery();
rs.next();
count = rs.getInt(1);
}catch(SQLException e) {
throw e;
}finally {
if(rs != null) {
try {
rs.close();
}catch(SQLException e) {
}
}
if(ps != null) {
try {
ps.close();
}catch(SQLException e) {
}
}
if(c != null) {
try {
c.close();
}catch(SQLException e) {
}
}
}
return count;
}*/
}
1. JdbcContext를 JdbcTemplate으로 변경했다.
private DataSource dataSource;
JdbcTemplate jdbcContext;
public void setDataSource(DataSource dataSource) {
this.jdbcContext = new JdbcTemplate();
this.jdbcContext.setDataSource(dataSource);
this.dataSource = dataSource;
}
2. 이제 deleteAll() 함수를 변경해보자.
public void deleteAll() throws SQLException {
this.jdbcTemplate.update("delete from users");
}
3. add()함수를 변경해보자
public void add(final User user) throws SQLException {
this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
user.getId(),user.getName(),user.getPassword());
}
4. 아직 템플릿/콜백 방식을 적용하지 않았던 메소드에 JdbcTemplate을 적용해보자.
getCount( ) 함수 수정
기존에 작성된 getCount( ) 함수는 JdbcTemplate의 query( ) 함수와 PreparedStatementCreator콜백과 resultSetExtractor 콜백을 사용하여 수정 할수 있다.
- PreparedStatementCreator callback 함수 - statement생성
- ResultSetExtractor callback 함수 - 쿼리실행후 얻은 Resultset에서 원하는 값 추출
public int getCount() throws SQLException {
return this.jdbcTemplate.query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
// TODO Auto-generated method stub
return con.prepareStatement("select count(*) from users");
}
}, new ResultSetExtractor<Integer>() {
public Integer extractData(ResultSet rs) throws SQLException,DataAccessException {
rs.next();
return rs.getInt(1);
}
});
}
"select count(*) from users" 처럼 SQL의 실행 결과가 하나의 정수 값이 되는 경우 이용할 수 있는 함수를 JdbcTemplate에서 제공하고 있다. 바로 queryForInt() 함수 이다. 이 함수를 이용하여 위 코드를 다시 수정해보자. 코드가 훨씬 간결해졌다.
public int getCount() throws SQLException {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
get( ) 메소드 수정
- queryForObject( ) 함수 사용
- RowMapper callback 사용
위에 두 함수를 사용하여 수정하였다.
public User get(String id) throws SQLException {
return this.jdbcTemplate.queryForObject("select * from users where id=? ",
new Object[] {id}, //-> SQL에 바인딩할 파라미터값, 가변인자 대신 배열을 사용
new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum)throws SQLException {
// TODO Auto-generated method stub
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
5. 모든 사용자 정보를 가져오는 getAll() 함수를 추가해 보자.
public List<User> getAll() throws SQLException{
return this.jdbcTemplate.query("select * from users",
new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum)throws SQLException {
// TODO Auto-generated method stub
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
위 코드는 JdbcTemplate의 query() 템플릿를 사용했다. 해당 query() 템플릿의 수행 Logic은 아래에 설명해 두었으니, 참고하기 바란다.
query () 템플릿 parameter
- 첫번째 : SQL문
- 두번째 : 바인딩할 파라미터가 있는 경우 넣고 없으면 생각가능
- 세번째 : RowMapper콜백이다.
query() 템플릿 수행 순서
- query() 템플릿은 SQL을 실행해서 얻은 Resultset의 모든 Row을 열어 RowMapper 콜백을 호출한다. 즉, SQL 쿼리 실행 결과 Row 수만큼 호출된다.
- 호출된 RowMapper콜백은 해당 Row를 User로 Mapping해서 돌려주게 된다.
- 이렇게 만들어진 User 오브젝트는 템플릿에서 미리 준비한 List<User> 컬렉션에 추가된다.
- 그리고 모든작업이 완료되면 템플릿은 List<User>을 리턴힌다.
6. 재사용 가능한 콜백 분리
DataSourc인스턴스 변수는 더이상 사용 하지 않으니, 제거하자.
하기 코드에서 보면 알수있듯이 get() , getAll() 함수에서 RowMapper가 중복됨을 확인했다. 중복되는 코드를 다시 분리시켜 보자.
중복되는 익명 내부 클래스를 인스턴스 변수 하나에 받아서 사용하도록 수정했다. 드디어 코드가 완성되었다.!!!!
public class UserDao{
JdbcTemplate jdbcTemplate;
private RowMapper<User> userMapper = new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum)throws SQLException {
// TODO Auto-generated method stub
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
};
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
}
public void deleteAll() {
this.jdbcTemplate.update("delete from users");
}
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=? ", this.userMapper,id);
}
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
public List<User> getAll() {
return this.jdbcTemplate.query("select * from users",this.userMapper);
}
}
'Spring' 카테고리의 다른 글
4장 예외 (0) | 2020.01.05 |
---|---|
3장 템플릿IV - 템플릿/콜백의 응용 (0) | 2020.01.04 |
3장 템플릿 II- 템플릿/ 콜백패턴 (0) | 2020.01.04 |
3장 템플릿I - 전략패턴 (0) | 2020.01.04 |
2장 테스트 (0) | 2019.12.28 |