장점
- 설정의 편리함
- JdbcTemplate은 spring-jdbc 라이브러리에 포함되어 있어서 별도의 설정 없이 사용할 수 있다.
- 반복 문제 해결
- 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업을 대신 처리해준다.
- 개발자는 SQL을 작성하고, 전달할 파라미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
- 반복 작업 목록
- 커넥션 흭득
- Statement를 준비하고 실행
- 결과를 반복하도록 루프를 실행
- 커넥션, Statment, resultset 종료
- 트랜잭션을 다루기 위한 커넥션 동기화
- 예외 발생시 스프링 예외 변환기 실행
- 반복 작업 목록
단점으로는 동적 SQL을 해결하기 어렵다.
NamedParameterJdbcTemplate
- new NamedParameterJdbcTemplate(dataSource) 로 생성
- JdbcTemplateItemRepositoryV2 생성자를 보면 의존관계 주입은 dataSource 를 받고 내부에서 NamedParameterJdbcTemplate 을 생성해서 가지고 있다. 스프링에서는 JdbcTemplate 관련 기능 을 사용할 때 관례상 이 방법을 많이 사용한다.
- 빈에 직접 등록해서 사용해도 된다.
//JdbcTemplate
String sql = " INSERT INTO item(item_name, price, quantity)" +
" VALUES (?, ?, ?) ";
//NamedJdbcTemplate
String sql = " INSERT INTO item(item_name, price, quantity)" +
" VALUES (:item_name, :price, :quantity) ";
코드를 보면 ' ? ' 대신에 해당 컬럼의 명을 ' : '를 사용해서 지정한다. (가독성 증가)
' : ' 파라미터에 데이터를 전달하려면 Map처럼 <Key, Value> 구조로 지정해줘야한다.
이때 Key는 지정한 이름과 동일하게 하면 qurey를 날릴때는 value의 값으로 치환된다.
template.update(sql, param(Map), keyHolder)
이름지정 바인딩에서 자주 사용하는 파라미터의 종류
- Map
- SqlParameterSource - 인터페이스
- MapSqlParameterSource - 구현 클래스
- BeanPropertySqlParameterSource - 구현 클래스
Map
가장 단순한 방법
Map<String, Object> param = Map.of("id", id);
Item item = template.queryForObject(sql, param, itemRowMapper());
MapSqlParameterSource
Map과 유사하지만 SQL에 특화된 기능을 추가로 제공한다. (메서드 체인)
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName", updateParam.getItemName())
.addValue("price", updateParam.getPrice())
.addValue("quantity", updateParam.getQuantity())
.addValue("id", itemId);
메서드체인
.addValue를 연속적으로 사용할 수 있게 해주는 기능
BeanPropertySqlParameterSource
자바빈 프로퍼티 규약을 통해서 자동으로 파라미터 객체를 생성한다
예를 들어서 getItemName() , getPrice() 가 있으면 다음과 같은 데이터를 자동으로 만들어낸다.
key=itemName, value=상품명 값
key=price, value=가격 값
String sql = " INSERT INTO item(item_name, price, quantity)" +
" VALUES (:item_name, :price, :quantity) ";
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
여기서 보면 BeanPropertySqlParameterSource 가 많은 것을 자동화 해주기 때문에 가장 좋아보이지만, BeanPropertySqlParameterSource 를 항상 사용할 수 있는 것은 아니다.
예를 들어서 update() 에서는 SQL에 :id 를 바인딩 해야 하는데, update() 에서 사용하는 ItemUpdateDto 에는 itemId 가 없다. 따라서 BeanPropertySqlParameterSource 를 사용할 수 없 고, 대신에 MapSqlParameterSource 를 사용했다.
BeanPropertyRowMapper
//기존
private RowMapper<Item> itemRowMapper() {
return (rs, rowNum) -> {
Item item = new Item();
item.setId(rs.getLong("id"));
item.setItemName(rs.getString("item_name"));
item.setPrice(rs.getInt("price"));
item.setQuantity(rs.getInt("quantity"));
return item;
};
}
//변경
private RowMapper<Item> itemRowMapper() {
return BeanPropertyRowMapper.newInstance(Item.class); //camel 치환
}
BeanPropertyRowMapper 는 ResultSet 의 결과를 받아서 자바빈 규약에 맞추어 데이터를 변환한다.
예를 들어서 데이터베이스에서 조회한 결과가 select id, price 라고 하면 다음과 같은 코드를 작성해준다.
(실제로는 리플렉션 같은 기능을 사용한다.)
Item item = new Item();
item.setId(rs.getLong("id"));
item.setPrice(rs.getInt("price"));
데이터베이스에서 조회한 결과 이름을 기반으로 setId() , setPrice() 처럼 자바빈 프로퍼티 규약에 맞춘 메서드 를 호출하는 것이다.
클래스 객체와 컬럼의 이름이 다를때는?
rdb : item_name
java : itemName
AS 를 사용해서 자바에 맞게 지정해주면 된다.
ex)
rdb : item_name
java : userName
SELECT item_name as userName FROM xxx;
관례의 불일치
자바 클래스에서 사용하는 표기법 : 카멜 표기법(className)
데이타베이스에서 사용하는 표기법 : 스네이크 표기법 (class_name)
부분을 관례로 많이 사용하다 보니 BeanPropertyRowMapper 는 언더스코어 표기법을 카멜로 자동 변환해준다. 따라서 select item_name 으로 조회해도 setItemName() 에 문제 없이 값이 들어간다
정리하면 snake_case 는 자동으로 해결되니 그냥 두면 되고, 컬럼 이름과 객체 이름이 완전히 다른 경우에는 조회 SQL에서 별칭을 사용하면 된다.
오라클!
오라클용 item 테이블
DROP TABLE item;
DROP SEQUENCE item_seq;
CREATE SEQUENCE item_seq
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;
CREATE TABLE item (
id NUMBER DEFAULT item_seq.nextval PRIMARY KEY,
item_name VARCHAR2(100),
price NUMBER,
quantity NUMBER
);
오라클을 사용하고, NamedJdbcTemplate을 사용할때 자동 생성 key 값 가져올 경우
jdbcTemplate.update(sql, param, keyHolder); //를
jdbcTemplate.update(sql, param, keyHolder,new String[]{"id"}); //와 같이 추가해줘야함
또한 오라클은 concat 기능을 사용하지 못하기 때문에
'%' || ? || '%' " --JdbcTemplate
'%' || :itemName || '%' " --NamedParameterJdbcTemplate
SimpleJdbcInsert
JdbcTemplate은 INSERT SQL를 직접 작성하지 않아도 되도록 SimpleJdbcInsert 라는 편리한 기능을 제공한다.
생성
private final SimpleJdbcInsert jdbcInsert;
public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.jdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("item") //삽입 테이블명
.usingGeneratedKeyColumns("id"); //자동생성되는 키 지정
// .usingColumns("item_name", "price","quantity") 생략 가능
}
마찬가지로 관례상 이 방법을 사용(삽입할 테이블이 하나가 아님), 빈 등록 가능
withTableName | 데이터를 저장할 테이블 지정 |
usingGeneratedKeyCoulumns | key를 생성하는 PK 컬럼 명 |
usingColumns | INSERT SQL에 사용할 컬럼 / 생략가능 (특정 값만 저장하고 싶을 때 사용) |
simpleJdbcInsert는 생성 시점에 메타 데이터를 조회하면서 어떤 컬럼이 있는지 확인한다. 그래서 usingColumns를 생략 할 수 있다.
사용
@Override
public Item save(Item item) {
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
Number key = jdbcInsert.executeAndReturnKey(param); //저장 후 자동 생성 키를 가져옴
item.setId(key.longValue());
return item;
}
DDL 사용
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
정리
- JdbcTemplate
- 순서 기반 파라미터 바인딩 지원
- NamedParameterJdbcTemplate
- 이름 기반 파라미터 바인딩 지원
- SimpleJdbcInsert
- INSERT SQL을 쉽게 사용 가능
- SimpleJdbcCall
- 스토어드 프로시저를 편리하게 호출 가능
- 스프링 JdbcTemplate 사용 방법 공식 메뉴얼
'Spring > JDBC' 카테고리의 다른 글
[Spring] 데이터 접근 기술 테스트 (0) | 2024.08.14 |
---|---|
[Spring DB] Mybatis (0) | 2024.08.12 |
[Spring DB 개념] 스프링 예외 추상화 (0) | 2024.08.12 |
[Spring] 예외 누수 문제 해결 (0) | 2024.08.09 |
[Spring DB] 스프링 부트 리소스 등록 (0) | 2024.08.08 |