티스토리 뷰
데이터에 접근 할 수 있는 기술은 여러가지가 있다.
데이터 접근 기술
- JdbcTemplate
- MyBatis
- JPA, Hibernate
- 스프링 데이터 JPA
- Querydsl
이번에는 JdbcTemplate에 대해 알아볼려고 한다.
JdbcTemplate는 SQLMapper 기술이다.
SQLMapper
- jdbcTemplate
- Mybatis
SQL Mapper 주요 기능
개발자는 SQL만 작성하면 해당 SQL의 결과를 객체로 편리하게 매핑해준다.
JDBC를 직접 사용할 때 발생하는 여러가지 중복을 제거해주고, 기타 개발자에게 여러가지 편리한 기능을 제공한다.
JdbcTemplate
JdbcTemplate은 JDBC를 매우 편리하게 사용할 수 잇게 도와준다.
장점
- JdbcTemplate은 스프링으로 JDBC를 사용할 때 자동으로 포함되는 spring-jdbc 라이브러리에 속해있기 때문에 별도의 복잡한 설정 없이 사용할 수 있다.
- 템플릿 콜백 패턴을 사용해서 JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업을 처리해준다.
- 개발자는 SQL을 작성하고 전달할 파라미터를 정의하여 응답 값을 매핑하기만 하면 된다.
- 트랜잭션을 위한 커넥션 동기화, 스프링 예외 변환기를 자동으로 실행한다.
단점
- 동적 SQL을 작성하기가 어렵다는 단점이 있다.
JdbcTemplate 사용 설정
build.gradle에 라이브러리를 추가해준다. 현재 h2 DB를 사용했으므로 h2 DB도 추가했다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
application.properties에 dataSource를 가져오기 위한 URL, USERNAME, PASSWORD를 미리 설정해준다.
비밀번호는 입력하지 않았기에 = 뒤에 아무것도 넣지 않았다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
JdbcTemplate 사용법
@Slf4j
@Repository
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {
private final JdbcTemplate template;
public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
this.template = new JdbcTemplate(dataSource);
}
...
}
- JdbcTemplate는 데이터소스(DataSource)가 필요하다.
- JdbcTemplateItemRepository 생성자를 보면 dataSource를 의존 관계 주입을 받고 생성자 내부에서 JdbcTemplate을 생성한다. 스프링에서 JdbcTemplate을 사용할 때 관례상 이 방법을 많이 사용한다.
- JdbcTemplate을 스프링 빈으로 직접 등록하고 주입 받아도 된다.
Template.update()
- 데이터를 변경할 때 update를 사용한다.
- INSERT, UPDATE, DELETE SQL에 사용한다.
template.queryForObject()
- 데이터를 하나 조회할 때 사용한다. 즉, 검색 결과 tuple이 하나일 때 사용한다.
- 결과가 없으면 EmptyResultDataAccessException 예외가 발생한다.
- 결과가 둘 이상이면 IncorrectResultSizeDataAccessException 예외가 발생한다.
template.query()
- 데이터를 하나 이상 조회할 때 사용한다. 데이터를 조회해서 list로 반환한다.
- 결과가 없을 시 빈 컬렉션을 반환한다.
RowMapper()
- 데이터 조회 결과를 객체로 변환할 때 사용한다.
- RowMapper가 필요한 메서드에 직접 람다식을 작성해도 되지만, 메서드로 빼서 사용하는 게 좋겠다.
파라미터 '?' 바인딩 예제
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql = "update item set item_name=?, price=?, quantity=? where id=?";
template.update(sql,
updateParam.getItemName(),
updateParam.getPrice(),
updateParam.getQuantity(),
itemId);
}
DB에 보낼 SQL을 String으로 작성하고, 입력 파라미터는 ?으로 대체한다.
template.update에서 ?에 넣을 값들을 순서대로 넣어준다.
순서에 맞지 않게 입력하는 순간 오류가 생길 것이다.
짧은 sql에서는 그럴 일이 없을 수 있지만 실무에서 많은 파라미터를 사용할 때 이 부분에 대해서 오류가 많이 발생한다고 한다.
JdbcTemplate은 이런 문제를 해결하기 위해 NameParameterTemplate라는 이름을 지정해서 파라미터를 바인딩하는 기능을 제공한다.
NamedParameterJdbcTemplate
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql = "update item " +
"set item_name=:itemName, price=:price, quantity=:quantity " +
"where id=:id";
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName", updateParam.getItemName())
.addValue("price", updateParam.getPrice())
.addValue("quantity", updateParam.getQuantity())
.addValue("id", itemId); //이 부분이 별도로 필요하다.
template.update(sql, param);
}
위에서 사용한 '?' 대신 :itemName, :price과 같이 바인딩 해야되는 파라미터의 이름을 지정할 수 있다.
이 방법을 사용하면 데이터를 순서대로 넣어서 발생하는 오류를 막을 수 있다.
또한 어느 자리에 어떤 값이 들어가는지 명확하게 알 수 있다.
개발을 할 때는 코드를 몇줄 줄이는 편리함도 좋지만, 모호함을 제거해서 코드를 명확하게 만드는 것이 유지보수 측면에서 매우 중요하다.
이제 순서대로 값을 입력하지 않아도 제대로 값이 들어가니 다양한 방법으로 데이터를 바인딩할 수 있다.
Map, MapSqlParameterSource, BeanPropertyRowMapper 등으로 객체를 바인딩할 수 있다.
1. Map 방식
@Override
public Optional<Item> findById(Long id) {
String sql = "select id, item_name, price, quantity from item where id = :id";
try {
Map<String, Object> param = Map.of("id", id);
Item item = template.queryForObject(sql, param, itemRowMapper());
return Optional.of(item);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}
- Map.of를 사용해서 Map 자료구조 생성과 동시에 값을 넣어준다.
- 데이터 조회 결과를 객체로 만들어 주기 위해서 itemRowMapper을 사용한다.
2. MapSqlParameterSource
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql = "update item " +
"set item_name=:itemName, price=:price, quantity=:quantity " +
"where id=:id";
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName", updateParam.getItemName())
.addValue("price", updateParam.getPrice())
.addValue("quantity", updateParam.getQuantity())
.addValue("id", itemId); //이 부분이 별도로 필요하다.
template.update(sql, param);
}
BeanProperyRowMapper
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);
- 자바빈 프로퍼티 규약을 통해서 자동으로 파라미터 객체를 생성한다.
- Item 클래스에 getXxx() 메서드를 찾아서 매칭시킨다. [ 예) getPrice -> price ]
BeanPropertySqlParameterSource가 많은 것을 자동화 해주기 때문에 가장 좋아보이지만, 항상 사용할 수 없다.
밑의 코드는 UpdateDto이다.
@Data
public class ItemUpdateDto {
private String itemName;
private Integer price;
private Integer quantity;
public ItemUpdateDto() {
}
public ItemUpdateDto(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
만약 updateDto를 사용해서 :id를 바인딩 해야되는 상황이 나온다면 dto에 id가 없기에 사용할 수 없다.
이러한 상황에서는 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"));
관례의 불일치
자바 객체는 카멜 표기법을 사용한다. itemName 처럼 중간에 낙타봉이 올라와 있는 표기법이다.
관계형 데이터베이스는 언더스코어를 사용하는 snake_case 표기법을 사용한다. item_name 처럼 중간에 표기한다.
관례로 많이 사용하다보니 BeanPropertyRowMapper는 언더스코어 표기법을 카멜로 자동 변화시켜준다.
select item_name으로 조회해도 setItemName()에 문제 없이 값이 들어간다.
그렇다면 관례에도 불일치 하는 경우는 어떨까?
별칭을 사용하면 된다. item_name이라고 데이터를 받아도 itemname으로 받을 수 있다.
select item_name as itemName
동적 쿼리
JdbcTemplate
String sql = "select id, item_name, price, quantity from item";
//동적 쿼리
if (StringUtils.hasText(itemName) || maxPrice != null) {
sql += " where";
}
boolean andFlag = false;
if (StringUtils.hasText(itemName)) {
sql += " item_name like concat('%',:itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
sql
+= " and";
}
sql += " price <= :maxPrice";
}
log.info("sql={}", sql);
return template.query(sql, param, itemRowMapper());
Mybatis
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
JdbcTemplate은 자바 코드로 직접 동적 쿼리를 작성해야 한다.
반면에 MyBatis는 동적 쿼리를 매우 편리하게 작성할 수 있는 다양한 기능들을 제공해준다.
JdbcTemplate은 정적 쿼리 작성에서 편한 포인트가 있으나 동적 쿼리 작성에서
Mybatis에 비해서 코드가 복잡해지며 작성하기 어려워진다.
다음으로 동적 쿼리에 유리한 Mybatis에 대해 알아보자.
2024.07.31 - [코드공부/DB] - 데이터 접근 기술 - Mybatis
데이터 접근 기술 - Mybatis
Mybatis MyBatis는 앞서 설명한 JdbcTemplate보다 더 많은 기능을 제공하는 SQL Mapper 이다.기본적으로 JdbcTemplate이 제공하는 대부분의 기능을 제공한다.JdbcTemplate과 비교해서 MyBatis의 가장 매력적인 점은
paine.tistory.com
'코드공부 > DB' 카테고리의 다른 글
데이터 접근 기술 JPA, Querydsl (0) | 2024.08.01 |
---|---|
데이터 접근 기술 - Mybatis (0) | 2024.07.31 |
- Total
- Today
- Yesterday
- 국비교육
- 김영한
- post-redirct-get
- JPA
- 정보처리기사
- 인텔리제이
- 그린대학교
- wsl
- 스택
- java
- docker
- 프로그래머스
- CSS
- 국비
- Git
- MySQL
- 자료구조
- 공공데이터포탈
- deque
- 국비지원
- (롯데)기업맞춤형 프로젝트
- 해시
- 덱
- form
- 오류
- 백준
- 메시지 오류
- static
- Queue
- JWT
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |