티스토리 뷰

 

데이터에 접근 할 수 있는 기술은 여러가지가 있다.

데이터 접근 기술

  • 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 &lt;= #{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
링크
«   2025/05   »
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
글 보관함