시냅스

Spring JdbcTemplate 정리 본문

Java, Spring

Spring JdbcTemplate 정리

ted k 2022. 9. 2. 22:02

JdbcTemplate

 

출처 : https://gmlwjd9405.github.io/2018/12/19/jdbctemplate-usage.html

 

  • JdbcTemplate은 JDBC를 편리하게 사용할 수 있는 SQL Mapper이다.
  • JdbcTemplate은 spring-jdbc 라이브러리에 포함되어 있어 복잡한 설정 없이 스프링으로 JDBC를 사용할 때 기본으로 사용되는 라이브러리이다.
  • 콜백 패턴(RowMapper)을 사용하여 반복잡업을 대신해준다.
  • 개발자는 SQL을 작성하고, 전달할 파라미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
  • 아래는 JdbcTemplate이 대신 처리해주는 작업들이다.
    • 커넥션 획득
    • statement를 준비하고 실행
    • 결과를 반복하도록 루프 실행
    • 커넥션 종료, statement, resultset 종료
    • 트랜잭션 다루기 위한 커넥션 동기화
    • 예외 발생시 스프링 예외 변환기 실행
  • 단점으로는 동적 SQL을 해결하기 어렵다는 점이 있다.

 

용례 - insert

@Repository
public class repository {
    private final JdbcTemplate template;
    
	public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
    	this.template = new JdbcTemplate(dataSource); // datasource를 주입받아 할당한다.
	}
    
    public Item save(Item item) {
        String sql = "insert into item(item_name, price, quantity) values (?, ?, ?)";
        // db로부터 키 값을 부여 받는다.
        KeyHolder keyHolder = new GeneratedKeyHolder();
        template.update(connection -> {
            //자동 증가 키 지정 -> new String[]{"id"}
            PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
            ps.setString(1, item.getItemName());
            ps.setInt(2, item.getPrice());
            ps.setInt(3, item.getQuantity());
            return ps;
        }, keyHolder);

        long key = keyHolder.getKey().longValue();
        item.setId(key);

        return item;
    }
}
  • 가장 기본적인 방법으로 개발자가 직업 binding 하는 작업이 이뤄지고 있다.

 

public class Repository {

    private final NamedParameterJdbcTemplate template;

    public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
        this.template = new NamedParameterJdbcTemplate(dataSource);
    }

    @Override
    public Item save(Item item) {
        String sql = "insert into item(item_name, price, quantity) values (:itemName, :price, :quantity)";

        SqlParameterSource param = new BeanPropertySqlParameterSource(item);

        KeyHolder keyHolder = new GeneratedKeyHolder();
        template.update(sql, param, keyHolder);

        long key = keyHolder.getKey().longValue();
        item.setId(key);
        return item;
    }
}
  • Sql에서 ? 로 바인딩하는 대신에 :파라미터이름 을 통해 바인딩이 이뤄지고 있다.
  • BeanPropertySqlParameterSource에서 바인딩이 이뤄진다.
  • 이 외에도 SimpleJdbcInsert 를 통해 insert문을 작성하지 않고도 insert를 할 수 있는 방법도 있다.

 

용례 - update

    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);

    }
  • 각 ? 에 변수들을 바인딩해준다.
    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);
    }
  • 위의 template은 NamedParameterJdbcTemplate이다.
  • MapSqlParameter를 통해 각 컬럼들을 binding해준다.

 

용례 - 단건 조회

    public Optional<Item> findById(Long id) {
        String sql = "select id, item_name, price, quantity from item where id = ?";
        try {
            Item item = template.queryForObject(sql, itemRowMapper(), id);
            return Optional.of(item);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }
    
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;
    });
}
  • queryForObejct 를 통해 데이터를 조회한다.
  • 이때 RowMapper를 사용하는데, RowMapper는 ResultSet을 객체로 변환한다.
  • 만약 결과가 없다면 EmptyResultDataAccessException이 발생한다.

 

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();
    }
}

private RowMapper<Item> itemRowMapper() {
    return BeanPropertyRowMapper.newInstance(Item.class); // camel 변환 지원
}
  • 위의 template은 NamedParameterJdbcTemplate이다.
  • BeanPropertyRowMapper를 통해 결과를 객체로 변환한다.

 

용례 - 다건 조회

public List<Item> findAll(ItemSearchCond cond) {
    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();

    String sql = "select id, item_name, price, quantity from item";
    if (StringUtils.hasText(itemName) || maxPrice != null) {
        sql += " where";
    }
    boolean andFlag = false;
    List<Object> param = new ArrayList<>();
    if (StringUtils.hasText(itemName)) {
        sql += " item_name like concat('%',?,'%')";
        param.add(itemName);
        andFlag = true;
    }
    if (maxPrice != null) {
        if (andFlag) {
            sql += " and";
        }
        sql += " price <= ?";
        param.add(maxPrice);
    }
    log.info("sql={}", sql);
    return template.query(sql, itemRowMapper(), param.toArray());
}
  • template.query() 는 결과가 하나 이상일 때 사용한다.
  • 마찬가지로 RowMapper를 통해 결과를 객체로 변환한다.
  • 쿼리에 대한 조건들과 sql을 동적으로 변환한다.

 

public List<Item> findAll(ItemSearchCond cond) {
    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();

    SqlParameterSource param = new BeanPropertySqlParameterSource(cond);

    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());
}
  • 위의 template은 NamedParameterJdbcTemplate이다.
  • 위와 마찬가지로 동적으로 할당하는데, 다만 바인딩 문법이 달라졌다.

 

정리

  • JdbcTemplate
    • 순서 기반 파라미터 바인딩
  • NamedParameterJdbcTemplate
    • 이름 기반 파라미터 바인딩을 지원
  • SimpleJdbcInsert
    • INSERT SQL을 편리하게 사용할 수 있다.
  • SimpleJdbcCall
    • 스토어드 프로시저를 편리하게 호출
Comments