지난 포스팅에서 Spring JDBC가 무엇인지, 그리고 어떤 도구들을 사용해 DB에 접근할 수 있는지에 대해서 알아보았다.
이번 포스팅에서는 JdbcTemplate을 사용해서 DB에 접근하고, Querying과 Updating을 하는 방법에 대해 공부해볼 것이다!
💋 JdbcTemplate
- Spring에서 제공하는 JDBC Core 패키지에서 가장 핵심이 되는 클래스
- 리소스의 생성과 해제를 처리해서 연결을 닫는 것을 까먹고 못하게 되는 등의 일반적인 오류를 방지한다.
- SQL 쿼리를 실행한다.
- SQL문을 실행하여 DB에서 데이터를 변경한다. 예를 들어 DB에 새로운 데이터를 추가하거나, 기존 데이터를 업데이트하거나 삭제하는 작업을 한다.
- SQL 쿼리의 실행 결과로 ResultSet을 생성한다.
- JDBC 예외를 잡아서 org.springframework.dao 패키지에서 정의된 일반적인 예외 계층 구조로 변환한다.
참고: ResultSet 인스턴스는 SQL 쿼리를 실행하여 DB에서 검색한 결과를 포함하는 객체이다.
💋 JdbcTemplate을 사용한 Querying (SELECT)
SQL 쿼리를 통해서 원하는 값을 찾아와 보자!
👍🏻 queryForObject
- 단일 결과 행을 반환하는 쿼리를 실행할 때 주로 사용된다.
- 결과 집합이 없거나 여러 개의 결과 행이 있으면 예외가 발생한다.
이 메서드는 딱 하나의 결과만을 반환하는 쿼리를 위해서 사용되는데, 파라미터가 다양하게 오버로딩 되어 있어서, 용도에 따라 사용하면 된다!
아래에서는 오버로딩된 몇 가지의 메서드를 소개하겠다!
✔ <T> T queryForObject(String sql, Class<T> requiredType)
아래와 같이 사용하면, 내가 원하는 전체 개수를 얻을 수 있다.
/**
* public <T> T queryForObject(String sql, Class<T> requiredType)
*/
public int count() {
String sql = "select count(*) from customers";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
✔ <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args)
/**
* public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args)
*/
public String getLastName(Long id) {
String sql = "select last_name from customers where id = ?";
return jdbcTemplate.queryForObject(sql, String.class, id);
}
이 쿼리문은 중간에 ?가 있다. 완성되지 않은 쿼리문이다.
@Override
public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException {
return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));
}
앞서 나왔던 예시에서처럼, sql문과 required type을 적어주고, 물음표에 들어갈 내용들을 순서대로 뒤에 적어주면 된다. 물음표 없으면 안 적으면 된다.
✔ <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
/**
* public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
*/
public Customer findCustomerById(Long id) {
String sql = "select id, first_name, last_name from customers where id = ?";
return jdbcTemplate.queryForObject(sql, customerRowMapper, id);
}
JDBC Template의 RowMapper는 ResultSet에서 가져온 데이터를 객체에 매핑하는 역할을 한다.
private final RowMapper<Customer> customerRowMapper = (resultSet, rowNum) -> {
Customer customer = new Customer(
resultSet.getLong("id"),
resultSet.getString("first_name"),
resultSet.getString("last_name")
);
return customer;
};
- resultSet은 쿼리 결과를 담고 있는 객체로, ResultSet에서 가져온 데이터를 매핑하기 위해 사용한다.
- rowNum은 ResultSet에서 가져온 데이터의 행 번호이다. RowMapper에서 rowNum을 사용하면, ResultSet에서 가져온 데이터의 행 번호를 사용하여 객체를 매핑할 수 있다.
👍🏻 query
- 결과를 List 형태로 반환하는 쿼리를 실행할 때 주로 사용된다.
- 결과는 RowMapper 인터페이스를 구현하는 객체를 사용하여 각 행을 매핑하여 반환한다.
- 결과 집합이 없을 때는 빈 List를 반환한다.
✔ <T> List<T> query(String sql, RowMapper<T> rowMapper)
/**
* public <T> List<T> query(String sql, RowMapper<T> rowMapper)
*/
public List<Customer> findAllCustomers() {
String sql = "select id, first_name, last_name from customers";
return jdbcTemplate.query(sql, customerRowMapper);
}
여기에서도 RowMapper 인터페이스를 구현하는 객체가 사용되었다.
✔ <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
아까 queryForObject에서와 마찬가지로 중간에 ?로 빵꾸 뚫려있어도, 뒤에 쭉 적으면 된다.
/**
* public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
*/
public List<Customer> findCustomerByFirstName(String firstName) {
String sql = "select id, first_name, last_name from customers where first_name = ?";
return jdbcTemplate.query(sql, customerRowMapper, firstName);
}
💋 JdbcTemplate을 사용한 Updating (INSERT, UPDATE, DELETE)
select랑 비슷하니, 간단하게 나열하겠음! 사실 이 포스팅은 나중에 내가 찾아보기 위한 용이므루...
✔ int update(String sql, @Nullable Object... args)
/**
* public int update(String sql, @Nullable Object... args)
*/
public void insert(Customer customer) {
String sql = "insert into customers (first_name, last_name) values (?, ?)";
jdbcTemplate.update(sql, customer.getFirstName(), customer.getLastName());
}
update() 메서드는 데이터베이스에서 실행된 업데이트 쿼리의 결과로 변경된 행의 수를 반환한다.
만약 변경된 행이 없으면 0을 반환한다.
같은 방식으로 쿼리문만 변경하면 DELETE도 가능하다.
public int delete(Long id) {
String sql = "delete from customers where id = ?";
return jdbcTemplate.update(sql, id);
}
✔ int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
Retrieving Auto-generated Keys에 관한 내용이다. 즉, 데이터베이스에서 자동으로 생성된 키 값을 가져오는 방법이다.
예를 들어, MySQL에서는 AUTO_INCREMENT 키워드를 사용하여 자동으로 증가하는 키 값을 생성할 수 있다.
이 메서드는 첫 번째 파라미터로는 PreparedStatementCreator를 받는데, 이건 insert 쿼리문을 물음표 같은 빈 곳 없이 완벽하게 특정하기 위한 것이다.
두 번째 파라미터는 KeyHolder이고, 이건 update가 성공적으로 되었을 때 생성된 key를 담고 있기 위한 것이다.
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
ps.setString(1, name);
return ps;
}, keyHolder);
// keyHolder.getKey() now contains the generated key
예시 하나 더!
/**
* public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
*/
public Long insertWithKeyHolder(Customer customer) {
final String sql = "insert into customers (first_name, last_name) values (?, ?)";
final KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
final PreparedStatement preparedStatement = connection.prepareStatement(sql,
new String[]{"id"}); // 여기 내가 가져오려 하는 AUTO_INCREMENT 되는 Key를 넣어준다!
preparedStatement.setString(1, customer.getFirstName());
preparedStatement.setString(2, customer.getLastName());
return preparedStatement;
}, keyHolder);
return Objects.requireNonNull(keyHolder.getKey()).longValue();
}
휴 이제 JdbcTemplate의 사용에 대한 부분은 거의 다 끝난 것 같다.
나중에 더 사용이 나오면 추가할 예정..
이제 다음 포스팅에서는 NamedParameterJdbcTemplate과 SimpleJdbcInsert을 다루는 방법에 대해 알아보려고 한다.
끗
💋 참고자료