[Spring] Spring의 JDBCTemplate

Zoe·2022년 1월 18일
0

Spring

목록 보기
6/9
post-thumbnail

Spring의 JDBCTemplate


🌟 템플릿 : 고정된 작업 흐름을 가진 코드를 재사용한다
🌟 콜백 : 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트
🌟 스프링은 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를 주입

✅ update()

//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()
  • 파라미터로 SQL문장을 전달
//내장 콜백을 사용하는 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());
  • add() 메소드에서 만드는 콜백의 2가지 작업 : PreparedStatement를 만드는 것, 파라미터를 바인딩
//JdbcTemplate에서 제공하는 편리한 메소드로 바꿈
this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
	user.getId(), user.getName, user.getPassword());
  • update() 메소드 이용

✅ queryForInt()

//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()가 한 줄로 바뀜

✅ queryForObject()

  • 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 콜백
}

✅ query()

1️⃣ 기능 정의와 테스트 작성

  • 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()));
}

2️⃣ query() 템플릿을 이용하는 getAll() 구현

  • 위의 테스트를 성공시키는 getAll() 메소드
  • 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;
			} 
       		});
}

3️⃣ 테스트 보완

//데이터가 없는 경우에 대한 검증 코드가 추가된 getAll() 테스트
public void getAll() { 
	dao.deleteAll();
	List<User> users0 = dao.getAll();
	assertThat(users0.size(), is(0));//데이터가 없을 때는 크기가 0인 리스트 오브젝트가 리턴돼야 한다.
...

✅ 재사용 가능한 콜백의 분리

  • UserDao 코드 보기

1️⃣ DI를 위한 코드 정리

//불필요한 DataSource 변수를 제거하고 남은 UserDao의 DI 코드
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
	this.jdbcTemplate = new JdbcTemplate(dataSource);
}
//DataSource 오브젝트는 JdbcTemplate을 만든 후에는 사용하지 않으니 저장 해두지 않아도 된다.

2️⃣ 중복 제거

//재사용 가능하도록 독립시킨 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);
}

3️⃣ 템플릿/콜백 패턴과 UserDao

//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 코드가 아니라 외부 리소스에 담고 이를 읽어와 사용하게 하기
  • 두 가지 기능은 다른 스프링 기술을 살펴보고 개선시킬 예정
profile
iOS 개발자😺

0개의 댓글