프로그래밍/JAVA Spring

[Spring 스프링] JdbcTemplate 써보기

hectick 2023. 4. 15. 17:19

 

스프링 공식문서를 참조해서 JdbcTemplate 사용법을 정리해보았다.

 

 

JdbcTemplate 생성

 

스프링 공식문서에 따르면 JdbcTemplate을 생성하는 방법은 두가지가 있다. 

1. DAO 구현체가 DataSource를 통해 JdbcTemplate를 직접 인스턴스화

2. 스프링 IoC 컨테이너에서 DAO에게 빈 참조로 제공

 

하지만 프로젝트의 의존성에 spring-boot-starter-jdbc 모듈을 추가한다면 스프링 부트가 DataSource와 JdbcTemplate 객체를 자동으로 IoC 컨테이너에 설정해준다. 

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    }

 

그러니까 우리는 IoC 컨테이너에 들어있는 DataSource 객체와 JdbcTemplate 객체를 가져다 쓰면 된다.

복잡한 것 필요 없고 다음 두가지 형태 중 하나를 골라 JdbcTemplate을 사용할 수 있다.

 

첫번째 방법

    @Repository
    public class CarDao {
        private final JdbcTemplate jdbcTemplate;

        public CarDao(DataSource dataSource) {
            this.jdbcTemplate = new JdbcTemplate(dataSource);
        }
    }

 

두번째 방법

    @Repository
    public class CarDao {
        private final JdbcTemplate jdbcTemplate;

        public CarDao(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    }

 

겉으로 봤을 때 두 방법의 차이는 DAO 생성자의 파라미터로 DataSource가 들어가냐, JdbcTemplate가 들어가냐이다.

 

첫번째 방법은 DAO 구현체 내에서 DataSource 빈을 생성자 주입 받아서 JdbcTemplate 객체를 직접 만든다. 이 방법을 사용하면 JdbcTemplate 객체의 생성과 관리에 대한 처리를 구현체 내에서 직접 해주어야 한다. 

 

두번째 방법은 DAO 구현체가 JdbcTemplate 빈을 생성자 주입 받는다.  이 방법을 쓰면 스프링에서 JdbcTemplate 객체의 생성과 관리를 자동으로 처리해준다. 

 

두 방법의 차이를 꼽는다면, JdbcTemplate를 직접 만들어 쓸 것이냐, 스프링 부트가 잘 만들어놓은 빈을 가져다 쓸 것이냐다.

 

 

Querying (SELECT)

 

조회할 때는 주로 queryForObject와 query 메서드를 사용한다.

queryForObject : 쿼리문 수행 결과가 한개일 때 사용

query : 쿼리문 수행 결과가 한개 이상일 때 사용

 

1. 조회된 행의 숫자를 가져온다.

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

 

2. 특정 조건을 만족하는 행의 숫자를 가져온다.

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

 

3. 조회된 값을 문자로 가져온다.

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

 

4. 조회된 내용을 하나의 도메인 객체로 만들어 가져온다.

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

 

5. 조회된 내용들을 도메인 객체 리스트로 만들어 가져온다.

    List<Actor> actors = this.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;
            });

 

위의 4번과 5번에서, 도메인 객체로 만들 때 코드의 중복이 발생한다. 이를 제거하기 위해서 RowMapper를 사용할 수 있다. 

RowMapper : 데이터베이스의 반환 결과인 ResultSet을 객체로 변환해 주는 클래스 

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

 

 

Updating (INSERT, UPDATE, DELETE)

 

업데이트를 해주는 코드들은 간단하니 한번 쭉 보면 된다.

 

1. INSERT

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

 

2. UPDATE

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

 

3. DELETE

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

 

 

NamedParameter

 

이름이 붙여진 파라미터를 사용함으로써, 쿼리문의 ? 순서와 파라미터의 순서가 일치하지 않아도 된다.

 

1. Map 자료구조를 이용해 파라미터에 이름을 붙여주는 방법

    // some JDBC-backed DAO class...
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int countOfActorsByFirstName(String firstName) {
        String sql = "select count(*) from T_ACTOR where first_name = :first_name";
        Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
        return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters,  Integer.class);
    }

 

2 MapSqlParameterSource를 이용해 파라미터에 이름을 붙여주는 방법

    // some JDBC-backed DAO class...
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int countOfActorsByFirstName(String firstName) {
        String sql = "select count(*) from T_ACTOR where first_name = :first_name";
        SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
        return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
    }

 

3. BeanPropertySqlParameterSource를 이용해 파라미터에 이름을 붙여주는 방법(자동)

자바빈 컨벤션을 따르는 자바빈을 이용해서 자동으로 파라미터 값들을 설정해준다.

    // some JDBC-backed DAO class...
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int countOfActors(Actor exampleActor) {
        String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
        SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
        return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
    }

 

 

데이터베이스에 Insert할 때 자동생성되는 키 가져오기

 

원래는 JdbcTemplate을 이용해 insert하고 난 후 key 값을 가져오려면 다음과 같은 거추장스러운 코드를 작성해야했다.

    @Repository
    public class InsertDao {
        private final JdbcTemplate jdbcTemplate;

        public InsertDao(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }

        public Customer insert(Customer customer) {
            String sql = "insert into customers (first_name, last_name) values (?, ?)";
            KeyHolder keyHolder = new GeneratedKeyHolder();

            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                ps.setString(1, customer.getFirstName());
                ps.setString(2, customer.getLastName());
                return ps;
            }, keyHolder);

            long id = Objects.requireNonNull(keyHolder.getKey()).longValue();
            return new Customer(id, customer.getFirstName(), customer.getLastName());
        }
    }

 

이럴땐 SimpleJdbcInsert를 쓰면 코드를 간단히 할 수 있다.

    @Repository
    public class SimpleInsertDao {
        private final SimpleJdbcInsert insertCustomer;

        public SimpleInsertDao(DataSource dataSource) {
            this.insertCustomer = new SimpleJdbcInsert(dataSource)
                    .withTableName("customers")
                    .usingGeneratedKeyColumns("id");
        }

        public Customer insert(Customer customer) {
            SqlParameterSource parameters = new BeanPropertySqlParameterSource(customer);
            Number id = insertCustomer.executeAndReturnKey(parameters);
            return new Customer(id.longValue(), customer.getFirstName(), customer.getLastName());
        }
    }

 

 

 

잘못된 내용을 댓글로 알려주세요