[스프링 DB 2편] #2. 데이터 접근 기술 - 스프링 JdbcTemplate #538
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
JdbcTemplate 소개와 설정
장점
spring-jdbc라이브러리에 포함되어 있는데 스프링으로 JDBC를 사용할 때 기본으로 사용되는 라이브러리이다.statement를 준비하고 실행statement,resultset종료단점
JdbcTemplate 설정
build.gradle
org.springframework.boot:spring-boot-starter-jdbc를 추가하면 JdbcTemplate이 들어있는spring-jdbc가 라이브러리에 포함된다.추가되는 부분
주의
진행하기 전에 먼저 H2 데이터베이스에
item테이블을 생성해야 한다.JdbcTemplate 적용1 - 기본
JdbcTemplateItemRepositoryV1
기본
JdbcTemplateItemRepositoryV1은ItemRepository인터페이스를 구현했다.this.template = new JdbcTemplate(dataSource)JdbcTemplate은 데이터소스(dataSource)가 필요하다.JdbcTemplateItemRepositoryV1()생성자를 보면dataSource를 의존 관계 주입 받고 생성자 내부에서JdbcTemplate을 생성한다.스프링에서는
JdbcTemplate을 사용할 때 관례상 이 방법을 많이 사용한다.JdbcTemplate을 스프링 빈으로 직접 등록하고 주입받아도 된다.save()
데이터를 저장한다.
template.update(): 데이터를 변경할 때는update()를 사용하면 된다.INSERT,UPDATE,DELETESQL에 사용한다.template.update()의 반환 값은int인데, 영향 받은 로우 수를 반환한다.identity(auto increment) 방식을 사용하기 때문에PK인 ID 값을 개발자가 직접 지정하는 것이 아니라 비워두고 저장해야 한다. 그러면 데이터베이스가 PK인 ID를 대신 생성해준다.
데이터베이스에 INSERT가 완료 되어야 생성된 PK ID 값을 확인할 수 있다.
KeyHolder와connection.prepareStatement(sql, new String[]{"id"})를 사용해서id를 지정해주면INSERT쿼리 실행 이후에 데이터베이스에서 생성된 ID 값을 조회할 수 있다.update()
데이터를 업데이트 한다.
template.update(): 데이터를 변경할 때는update()를 사용하면 된다.?에 바인딩할 파라미터를 순서대로 전달하면 된다.where id=?를 지정했기 때문에 영향 받은 로우수는 최대 1개이다.findById()
데이터를 하나 조회한다.
template.queryForObject()RowMapper는 데이터베이스의 반환 결과인ResultSet을 객체로 변환한다.EmptyResultDataAccessException예외가 발생한다.IncorrectResultSizeDataAccessException예외가 발생한다.ItemRepository.findById()인터페이스는 결과가 없을 때Optional을 반환해야 한다.따라서 결과가 없으면 예외를 잡아서
Optional.empty를 대신 반환하면 된다.queryForObject() 인터페이스 정의
findAll()
데이터를 리스트로 조회한다. 그리고 검색 조건으로 적절한 데이터를 찾는다.
template.query()RowMapper는 데이터베이스의 반환 결과인ResultSet을 객체로 변환한다.query() 인터페이스 정의
itemRowMapper()
데이터베이스의 조회 결과를 객체로 변환할 때 사용한다.
JDBC를 직접 사용할 때
ResultSet를 사용했던 부분을 떠올리면 된다.차이가 있다면 다음과 같이 JdbcTemplate이 다음과 같은 루프를 돌려주고
개발자는
RowMapper를 구현해서 그 내부 코드만 채운다고 이해하면 된다.JdbcTemplate 적용2 - 동적 쿼리 문제
결과를 검색하는
findAll()에서 어려운 부분은 사용자가 검색하는 값에 따라서 실행하는 SQL이 동적으로 달려져야 한다는 점이다.검색 조건이 없음
상품명(
itemName)으로 검색최대 가격(
maxPrice)으로 검색상품명(
itemName), 최대 가격(maxPrice) 둘다 검색결과적으로 4가지 상황에 따른 SQL을 동적으로 생성해야 한다. 동적 쿼리가 쉬워 보이지만 막상 개발해보면 생각보다 다양한 상황을 고민해야 한다.
예를 들어서 어떤 경우에는
where를 앞에 넣고 어떤 경우에는and를 넣어야 하는지 등을 모두 계산해야 한다.그리고 각 상황에 맞추어 파라미터도 생성해야 한다.
JdbcTemplate 적용3 - 구성과 실행
JdbcTemplateV1Config
ItemRepository구현체로JdbcTemplateItemRepositoryV1이 사용되도록 했다.이제 메모리 저장소가 아니라 실제 DB에 연결하는 JdbcTemplate이 사용된다.
ItemServiceApplication - 변경
데이터베이스 접근 설정
src/main/resources/application.properties이렇게 설정만 하면 스프링 부트가 해당 설정을 사용해서 커넥션 풀과
DataSource, 트랜잭션 매니저를 스프링 빈으로 자동 등록한다.JdbcTemplate - 이름 지정 파라미터 1
순서대로 바인딩
JdbcTemplate을 사용하면 기본으로 파라미터를 순서대로 바인딩한다.
만약 실수로 SQL 순서를 변경했다고 가정해보자.
밑의 순서와 같이 데이터가 바인딩된다.
item_name=itemName, quantity=price, price=quantity개발을 할 때는 코드를 몇줄 줄이는 편리함도 중요하지만, 모호함을 제거해서 코드를 명확하게 만드는 것이 유지보수 관점에서 매우 중요하다.
이름 지정 바인딩
JdbcTemplate은 이런 문제를 보완하기 위해
NamedParameterJdbcTemplate라는 이름을 지정해서 파라미터를 바인딩 하는 기능을 제공한다.JdbcTemplateItemRepositoryV2
기본
JdbcTemplateItemRepositoryV2는ItemRepository인터페이스를 구현했다.this.template = new NamedParameterJdbcTemplate(dataSource)NamedParameterJdbcTemplate도 내부에dataSource가 필요하다.JdbcTemplateItemRepositoryV2생성자를 보면 의존관계 주입은dataSource를 받고 내부에서NamedParameterJdbcTemplate을 생성해서 가지고 있다. 스프링에서는JdbcTemplate관련 기능을 사용할 때 이 방법을 많이 사용한다.NamedParameterJdbcTemplate을 스프링 빈으로 직접 등록하고 주입받아도 된다.save()
SQL에서 다음과 같이
?대신에:파라미터이름을 받는 것을 확인할 수 있다.추가로
NamedParameterJdbcTemplate은 데이터베이스가 생성해주는 키를 매우 쉽게 조회하는 기능도 제공해준다.JdbcTemplate - 이름 지정 파라미터 2
이름 지정 파라미터
파라미터를 전달하려면
Map처럼key,value데이터 구조를 만들어서 전달해야 한다.여기서
key는:파리이터이름으로 지정한, 파라미터의 이름이고 ,value는 해당 파라미터의 값이 된다.이름 지정 바인딩에서 자주 사용하는 파라미터의 종류는 크게 3가지가 있다.
MapSqlParameterSourceMapSqlParameterSourceBeanPropertySqlParameterSource1. Map
2. MapSqlParameterSource
Map과 유사한데, SQL 타입을 지정할 수 있는 등 SQL에 좀 더 특화된 기능을 제공한다.SqlParameterSource인터페이스의 구현체이다.MapSqlParameterSource는 메서드 체인을 통해 편리한 사용법도 제공한다.update()코드에서 확인할 수 있다.BeanPropertySqlParmeterSource
자바빈 프로퍼티 규약을 통해서 자동으로 파라미터 객체를 생성한다.
예) (
getXxx() -> xxx, getItemName() -> itemName)예를 들어서
getItemName(),getPrice()가 있으면 다음과 같은 데이터를 자동으로 만들어낸다.key=itemName, value=상품명 값,key=price, value=가격 값SqlParameterSource인터페이스의 구현체이다.save(),findAll()코드에서 확인할 수 있다.BeanPropertySqlParameterSource가 많은 것을 자동화 해주기 때문에 가장 좋아보이지만BeanPropertySqlParameterSource를 항상 사용할 수 있는 것은 아니다.update()에서는 SQL에:id를 바인딩 해야 하는데,update()에서 사용하는ItemUpdateDto에는itemId가 없다.따라서
BeanPropertySqlParameterSource를 사용할 수 없고 대신에MapSqlParameterSource를 사용했다.BeanPropertyRowMapper
JdbcTemplateItemRepositoryV1 - itemRowMapper()
JdbcTemplateItemRepositoryV2 - itemRowMapper()
BeanPropertyRowMapper는ResultSet의 결과를 받아서 자바빈 규약에 맞추어 데이터를 변환한다.예를 들어서 데이터베이스에서 조회한 결과가
select id, price라고 하면 다음과 같은 코드를 작성해준다. (실제로는 리플렉션 같은 기능을 사용한다.)데이터베이스에서 조회한 결과 이름을 기반으로
setId(),setPrice()처럼 자바빈 프로퍼티 규약에 맞춘 메서드를 호출하는 것이다.별칭
select item_name의 경우setItem_name()이라는 메서드가 없기 때문에 골치가 아프다.이런 경우 개발자가 조회 SQL을 다음과 같이 고치면 된다.
select item_name as itemName별칭
as를 사용해서 SQL 조회 결과의 이름을 변경하는 것이다. 실제로 이 방법은 자주 사용된다. 특히 데이터베이스컬럼 이름과 객체 이름이 완전히 다를 때 문제를 해결할 수 있다. 예를 들어서 데이터베이스에는
member_name이라고 되어 있는데객체에
username이라고 되어 있다면 다음과 같이 해결할 수 있다.select member_name as username이렇게 데이터베이스 컬럼 이름과 객체의 이름이 다를 때 별칭(
as)을 사용해서 문제를 많이 해결한다.JdbcTemplate은 물론이고,MyBatis같은 기술에서도 자주 사용된다.관례의 불일치
자바 객체는 카멜(
camelCase) 표기법을 사용한다.itemName처럼 중간에 낙타 봉이 올라와 있는 표기법이다.반면에 관계형 데이터베이스에서는 주로 언더스코어를 사용하는
snake_case표기법을 사용한다.item_name처럼 중간에 언더스코어를 사용하는 표기법이다.이 부분을 관례로 많이 사용하다 보니
BeanPropertyRowMapper는 언더스코어 표기법을 카멜로 자동 변환해준다.따라서
select item_name으로 조회해도setItemName()에 문제 없이 값이 들어간다.JdbcTemplate - 이름 지정 파라미터 3
JdbcTemplateV2Config
ItemServiceApplication - 변경
JdbcTemplate - SimpleJdbcInsert
JdbcTemplate은 INSERT SQL를 직접 작성하지 않아도 되도록
SimpleJdbcInsert라는 편리한 기능을 제공한다.JdbcTemplateItemRepositoryV3
기본
은ItemRepository` 인터페이스를 구현했다.this.jdbcInsert = new SimpleJdbcInsert(dataSource): 생성자를 보면 의존관계 주입은dataSource를 받고 내부에서SimpleJdbcInsert을 생성해서 가지고 있다.스프링에서는
JdbcTemplate관련 기능을 사용할 때 관례상 이 방법을 많이 사용한다.SimpleJdbcInsert을 스프링 빈으로 직접 등록하고 주입받아도 된다.SimpleJdbcInsert
withTableName: 데이터를 저장할 테이블 명을 지정한다.usingGeneratedKeyColumns:key를 생성하는 PK 컬럼 명을 지정한다.usingColumns: INSERT SQL에 사용할 컬럼을 지정한다. 특정 값만 저장하고 싶을 때 사용한다. 생략할 수 있다.SimpleJdbcInsert는 생성 시점에 데이터베이스 테이블의 메타 데이터를 조회한다. 따라서 어떤 컬럼이 있는지 확인 할 수 있으므로usingColumns을 생략할 수 있다. 만약 특정 컬럼만 지정해서 저장하고 싶다면usingColumns를 사용하면 된다.애플리케이션을 실행해보면
SimpleJdbcInsert이 어떤 INSERT SQL을 만들어서 사용하는지 로그로 확인할 수 있다.save()
jdbcInsert.executeAndReturnKey(param)을 사용해서 INSERT SQL을 실행하고, 생성된 키 값도 매우 편리하게 조회할 수 있다.public Item save(Item item) { SqlParameterSource param = new BeanPropertySqlParameterSource(item); Number key = jdbcInsert.executeAndReturnKey(param); item.setId(key.longValue()); return item; }JdbcTemplateV3Config
ItemServiceApplication - 변경
JdbcTemplate 기능 정리
주요 기능
JdbcTemplate이 제공하는 주요 기능은 다음과 같다.
JdbcTemplateNamedParameterJdbcTemplateSimpleJdbcInsertSimpleJdbcCall조회
단건 조회 - 숫자 조회
하나의 로우를 조회할 때는
queryForObject()를 사용하면 된다.지금처럼 조회 대상이 객체가 아니라 단순 데이터 하나라면 타입을
Integer.class,String.class와 같이 지정해주면 된다.단건 조회 - 숫자 조회, 파라미터 바인딩
단건 조회 - 문자 조회
단건 조회 - 객체 조회
객체 하나를 조회한다. 결과를 객체로 매핑해야 하므로
RowMapper를 사용해야 한다. 여기서는 람다를 사용했다.목록 조회 - 객체
여러 로우를 조회할 때는
query()를 사용하면 된다. 결과를 리스트로 반환한다.결과를 객체로 매핑해야 하므로
RowMapper를 사용해야 한다. 여기서는 람다를 사용했다.목록 조회 - 객체
여러 로우를 조회할 때는
query()를 사용하면 된다. 결과를 리스트로 반환한다.여기서는
RowMapper를 분리했다. 이렇게 하면 여러 곳에서 재사용 할 수 있다.변경(INSERT, UPDATE, DELETE)
데이터를 변경할 때는
jdbcTemplate.update()를 사용하면 된다. 참고로int반환값을 반환하는데, SQL 실행 결과에 영향받은 로우 수를 반환한다.등록
수정
삭제
기타 기능
임의의 SQL을 실행할 때는
execute()를 사용하면 된다. 테이블을 생성하는 DDL에 사용할 수 있다.DDL
스토어드 프로시저 호출
jdbcTemplate.update("call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", Long.valueOf(unionId));
정리
실무에서 가장 간단하고 실용적인 방법으로 SQL을 사용하려면 JdbcTemplate을 사용하면 된다.
JPA와 같은 ORM 기술을 사용하면서 동시에 SQL을 직접 작성해야 할 때가 있는데, 그때도 JdbcTemplate을 함께 사용하면 된다.
그런데 JdbcTemplate의 최대 단점이 있는데, 바로 동적 쿼리 문제를 해결하지 못한다는 점이다.
그리고 SQL을 자바 코드로 작성하기 때문에 SQL 라인이 코드를 넘어갈 때 마다 문자 더하기를 해주어야 하는 단점도 있다.
동적 쿼리 문제를 해결하면서 동시에 SQL도 편리하게 작성할 수 있게 도와주는 기술이 바로 MyBatis 이다.
All reactions