DB - JdbcTemplate SimpleJdbcInsert

박민수·2023년 11월 15일
post-thumbnail

JdbcTemplate SimpleJdbcInsert

JdbcTemplate은 INSERT SQL를 직접 작성하지 않아도 되도록 SimpleJdbcInsert라는 기능을 제공한다.

구현

JdbcTemplateItemRepositoryV3 은 ItemRepository 인터페이스를 구현했다.
this.jdbcInsert = new SimpleJdbcInsert(dataSource) 생성자를 보면 의존관계 주입은 dataSource 를 받고 내부에서 SimpleJdbcInsert 을 생성해서 가지고 있다.
스프링에서는 JdbcTemplate 관련 기능을 사용할 때 관례상 이 방법을 많이 사용한다.
물론 SimpleJdbcInsert을 스프링 빈으로 직접 등록하고 주입받아도 된다

@Slf4j
@Repository
public class JdbcTemplateItemRepositoryV3 implements ItemRepository {

    private final NamedParameterJdbcTemplate template;
    private final SimpleJdbcInsert jdbcInsert;
    
    public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
        this.template = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcInsert = new SimpleJdbcInsert(dataSource)
            .withTableName("item")
            .usingGeneratedKeyColumns("id");
            // .usingColumns("item_name", "price", "quantity"); //생략 가능
    }
    
    @Override
    public Item save(Item item) {
        SqlParameterSource param = new BeanPropertySqlParameterSource(item);
        Number key = jdbcInsert.executeAndReturnKey(param);
        item.setId(key.longValue());
        return item;
    }
    
    @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);
    }
    
    @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();
        }
    }
    
    @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);
    }
}

SimpleJdbcInsert는 생성 시점에 데이터베이스 테이블의 메타 데이터를 조회한다. 따라서 어떤 컬럼이 있는지 확인 할 수 있으므로 usingColumns 을 생략할 수 있다. 만약 특정 컬럼만 지정해서 저장하고 싶다면 usingColumns를 사용하면 된다. 애플리케이션을 실행해보면 SimpleJdbcInsert이 어떤 INSERT SQL을 만들어서 사용하는지 로그로 확인할 수 있다.

this.jdbcInsert = new SimpleJdbcInsert(dataSource)
    .withTableName("item")
    .usingGeneratedKeyColumns("id");
    // .usingColumns("item_name", "price", "quantity"); //생략 가능
  • withTableName : 데이터를 저장할 테이블 명을 지정한다.
  • usingGeneratedKeyColumns : key 를 생성하는 PK 컬럼 명을 지정한다.
  • usingColumns : INSERT SQL에 사용할 컬럼을 지정한다. 특정 값만 저장하고 싶을 때 사용한다. 생략할 수 있다.
  • save() : jdbcInsert.executeAndReturnKey(param)을 사용해서 INSERT SQL을 실행하고, 생성된 키 값도 매우 편리하게 조회할 수 있다.
public Item save(Item item) {
    SqlParameterSource param = new BeanPropertySqlParameterSource(item);
    Number key = jdbcInsert.executeAndReturnKey(param);
    item.setId(key.longValue());
    return item;
}

앞서 개발한 JdbcTemplateItemRepositoryV3를 사용하도록 스프링 빈에 등록하자.

@Configuration
@RequiredArgsConstructor
public class JdbcTemplateV3Config {
    private final DataSource dataSource;
    
    @Bean
    public ItemService itemService() {
    	return new ItemServiceV1(itemRepository());
    }
    
    @Bean
    public ItemRepository itemRepository() {
    	return new JdbcTemplateItemRepositoryV3(dataSource);
    }
}

JdbcTemplateV3Config.class를 설정으로 사용하도록 변경하자.

@Slf4j
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
}

참조
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard

profile
안녕하세요 백엔드 개발자입니다.

0개의 댓글