Spring

NamedParameterJdbcTemplate

Tommy__Kim 2023. 7. 1. 22:07

JdbcTemplate의 문제점

JdbcTemplate을 사용함으로 인해 개발자들은 코드에서 높게 일어나는 중복 현상들을 제거해 주어 개발자에게 많은 도움을 주었습니다.
하지만 JdbcTemplate에서 종종 일어나는 문제들이 있습니다.
Jdbctemplate은 순서 기반으로 파라미터들을 할당해줍니다. 그렇기 때문에 순서가 쿼리와 맞지 않게 뒤바뀐다면 큰 문제가 발생합니다.

String sql = "update customer set customer_name = ?, age = ?, phone_number = ? where id = ?";
        jdbcTemplate.update(sql,
                updateParam.getCustomerName(),
                updateParam.getAge(),
                updateParam.getPhoneNumber(),
                customerId);

다음과 같이 Customer를 업데이트하는 로직이 있을 때, 다른 개발자가 실수로 다음과 같이 바꿨습니다.

String sql = "update customer set customer_name = ?, age = ?, phone_number = ? where id = ?";
        jdbcTemplate.update(sql,
                updateParam.getPhoneNumber(),
                updateParam.getAge(),
                updateParam.getCustomerName(),
                customerId);

이렇게 코드가 바뀌었을 때, customer_name, 그리고 phone_number 모두 String 타입이기 때문에 전혀 문제가 되지 않습니다.
결국 다음과 같은 현상이 발생합니다.

  • customer_name : 핸드폰 번호 저장
  • phone_number : 고객 이름 저장
    이와 같은 실수들을 방지하기 위해 이름을 지정해 파라미터를 바인딩 하는 NamedParameterJdbcTemplate가 생겼습니다.

NamedParameter 사용

Parameter 지정 방식

jdbcTemplate과 다르게 NamedParameterJdbcTemplate은 파라미터를 지정해야 합니다.
Parameter 지정 방식은 크게 세가지 방식이 있습니다.

  • SqlParameterSource
    • BeanPropertySqlParameterSource
    • MapSqlParameterSource
  • Map

BeanPropertySqlParameterSource

BeanPropertySqlParameterSource param = new BeanPropertySqlParameterSource(파라미터화 하고자 하는 인스턴스);

BeanPropertySqlParameterSource의 경우 자바빈 프로퍼티 규약을 통해 자동으로 파라미터 객체를 생성합니다.
예를 들어 getCustomerName이 있으면 key = customerName, value = 값으로 할당을 해줍니다.

MapSqlParameterSource

SqlParameterSource param = new MapSqlParameterSource()
                .addValue("customerName", updateParam.getCustomerName())
                .addValue("age", updateParam.getAge())
                .addValue("phoneNumber", updateParam.getPhoneNumber())
                .addValue("id", customerId);

위의 예시와 같이 원하는 파라미터를 메서드 체이닝 방식으로 값을 넣을 수 있습니다.

Map

Customer customer = jdbcTemplate.queryForObject(sql, Map.of("id", customerId), customerRowMapper());

다음과 같이 파라미터가 간단한 경우에는 Map을 사용해서 파라미터를 지정할 수 있습니다.

데이터 저장

@Override
public Customer save(Customer customer) {
    String sql = "insert into customer(customer_name, age, phone_number) values (:customerName, :age, :phoneNumber)";
    BeanPropertySqlParameterSource param = new BeanPropertySqlParameterSource(customer);
    GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(sql, param, keyHolder);

    long key = keyHolder.getKey().longValue();
    customer.setId(key);
    return customer;
}
String sql = "insert into customer(customer_name, age, phone_number) values (:customerName, :age, :phoneNumber)";

기존 jdbcTemplate에서 사용하던 query 방식과 조금 다르게 ? 대신 :변수명을 사용하고 있습니다.

쿼리를 실행하기 위해서는 jdbcTemplate.update()를 실행하면 됩니다.

데이터 수정

@Override
public void update(Long customerId, CustomerUpdateDto updateParam) {
    String sql = "update customer set customer_name = :customerName, age = :age, phone_number = :phoneNumber where id = :id";
    SqlParameterSource param = new MapSqlParameterSource()
            .addValue("customerName", updateParam.getCustomerName())
            .addValue("age", updateParam.getAge())
            .addValue("phoneNumber", updateParam.getPhoneNumber())
            .addValue("id", customerId);

    jdbcTemplate.update(sql, param);
}

데이터 수정의 경우 저장과 마찬가지로 jdbcTemplate.update()를 실행하면 됩니다.

데이터 단건 조회

@Override
public Optional<Customer> findById(Long customerId) {
    String sql = "select * from customer where id = :id";
    try {
        Customer customer = jdbcTemplate.queryForObject(sql, Map.of("id", customerId), customerRowMapper());
        return Optional.of(customer);
    } catch (EmptyResultDataAccessException e) {
        return Optional.empty();
    }
}

--- 

private RowMapper<Customer> customerRowMapper() {
    return BeanPropertyRowMapper.newInstance(Customer.class);
}

데이터 단건 조회의 경우 jdbcTemplate.queryForObject()를 사용하면 됩니다.
RowMapper의 경우 BeanPropertyRowMapper을 사용하면 람다 표현식을 사용한 것보다 더욱 간편하게 RowMapper를 구현할 수 있습니다.

데이터 다수 조회

@Override
public List<Customer> findAll() {
    String sql = "select * from customer";
    return jdbcTemplate.query(sql, customerRowMapper());
}

데이터 다수 조회의 경우 jdbcTemplate.query()를 사용하면 됩니다.

해당 글에서 사용한 예제코드는 다음 링크에 있습니다.
예제코드 바로가기