스프링 공식문서를 참조해서 JdbcTemplate 사용법을 정리해보았다.
JdbcTemplate 생성
스프링 공식문서에 따르면 JdbcTemplate을 생성하는 방법은 두가지가 있다.
1. DAO 구현체가 DataSource를 통해 JdbcTemplate를 직접 인스턴스화
2. 스프링 IoC 컨테이너에서 DAO에게 빈 참조로 제공
하지만 프로젝트의 의존성에 spring-boot-starter-jdbc 모듈을 추가한다면 스프링 부트가 DataSource와 JdbcTemplate 객체를 자동으로 IoC 컨테이너에 설정해준다.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' }
그러니까 우리는 IoC 컨테이너에 들어있는 DataSource 객체와 JdbcTemplate 객체를 가져다 쓰면 된다.
복잡한 것 필요 없고 다음 두가지 형태 중 하나를 골라 JdbcTemplate을 사용할 수 있다.
첫번째 방법
@Repository public class CarDao { private final JdbcTemplate jdbcTemplate; public CarDao(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } }
두번째 방법
@Repository public class CarDao { private final JdbcTemplate jdbcTemplate; public CarDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
겉으로 봤을 때 두 방법의 차이는 DAO 생성자의 파라미터로 DataSource가 들어가냐, JdbcTemplate가 들어가냐이다.
첫번째 방법은 DAO 구현체 내에서 DataSource 빈을 생성자 주입 받아서 JdbcTemplate 객체를 직접 만든다. 이 방법을 사용하면 JdbcTemplate 객체의 생성과 관리에 대한 처리를 구현체 내에서 직접 해주어야 한다.
두번째 방법은 DAO 구현체가 JdbcTemplate 빈을 생성자 주입 받는다. 이 방법을 쓰면 스프링에서 JdbcTemplate 객체의 생성과 관리를 자동으로 처리해준다.
두 방법의 차이를 꼽는다면, JdbcTemplate를 직접 만들어 쓸 것이냐, 스프링 부트가 잘 만들어놓은 빈을 가져다 쓸 것이냐다.
Querying (SELECT)
조회할 때는 주로 queryForObject와 query 메서드를 사용한다.
queryForObject : 쿼리문 수행 결과가 한개일 때 사용
query : 쿼리문 수행 결과가 한개 이상일 때 사용
1. 조회된 행의 숫자를 가져온다.
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
2. 특정 조건을 만족하는 행의 숫자를 가져온다.
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
3. 조회된 값을 문자로 가져온다.
String lastName = this.jdbcTemplate.queryForObject( "select last_name from t_actor where id = ?", String.class, 1212L);
4. 조회된 내용을 하나의 도메인 객체로 만들어 가져온다.
Actor actor = jdbcTemplate.queryForObject( "select first_name, last_name from t_actor where id = ?", (resultSet, rowNum) -> { Actor newActor = new Actor(); newActor.setFirstName(resultSet.getString("first_name")); newActor.setLastName(resultSet.getString("last_name")); return newActor; }, 1212L);
5. 조회된 내용들을 도메인 객체 리스트로 만들어 가져온다.
List<Actor> actors = this.jdbcTemplate.query( "select first_name, last_name from t_actor", (resultSet, rowNum) -> { Actor actor = new Actor(); actor.setFirstName(resultSet.getString("first_name")); actor.setLastName(resultSet.getString("last_name")); return actor; });
위의 4번과 5번에서, 도메인 객체로 만들 때 코드의 중복이 발생한다. 이를 제거하기 위해서 RowMapper를 사용할 수 있다.
RowMapper : 데이터베이스의 반환 결과인 ResultSet을 객체로 변환해 주는 클래스
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> { Actor actor = new Actor(); actor.setFirstName(resultSet.getString("first_name")); actor.setLastName(resultSet.getString("last_name")); return actor; }; public List<Actor> findAllActors() { return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper); }
Updating (INSERT, UPDATE, DELETE)
업데이트를 해주는 코드들은 간단하니 한번 쭉 보면 된다.
1. INSERT
this.jdbcTemplate.update( "insert into t_actor (first_name, last_name) values (?, ?)", "Leonor", "Watling");
2. UPDATE
this.jdbcTemplate.update( "update t_actor set last_name = ? where id = ?", "Banjo", 5276L);
3. DELETE
this.jdbcTemplate.update( "delete from t_actor where id = ?", Long.valueOf(actorId));
NamedParameter
이름이 붙여진 파라미터를 사용함으로써, 쿼리문의 ? 순서와 파라미터의 순서가 일치하지 않아도 된다.
1. Map 자료구조를 이용해 파라미터에 이름을 붙여주는 방법
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActorsByFirstName(String firstName) { String sql = "select count(*) from T_ACTOR where first_name = :first_name"; Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
2 MapSqlParameterSource를 이용해 파라미터에 이름을 붙여주는 방법
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActorsByFirstName(String firstName) { String sql = "select count(*) from T_ACTOR where first_name = :first_name"; SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
3. BeanPropertySqlParameterSource를 이용해 파라미터에 이름을 붙여주는 방법(자동)
자바빈 컨벤션을 따르는 자바빈을 이용해서 자동으로 파라미터 값들을 설정해준다.
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActors(Actor exampleActor) { String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"; SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
데이터베이스에 Insert할 때 자동생성되는 키 가져오기
원래는 JdbcTemplate을 이용해 insert하고 난 후 key 값을 가져오려면 다음과 같은 거추장스러운 코드를 작성해야했다.
@Repository public class InsertDao { private final JdbcTemplate jdbcTemplate; public InsertDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public Customer insert(Customer customer) { String sql = "insert into customers (first_name, last_name) values (?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, customer.getFirstName()); ps.setString(2, customer.getLastName()); return ps; }, keyHolder); long id = Objects.requireNonNull(keyHolder.getKey()).longValue(); return new Customer(id, customer.getFirstName(), customer.getLastName()); } }
이럴땐 SimpleJdbcInsert를 쓰면 코드를 간단히 할 수 있다.
@Repository public class SimpleInsertDao { private final SimpleJdbcInsert insertCustomer; public SimpleInsertDao(DataSource dataSource) { this.insertCustomer = new SimpleJdbcInsert(dataSource) .withTableName("customers") .usingGeneratedKeyColumns("id"); } public Customer insert(Customer customer) { SqlParameterSource parameters = new BeanPropertySqlParameterSource(customer); Number id = insertCustomer.executeAndReturnKey(parameters); return new Customer(id.longValue(), customer.getFirstName(), customer.getLastName()); } }
잘못된 내용을 댓글로 알려주세요
'프로그래밍 > JAVA Spring' 카테고리의 다른 글
[Spring 스프링] Spring MVC - ControllerAdvice(@ControllerAdvice와 @ExceptionHandler를 이용한 예외처리) (0) | 2023.04.20 |
---|---|
[Spring 스프링] Spring MVC - Annotated Controllers(@Controller와 @RestController) (0) | 2023.04.20 |
[Spring 스프링] JDBC, JDBC 드라이버, JDBCTemplate (0) | 2023.04.14 |
[Spring 스프링] @Component, @Repository, @Service, @Controller 는 뭐가 다를까 (2) | 2023.04.12 |
[Spring 스프링] consumes와 produces의 차이 (1) | 2023.04.11 |