NamedParameterJdbcTemplate
- JdbcTemplate을 쓸 때 매번 getter로 값을 가져오고, 넣어주는게 아니라
<이름, 값>
쌍으로 된 맵을 이용해서 이름을 지어주고 넣어주면 좋지 않을까?
private final NamedParameterJdbcTemplate jdbcTemplate;
public CustomerNamedParamterJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private Map<String, Object> toParamMap(Customer customer) {
return new HashMap<>() {{
put("customerId", customer.getCustomerId().toString().getBytes());
put("name", customer.getName());
put("email", customer.getEmail());
put("createdAt", Timestamp.valueOf(customer.getCreateAt()));
put("lastLoginAt", customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null);
}};
}
@Override
public Customer insert(Customer customer) {
var update = jdbcTemplate.update(
"INSERT INTO customers(customer_id, name, email, created_at) VALUES (UUID_TO_BIN(:customerId), :name, :email, :createdAt)",
toParamMap(customer)
);
if (update != 1) {
throw new RuntimeException("Nothing was inserted");
}
return customer;
}
@Override
public Customer update(Customer customer) {
var update = jdbcTemplate.update(
"UPDATE customers SET name = :name, email = :email, last_login_at = :lastLoginAt WHERE customer_id = UUID_TO_BIN(:customerId)",
toParamMap(customer)
);
if (update != 1) {
throw new RuntimeException("Nothing was updated");
}
return customer;
}
@Override
public List<Customer> findAll() {
return jdbcTemplate.query(SELECT_ALL_SQL, customerRowMapper);
}
@Override
public Optional<Customer> findById(UUID customerId) {
try {
return Optional.ofNullable(
jdbcTemplate.queryForObject(
"SELECT * FROM customers WHERE customer_id = UUID_TO_BIN(:customerId)",
Collections.singletonMap("customerId", customerId.toString().getBytes()),
customerRowMapper
)
);
} catch (EmptyResultDataAccessException e) {
logger.error("Got empty result", e);
return Optional.empty();
}
}
@Override
public Optional<Customer> findByName(String name) {
try {
return Optional.ofNullable(
jdbcTemplate.queryForObject(
"SELECT * FROM customers WHERE name = :name",
Collections.singletonMap("name", name),
customerRowMapper
)
);
} catch (EmptyResultDataAccessException e) {
logger.error("Got empty result", e);
return Optional.empty();
}
}
@Override
public Optional<Customer> findByEmail(String email) {
try {
return Optional.ofNullable(
jdbcTemplate.queryForObject(
"SELECT * FROM customers WHERE email = :email",
Collections.singletonMap("email", email),
customerRowMapper)
);
} catch (EmptyResultDataAccessException e) {
logger.error("Got empty result", e);
return Optional.empty();
}
}
@Override
public void deleteAll() {
jdbcTemplate.getJdbcTemplate().update(DELETE_ALL_SQL, Collections.emptyMap());
}
@Override
public int count() {
return jdbcTemplate.queryForObject(SELECT_COUNT_ALL, Collections.emptyMap(),Integer.class);
}
- 이런식으로 공통부분을 따로 추출한 맵을 만들어서 전역으로 사용 가능
- NamedParameterJdbcTemplate의
query
, update
함수는 두 번째 파라미터로 mapper를 요구
- 이름 - 정보 쌍으로 이뤄진
Map
을 전달해주면 됨
- 만약에 필요한 정보가 없는 경우 (
delete
같은 경우)엔 Collections.empty()
를 전달하면 됨
- 한 개의 정보만 필요한 경우엔
Collections.singletonMap()
테스트
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
- 테스트 설정 클래스에 저렇게 Bean을 설정해주면 된다.
- NamedParameterJdbcTemplate은 인자로 DataSource를 받을 수도 있고, JdbcTemplate을 받을 수도 있다
- 테스트 자체는 JdbcTemplate이랑 동일하다.