SPRING

스프링 DB 2편 - 데이터 접근 활용 기술 (2)

illho 2023. 6. 15. 22:08

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

 

스프링 DB 2편 - 데이터 접근 활용 기술 - 인프런 | 강의

백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의 소개 | 인

www.inflearn.com

 

JdbcTemplate 

 

장점

  • 설정의 편리함
    • JdbcTemplate은 spring-jdbc 라이브러리에 포함되어 있는데, 이 라이브러리는 스프링으로 JDBC를 사용할 때 기본으로 사용되는 라이브러리이다.
  • 반복 문제 해결
    • JdbcTemplate은 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때는 발생하는 대부분의 반복 작업을 대신 처리해준다.
    • 개발자는 SQL을 작성하고, 전달할 파라미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
    • 반복 작업을 대신 처리 해준다. (커넥션 획득, statement 준비 실행, 루프 실행, 커넥션 종료 등등)

단점

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

 

JdbcTemplate 설정

 

org.springframework.boot:spring-boot-starter-jdbc 를 추가하면 JdbcTemplate이 들어있는 spring-jdbc가 라이브러리에 포함된다.

 

 

JdbcTemplate 적용

 

package hello.itemservice.repository.jdbctemplate;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * JdbcTemplate
 */
@Repository
@Slf4j
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {

    private final JdbcTemplate template;

    public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
        this.template = new JdbcTemplate(dataSource);
    }
    @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;
    }

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

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

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

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

    }
}

 

 

JdbcTemplate 기능 정리

 

JdbcTemplate 

  • 순서 기반 파라미터 바인딩을 지원한다.

NamedParameterJdbcTemplate

  • 이름 기반 파라미터 바인딩을 지원한다.

SimpleJdbcInsert

  • INSERT SQL을 편리하게 사용할 수 있다.

SimpleJdbcCall

  • 스토어드 프로시저를 편리하게 호출할 수 있다.

 

조회

 

단건 조회 - 숫자 조회

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

 

하나의 로우를 조회할 때는 queryForObject()를 사용하면 된다. 조회 대상이 객체가 아니라 단순 데이터 하나라면 타입을 Integer.class , String.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);

 

객체 하나를 조회한다. 객체로 매핑해야 하므로 RowMapper를 사용해야 한다.

 

변경(INSERT , UPDATE , DELETE )

 

데이터를 변경할 때는 jdbcTemplate.update()를 사용하면 된다. 참고로 int 반환값을 반환하는데, SQL 실행 결과에 영향받은 로우 수를 반환한다.

 

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

 

기타 기능

 

임의의 SQL을 실행할 때는 execute()를 사용하면 된다.

jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");