본문 바로가기
인프런/스프링 DB 2편 - 데이터 접근 활용 기술

[인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 2. 데이터 접근 기술 - 스프링 JdbcTemplate

by hxxyeoniii 2024. 7. 18.

JdbcTemplate 소개와 설정

SQL을 직접 사용하는 경우 스프링이 제공하는 JdbcTemplate을 선택하면 좋음

 

(+) JdbcTemplate은 spring-jdbc 라이브러리에 포함되어 있는데, 이 라이브러리는 스프링으로 JDBC를 사용할 때 기본으로 사용되는 라이브러리로, 별도 복잡한 설정 없이 바로 사용이 가능함

(+) JdbcTemplte은 템플릿 콜백 패턴을 사용해 JDBC를 직접 사용할 때 발생하는 반복 작업을 대신 처리해줌

   - 커넥션 획득

   - statement 준비 및 실행

   - 결과를 반복하도록 루프 실행

   - 커넥션 종료, statement, resultset 종료

   - 트랜잭션을 다루기 위한 커넥션 동기화

   - 예외 발생시 스프링 예외 변환기 실행

(+) 개발자는 SQL을 작성하고, 전달 파라미터를 정의하고, 응답 값을 매핑하기만 하면 됨

 

(-) 동적 SQL을 해결하기 어려움


JdbcTemplate 적용 

JdbcTemplateRepositoryV1.java

 

JdbcTemplate은 dataSource가 필요함 : dataSource를 의존 관계 주입 받고 생성자 내부에서 JdbcTemplate 생성

(JdbcTemplate을 스프링 빈으로 직접 등록 후 주입받아도 됨)

private final JdbcTemplate template;
public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
    this.template = new JdbcTemplate(dataSource);
}

 

 

save()

@Override
public Item save(Item item) {
        String sql = "insert into item (item_name, price, quantity) values (?, ?, ?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        template.update(connection -> {
            {"id"});
            //자동 증가 키
            PreparedStatement ps = connection.prepareStatement(sql, new String[]
            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;
}

 

 

update()

@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
        String sql = "update item set item_name=?, price=?, quantity=? where id=?";
        template.update(sql,
            updateParam.getItemName(),
            updateParam.getPrice(),
            updateParam.getQuantity(),
            itemId);
}

 

 

findById()

@Override
public Optional<Item> findById(Long id) {
        String sql = "select id, item_name, price, quantity from item where id = ?";
        
        try {
            Item item = template.queryForObject(sql, itemRowMapper(), id);
            return Optional.of(item);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        } 
}

 

 

findAll()

@Override
public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();
        String sql = "select id, item_name, price, quantity from item"; //동적 쿼리
        
        
        if (StringUtils.hasText(itemName) || maxPrice != null) {
            sql += " where";
        }
        
        boolean andFlag = false;
        List<Object> param = new ArrayList<>();
        
        if (StringUtils.hasText(itemName)) {
            sql += " item_name like concat('%',?,'%')";
            param.add(itemName);
            andFlag = true;
        }
        
        if (maxPrice != null) {
            if (andFlag) {
                sql += " and";
            }
            
            sql += " price <= ?";
            param.add(maxPrice);
         }
            
         log.info("sql={}", sql);
         return template.query(sql, itemRowMapper(), param.toArray());
     }
     
     private RowMapper<Item> itemRowMapper() {
         return (rs, rowNum) -> {
             Item item = new Item();
             item.setId(rs.getLong("id"));
             item.setItemName(rs.getString("item_name"));
             item.setPrice(rs.getInt("price"));
             item.setQuantity(rs.getInt("quantity"));
             return item;
	}; 
}

-> template.query() : 결과가 하나 이상일 때 사용

-> RowMapper는 데이터베이스의 반환 결과인 ResultSet을 객체로 변환

-> 실무에서는 더 복잡한 동적 쿼리가 필요한데 이는 너무 복잡함.. MyBatis는 이런 동적 쿼리를 쉽게 작성할 수 있게 도와줌


JdbcTemplate - 이름 지정 파라미터

JdbcTemplateItemRepositoryV2.java

 

NamedParameterJdbcTemplate 사용 : 파라미터를 이름을 지정해서 바인딩

private final NamedParameterJdbcTemplate template;
public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
    this.template = new NamedParameterJdbcTemplate(dataSource);
}

 

 

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

 

 

 

이름 지정 바인딩에서 자주 사용하는 파라미터 종류는 크게 3가지가 있다.

 

1. Map 사용

Map<String, Object> param = Map.of("id", id);
Item item = template.queryForObject(sql, param, itemRowMapper());

 

 

2. SqlParameterSource > MapSqlParameterSource

: 메서드 체인을 통해 편리한 사용

SqlParameterSource param = new MapSqlParameterSource()
    .addValue("itemName", updateParam.getItemName()) 
    .addValue("price", updateParam.getPrice()) 
    .addValue("quantity", updateParam.getQuantity()) 
    .addValue("id", itemId); //이 부분이 별도로 필요하다.
template.update(sql, param);

 

 

3. SqlParameterSource > BeanPropertySqlParameterSource

: 자바빈 프로퍼티 규약을 통해 자동으로 파라미터 객체 생성

SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);

 

 

BeanPropertyRowMapper 사용

: 기존 V1의 RowMapper 함수를 다음과 같이 변경 가능

private RowMapper<Item> itemRowMapper() {
    return BeanPropertyRowMapper.newInstance(Item.class); //camel 변환 지원
}

-> ResultSet의 결과를 받아 자바빈 규약에 맞춰 데이터 변환

-> 언더스코어 표기법을 카멜로 자동으로 변환해줌


JdbcTemplate - SimpleJdbcInsert

JdbcTemplateItemRepositoryV3.java

 

SimpleJdbcInsert : Insert SQL을 직접 작성하지 않아도 됨

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"); //생략 가능
}

-> withTableName : 데이터 저장할 테이블 명

-> usingGenerateKeyColumns : key를 생성하는 PK 컬럼 명 지정

-> usingColumns : INSERT SQL에 사용할 컬럼 지정

 

 

save()

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

정리

JdbcTemplate의 최대 단점은 동적 쿼리 문제를 해결하지 못한다는 것과, SQL을 자바로 작성하기 때문에 SQL 라인이 코드를 넘어갈 때 마다 문자 더하기를 해주어야 한다는 것이다.

 

-> 이 문제를 해결할 수 있는 기술이 MyBatis이다.