스프링/6. 스프링 DB-2

9. 이름지정 파라미터

sdafdq 2023. 10. 8. 18:09

우리가 sql 할 때

String sql = "update item set item_name = ?, price = ?, quantity = ? where id = ?";

이렇게 하는데 나중에 뭐 db에 colum이 추가된다던지 하면 저기에다 추가로 넣어야 된다.

근데 그러 한 과정에서 예를들면 저 위에 price랑 quantity 순서가 바뀌거나 등..

 

그럼 진짜 큰일난다.

이런 일 없을 거 같지만, 실무에서 막 파라미터 10~20개가 넘어가는 일도 많이 있덴다.

 

버그 중 가장 고치기 힘든 버그가 DB버그라고 한다.

코드만 고치는 수준이 아니라, DB를 복구해야 하기 때문이다.

DB복구하고 데이터 보정하고 넣고..

들어가야 하는 인적 자원이 어마어마 하다.

 

 

코드를 줄이는 것도 중요하지만, 모호함을 제거하는 것도 중요하다.

 

이러 한 심각한 문제를 보완하기 위해, JdbcTemplate에서는 이름을 지정해서 바인딩을 해 줄수 있게 하는,

JdbcTemplate를 상속받은 NamedParameterJdbcTemplate를 제공해준다.

 

 

그럼 직접 한번 코드로 보자.

 

먼저, 이제 그냥 JdbcTemplate가 아니라 NamedparameterJdbcTemplate를 쓴다.

@Slf4j
@Repository
public class JdbcTemplateItemRepositoryV2 implements ItemRepository {
    private final NamedParameterJdbcTemplate template;

    public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
        this.template = new NamedParameterJdbcTemplate(dataSource); //커넥션을 얻어오고 그래야 되기 때문에 필요. 어떤 DB인지도 알아야 하고.
    }

저거 근데 필드가 인터페이스가 아닌 구현체를 참조하는 게 좀 아쉽다.

그리고, 물론 JdbcTemplate를 밖에서 Bean으로 주입받아도 되긴 하지만, 관례상 dataSource만 주입받고 Jdbc템플릿은 안에서 만든다.

 

 

@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;
}

save()

저거 :itemName, :price, :quantity 가 뭐냐면,

SqlParameterSource 생성할 때 item 객체로 줘서 생성하는데, (뭔가 파라미터 모둠같은 걸 담당하는 객체인가 봄.)

저 인자로 들어간 객체의 필드명과 필드값이 뭐랄까 약간 key:value 형태로 만들어 질 듯?

왜냐하면 item 클래스의 필드명이

이거니까 저 이름에 맞게 알아서 바인딩이 잘 됨.

 

뭐 명시적이기도 하고 간편해 지기도 했음.

근데 이왕 이럴거면 좀 아쉬운거 item_name, price등 이런 것의 순서도 상관없으면 좋겠는데,

근데 JdbcTemplate는 우리가 직접 쿼리를 만들어서 날리는 거라 물리적으로 불가능 하긴 함.

JPA에서는 상관 없을 듯.

 

그리고 이제 namedJdbcTempalate 쓰니까 key 얻어온다고 콜백함수 넣고 안해도 된다.

 

 

그 다음 update()

@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
    String sql = "update item set item_name = :itemName, price = :price, quantity = :quantity where id = :id";

    MapSqlParameterSource param = new MapSqlParameterSource();

    param.addValue("itemName", updateParam.getItemName());
    param.addValue("price", updateParam.getPrice());
    param.addValue("quantity", updateParam.getQuantity());
    param.addValue("id", itemId);

    template.update(sql, param);
}

위처럼 BeanPropertySqlParameterSource 말고 이렇게 여러 종류의 sql파라미터소스를 쓸 수 있음.

이거는 자기가 직접 key, value 형식으로 넣어주는 거임.

 

그리고 MapSqlParameterSource 이거 인터페이스가 SqlParameterSource인데,

근데 그러면 .addValue를 못 쓴다. addValue는 MapSqlParameterSource 기술이라.

물론 

new MapSqlParameterSource()

            .addValue(~~~)

            .addValue(~~~)

            ...

            ...

 

MapSqlParameterSource param = new MapSqlParameterSource()
    .addValue("itemName", updateParam.getItemName())
    .addValue("price", updateParam.getPrice())
    .addValue("quantity", updateParam.getQuantity())
    .addValue("id", itemId);

이런 식으로..

이러면 추상체인 SqlParamterSource를 참조할 수 있긴 한데..

 

 

뭐랄까 그냥 이 메소드 내에서도 생성부, 사용부를 나눠놓고 싶다는 생각이 들었다.

그게 훨씬 깔끔해 보여서..

 

 

다음은 findById()

@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도 들어갈 수 있다.

Map.of(key,value)는

new Map() + Map().add(key, value) 뭐 그런 거 인듯?

 

template.queryForObject(쿼리, 바인딩될파라미터들, 맵퍼)

 

 

 

findAll()

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

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

private RowMapper<Item> itemRowMapper(){
    return BeanPropertyRowMapper.newInstance(Item.class);
}

뭐 똑같이 BeanPropertySqlParameterSource(cond) 하면 cond의 필드명 : 값

이렇게 되서 알아서

template.query(sql, param, rowMapper())

된다.

 

itemRowMapper()는

아예 return BeanPropertyRowMapper.newInstance(Item.class)

하면

rs의 열이름과 Item의 필드명들이 알아서 각자 바인딩 되서 객체로 만들어 지는 것 같다.

camel표기법이랑 _ 쓰는 거 이거도 인지하고 변환해서 잘 넣어짐.

 

이것들이랑

 

이것들 알아서 잘 묶임. 

'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글

11. JDBC 기능들  (0) 2023.10.08
10. 이름지정 파라미터2  (0) 2023.10.08
8. 구현한 Jdbc로 갈아 끼우기.  (0) 2023.10.08
7. Jdbc 템플릿 + h2를 리포지토리 구현체로  (0) 2023.10.08
6. Jdbc 템플릿  (0) 2023.10.08