작심칠일러의 스프링 시작하기(7)-2

서은경·2022년 8월 10일
0

Spring

목록 보기
12/43

JdbcTemplate 을 이용한 쿼리 실행

JdbcTemplate 은 JDBC 코어 패키지의 중심 클래스이며 자원 생성 및 반환처리로 JDBC 사용을 단순화한다.(데이터 저장을 위해 도와주는 API라고 보면 된다.) 가장 먼저 JdbcTemplate 객체를 생성하고

package spring;

import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;

public class MemberDao {

    private JdbcTemplate jdbcTemplate;

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

스프링 설정에 MemberDao 빈 설정을 추가한다.


    @Bean
    public MemberDao memberDao() {
        return new MemberDao(dataSource());
    }
  1. 조회쿼리
  • JdbcTemplate 클래스는 select 쿼리 실행을 위한 query() 메서드를 제공한다. 자주 사용되는 쿼리 메서드는 다음과 같다.
    • List<T> query(String sql, RowMapper<T> rowMapper)
    • List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
    • List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
  • query() 메서드는 sql 파라미터로 전달받은 쿼리를 실행하고 RowMapper를 이용해서 ResultSet의 결과를 자바 객체로 변환한다.
package org.springframework.jdbc.core;

public interface RowMapper<T> {
	T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

// RowMapper 객체를 query 메서드의 매개변수로 넘겨주게 되면 스프링 컨테이너는 sql을 수행 후 자동으로 RowMapper 객체의 mapRow()를 호출한다
  • 결과가 1행인 경우 사용할 수 있는 queryForObject() 메서드를 제공한다.주요 메서드는 다음과 같다.

    • T queryForObject(String sql, Class<T> requiedType)
    • T queryForObject(String sql, Class<T> requiedType, Object... args)
    • T queryForObject(String sql, RowMapper<T> rowMapper)
    • T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
	public int count() {
        // 두번째 파라미터는 컬럼을 읽어올 때 사용할 타입 지정
        Integer count = jdbcTemplate.queryForObject(
                "select count(*) from MEMBER", Integer.class
        );
        return count;
    }
  1. 변경쿼리
  • INSERT, UPDATE, DELETE 쿼리는 update() 메서드를 사용한다.
    • int update(String sql)
    • int update(String sql, Object... args)
  1. PreparedStatementCreator를 이용한 실행 쿼리
  • JdbcTemplate 클래스가 제공하는 메서드 중 PreparedStatementCreator 인터페이스를 파라미터로 갖는 메서드는 다음과 같다
    • List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper)
    • int update(PreparedStatementCreator psc)
    • int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
package org.springframework.jdbc.core

import java.sql.Connection;
import java.sql.PreparedStatement
import java.sql.SQLException

public interface PreparedStatementCreator {
	PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}

PreparedStatementCreator 인터페이스의 createPreparedStatement() 메서드는 Connection 타입의 파라미터를 갖는다. PreparedStatementCreator를 구현한 클래스는 파라미터인 con 을 이용해 PreparedStatement 객체를 생성하고 인덱스 파라미터를 알맞게 설정한 뒤에 리턴하면 된다.
  1. INSERT 쿼리 실행 시 KeyHolder를 이용해 자동 생성 키값 구하기
public void insert(Member member) {
        // GeneratedKeyHolder 객체를 생성(자동 생성된 키값을 구해주는 KeyHolder 구현클래스임)
        KeyHolder keyHolder = new GeneratedKeyHolder();

        //PreparedStatementCreator 객체와 KeyHolder 객체를 파라미터로 가짐
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                // 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
                PreparedStatement pstmt = con.prepareStatement(
                        "insert into MEMBER(EMAIL, PASSWORD, NAME, REGDATE) values (?,?,?,?)",
                        new String[]{"ID"}
                );

                // 인덱스 파라미터의 값 설정
                pstmt.setString(1, member.getEmail());
                pstmt.setString(2, member.getPassword());
                pstmt.setString(3, member.getName());
                pstmt.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime()));
                // 생성한 PreparedStatement 객체 리턴
                return pstmt;
            }
        }, keyHolder);
        Number keyValue = keyHolder.getKey();
        member.setId(keyValue.longValue());
}
  • PreparedStatement 객체를 생성할 때 두번째 파라미터로 String 배열인 {"ID"}를 주었다. 이 두번째 파라미터는 자동 생성되는 키 칼럼 목록을 지정할 때 사용한다.

테스트

MemberDao.java

package spring;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import java.sql.*;
import java.util.List;

@Component
public class MemberDao {

    private JdbcTemplate jdbcTemplate;

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

    public Member selectByEmail(String email) {
        List<Member> results = jdbcTemplate.query(
                "select * from MEMBER where EMAIL = ?",
                /* RowMapper 구현 클래스를 만들어 해당 코드 삭제
                new RowMapper<Member>() {

                    @Override
                    public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Member member = new Member(
                            rs.getString("EMAIL"),
                            rs.getString("PASSWORD"),
                            rs.getString("NAME"),
                            rs.getTimestamp("REGDATE").toLocalDateTime());
                        member.setId(rs.getLong("ID"));
                        return member;
                    }
                },
                 */
                new MemberRowMapper(),
                email);
        return results.isEmpty() ? null : results.get(0);
    }

    public void insert(Member member) {
        // GeneratedKeyHolder 객체를 생성(자동 생성된 키값을 구해주는 KeyHolder 구현클래스임)
        KeyHolder keyHolder = new GeneratedKeyHolder();

        //PreparedStatementCreator 객체와 KeyHolder 객체를 파라미터로 가짐
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                // 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
                PreparedStatement pstmt = con.prepareStatement(
                        "insert into MEMBER(EMAIL, PASSWORD, NAME, REGDATE) values (?,?,?,?)",
                        new String[]{"ID"}
                );

                // 인덱스 파라미터의 값 설정
                pstmt.setString(1, member.getEmail());
                pstmt.setString(2, member.getPassword());
                pstmt.setString(3, member.getName());
                pstmt.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime()));
                // 생성한 PreparedStatement 객체 리턴
                return pstmt;
            }
        }, keyHolder);
        Number keyValue = keyHolder.getKey();
        member.setId(keyValue.longValue());


        /* 람다식 사용
        jdbcTemplate.update((Connection con) -> {
            PreparedStatement pstmt = con.prepareStatement(
                    "insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE)" + "values (?,?,?,?)",
                    new String[]{"ID"});

            pstmt.setString(1, member.getEmail());
            pstmt.setString(2, member.getPassword());
            pstmt.setString(3, member.getName());
            pstmt.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime()));

            return pstmt;

            }, keyHolder);

         */
    }

    public void update(Member member) {
        jdbcTemplate.update(
                "update Member set NAME=?, PASSWORD=? where EMAIL=?",
                member.getName(), member.getPassword(), member.getEmail()
        );
    }

    public List<Member> selectAll() {
        List<Member> results = jdbcTemplate.query("select * from MEMBER", new MemberRowMapper());
        return results;
    }

    public int count() {
        // 두번째 파라미터는 컬럼을 읽어올 때 사용할 타입 지정
        Integer count = jdbcTemplate.queryForObject(
                "select count(*) from MEMBER", Integer.class
        );
        return count;
    }
}

MainForMemberDao

package main;

import config.AppCtx;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.Member;
import spring.MemberDao;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

public class MainForMemberDao {
    /*
        static 필드에 autowired 안되는 이유는
        스프링 컨테이너가 올라갈때 등록된 빈을 가지고 설정에 따른 의존성 주입을 하는데
        static이면 컨테이너가 올라가기도 전에 관련 의존성을 찾기 때문에
        memberDao 클래스가 빈으로 생성되기 전이므로 nullpointer가 발생
     */
    private static MemberDao memberDao;

    public static void main(String[] args) {
        // 자바 어노테이션을 이용한 클래스로부터 객체 설정정보를 가져옴
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);

        memberDao = ctx.getBean(MemberDao.class);

        selectAll();
        updateMember();
        insertMember();

        ctx.close();
    }

    private static void selectAll() {
        System.out.println("-----------------selectAll");
        int total = memberDao.count();
        System.out.println("전체 데이터 : " + total);
        List<Member> members = memberDao.selectAll();
        for (Member m : members) {
            System.out.println(m.getId() + ":" + m.getEmail() + ":" + m.getName());
        }
    }
    private static void updateMember() {
        System.out.println("-----------------updateMember");
        Member member = memberDao.selectByEmail("eeun95@gmail.com");
        String oldPw = member.getPassword();
        String newPw = Double.toHexString(Math.random());
        member.changePassword(oldPw, newPw);

        memberDao.update(member);
        System.out.println("암호 변경 : " + oldPw + ">" + newPw);
    }

    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMddHHmmss");

    private static void insertMember() {
        System.out.println("-----------------insertMember");

        String prefix = formatter.format(LocalDateTime.now());
        Member member = new Member(prefix + "@test.com", prefix, prefix, LocalDateTime.now());
        memberDao.insert(member);
        System.out.println(member.getId() + " 데이터 추가");
    }
}

🙋‍♀️ 순서가 당최 어떻게 되나요?
💡 AnnotationConfigApplicationContext 생성자를 이용해 컨텍스트 객체를 생성합니다 (스프링 컨테이너 초기화 시점, 설정정보를 읽어와 빈 객체 생성 및 의존 주입 수행) -> memberDao 객체에 빈 객체를 담아줍니다 -> 각각의 메서드가 실행됩니다

0개의 댓글