상세 컨텐츠

본문 제목

[3.6] 스프링의 JdbcTemplate

토비의스프링

by kwanghyup 2020. 10. 29. 02:38

본문

스프링이 제공하는 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

관련글 더보기

댓글 영역