BackEnd/Database

JdbcTemplate

연향동큰손 2025. 2. 7. 13:49

 실무에서 가장 간단하고 실용적인 방법으로 SQL을 사용하려면 JdbcTemplate를 사용하면 된다.

 

 

기본 사용법

 

단건 조회(queryForObject)

int rowCount = jdbcTemplate.queryForObject("select count(*) from t_actor", 
Integer.class);

 

단건 조회 - 숫자 조회, 파라미터 바인딩

 int countOfActorsNamedJoe = jdbcTemplate.queryForObject(
 "select count(*) from t_actor where first_name = ?", Integer.class, 
"Joe");

 

단건 조회 - 문자 조회

 String lastName = jdbcTemplate.queryForObject(
 "select last_name from t_actor where id = ?",
 String.class, 1212L);

 

단건 조회 - 객체 조회

 Actor actor = jdbcTemplate.queryForObject(
 "select first_name, last_name from t_actor where id = ?",
        (resultSet, rowNum) -> {
 Actor newActor = new Actor();
            newActor.setFirstName(resultSet.getString("first_name"));
            newActor.setLastName(resultSet.getString("last_name"));
 return newActor;
        },
 1212L);

 

목록 조회 - 객체

List<Actor> actors = jdbcTemplate.query(
 "select first_name, last_name from t_actor",
        (resultSet, rowNum) -> {
 Actor actor = new Actor();
            actor.setFirstName(resultSet.getString("first_name"));
            actor.setLastName(resultSet.getString("last_name"));
 return actor;
        });

 

목록 조회 - 객체

 private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
 Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
 return actor;
 };
 public List<Actor> findAllActors() {
 return this.jdbcTemplate.query("select first_name, last_name from t_actor", 
actorRowMapper);
 }

 

 

INSERT

jdbcTemplate.update(
 "insert into t_actor (first_name, last_name) values (?, ?)",
 "Leonor", "Watling");

 

UPDATE

 jdbcTemplate.update(
 "update t_actor set last_name = ? where id = ?",
 "Banjo", 5276L);

 

DELETE

 jdbcTemplate.update(
 "delete from t_actor where id = ?",
 Long.valueOf(actorId));

 

 

이름 지정 파라미터[NamedParameterJdbcTemplate]

 

 

JdbcTemplate을 기본으로 사용하면 파라미터를 sql에 순서대로 바인딩한다.

String sql = "update item set item_name=?, price=?, quantity=? where id=?";
 template.update(sql,
        itemName,
        price,
        quantity,
        itemId);

 

파라미터가 적을때는 문제가 안되지만 파라미터가 많이 있을때는 문제가 발생할 수 있다.

 

이러한 문제를 해결하기 위해 Jdbc에서는 NamedParameterJdbcTemplate를 제공한다.

 

<JdbcTemplate 기본 사용 코드>

@Override
public Item save(Item item) {
    String sql = "insert into item (item_name, price, quantity) values (?, ?, ?)";
    KeyHolder keyHolder = new GeneratedKeyHolder();
    template.update(connection ->{
        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;
}

 

 

< NamedParameterJdbcTemplate 사용 코드(save)>

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

 

 

< NamedParameterJdbcTemplate 사용 코드(update)>

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

MapSqlParameterSource ==> Map과 유사하며 SQL타입을 지정할 수 있는 등 SQL에 특화된 기능을 제공

 

 

< NamedParameterJdbcTemplate 사용 코드(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()); //데이터베이스에서 조회한 결과를 Item 객체로 매핑.
       return Optional.of(item);
   }catch (EmptyResultDataAccessException e){
       return Optional.empty(); //item이 NULL일 경우
   }
}

private RowMapper<Item> itemRowMapper() { //데이터베이스에서 가져온 결과를 Item 클래스의 필드에 자동으로 매핑해준다.
        return BeanPropertyRowMapper.newInstance(Item.class);
    }

 

BeanPropertyRowMapper ==> ResultSet 의 결과를 받아서 자바빈 규약에 맞추어 데이터를 변환한다.

 

또한, BeanPropertyRowMapper는 camel 변환을 지원한다.

따라서 select item_name으로 조회해도 setItemName()에 문제없이 들어간다.

 

queryForObject==> 하나의 로우 조회

 

 

< NamedParameterJdbcTemplate 사용 코드(findAll)>

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

 

 

SimpleJdbcInsert

 

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

 

< SimpleJdbcInsert 사용 코드>

 

@Slf4j
public class JdbcTemplateRepositoryV3 implements ItemRepository {

    private final NamedParameterJdbcTemplate template;
    private final SimpleJdbcInsert jdbcInsert;

    public JdbcTemplateRepositoryV3(DataSource dataSource){
        this.template = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcInsert = new SimpleJdbcInsert(dataSource)
                .withTableName("item")
                .usingGeneratedKeyColumns("id");
      //  .usingColums("item_name","price","quantity"); 생략 가능

    }

    @Override
    public Item save(Item item) {
        SqlParameterSource param = new BeanPropertySqlParameterSource(item);
        Number key = jdbcInsert.executeAndReturnKey(param); // INSERT SQL을 실행
        item.setId(key.longValue());
        return item;
    }
    }

 

  • withTableName : 데이터를 저장할 테이블 명 지정
  • usingGeneratedKeyColumns : 테이블의 기본키 칼럼 명을 지정

 

로그를 확인하면 INSERT 쿼리를 자동으로 생성하는 것을 확인할 수 있다.

 

 

JdbcTemplate의 장단점

 

장점

  • 별도의 복잡한 설정 없이 사용 가능
  • JDBC를 직접 사용할때 발생하는 반복작업(커넥션 획득, statement 를 준비하고 실행, 커넥션 종료,  statement, resultset종료, 예외발생시 스프링 예외 변환기 실행 ....)을 대신 처리해준다.

단점

  • 동적 SQL을 해결하기 어렵다.