본문으로 바로가기

목차

    0. 환경

    • m1 macbook
    • IntelliJ IDEA(m1) - 202102
    • java 11(AdoptOpenJDK-11.0.11)

    1. JdbcTemplate

    • hello/hellospring/repository/JdbcMemberRepository.java
    • implementation 'org.springframework.boot:spring-boot-starter-jdbc'를 사용하므로 build.gradle 파일을 수정할 필요는 없습니다.
    • MyBatis와 비슷한 라이브러리로 jdbc api의 반복적인 코드를 제거(생산성 향상) 해 줍니다. 그러나 SQL은 직접 작성해야 합니다.
    • 디자인 패턴 중 템플릿 메서드 패턴를 주로 활용하였으므로 JdbcTemplate로 이름이 붙여졌습니다.

    [순수 Jdbc 리포지토리 구현]

    순수 Jdbc를 이용하여 구현 할 경우

     

    [JdbcTemplate를 활용하여 리포지토리 구현]

    개발자는 SQL만 처리하면 됩니다.

    JdbcTemplate를 활용하여 리포지토리 구현

     

    [순수 Jdbc 사용 시 개발자가 구현할 부분]

        @Override
        public Member save(Member member) {
    
            Connection conn = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
    
            try {
                conn = getConnection();
    
                String sql = "insert into member(name) values(?)";
                pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                pstmt.setString(1, member.getName());
                pstmt.executeUpdate();
                
                rs = pstmt.getGeneratedKeys();
    
                if (rs.next()) {
                    member.setId(rs.getLong(1));
                } else {
                    throw new SQLException("id 조회 실패");
                }
    
                return member;
    
            } catch (Exception e) {
                throw new IllegalStateException(e);
            } finally {
                close(conn, pstmt, rs);
            }
        }
        
        private Connection getConnection() {
    
            return DataSourceUtils.getConnection(dataSource);
        }
    
        private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
    
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            try {
                if (conn != null) {
                    close(conn);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        private void close(Connection conn) throws SQLException {
    
            DataSourceUtils.releaseConnection(conn, dataSource);
        }

     

    [JdbcTemplate를 사용 시 개발자가 구현할 부분]

                String sql = "insert into member(name) values(?)";
                pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                pstmt.setString(1, member.getName());
                pstmt.executeUpdate();
                
                rs = pstmt.getGeneratedKeys();
    
                if (rs.next()) {
                    member.setId(rs.getLong(1));
                } else {
                    throw new SQLException("id 조회 실패");
                }
    
                return member;

    2. JdbcTemplate 리포지토리 구현

    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
    import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
    import javax.sql.DataSource;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    
    public class JdbcTemplateMemberRepository implements MemberRepository {
    
        private final JdbcTemplate jdbcTemplate;
    
        public JdbcTemplateMemberRepository(DataSource dataSource) {
            jdbcTemplate = new JdbcTemplate(dataSource);
        }
    
        @Override
        public Member save(Member member) {
    
            /*
                Insert 쿼리를 작성할 필요가 없이 SimpleJdbcInsert를 활용하여,
                테이블 이름과, 키 컬럼을 등록한 후
                Map<String, Object>에 Insert 할 데이터를 담아 매개변수로 넣어주면 됩니다.
             */
            SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
            jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
    
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("name", member.getName());
    
            Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
            member.setId(key.longValue());
    
            return member;
        }
    
        @Override
        public Optional<Member> findById(Long id) {
    
            /*
                jdbcTemplate를 이용해 쿼리와 바인드 매개변수를 날리고,
                결과를 RowMapper를 통해 매핑을 하고
                List로 받아서 Optional로 반환 합니다.
             */
            List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
            return result.stream().findAny();
        }
    
        @Override
        public Optional<Member> findByName(String name) {
            List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
            return result.stream().findAny();
        }
    
        @Override
        public List<Member> findAll() {
            return jdbcTemplate.query("select * from member", memberRowMapper());
        }
    
        private RowMapper<Member> memberRowMapper() {
    
            //기본 형태
            //[Option] + [Enter] 를 이용해 람다로 변경 가능합니다. (Replace with lambda)
    //        return new RowMapper<Member>() {
    //            @Override
    //            public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
    //                Member member = new Member();
    //                member.setId(rs.getLong("id"));
    //                member.setName(rs.getString("name"));
    //                return member;
    //            }
    //        };
    
            //람다 형태
            //템플릿 메서드 패턴과 콜백을 가지고 구현
            return (rs, rowNum) -> {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            };
    
        }
    }

    3. 설정 파일 변경

    • hello/hellospring/SpringConfig.java
    • 스프링 빈 설정 파일
    package hello.hellospring;
    
    import hello.hellospring.repository.*;
    import hello.hellospring.service.MemberService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.persistence.EntityManager;
    import javax.sql.DataSource;
    
    //자바 코드로 직접 스프링 빈 등록하기
    @Configuration
    public class SpringConfig {
    
        //DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다.
        //스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다. 그래서 DI를 받을 수 있다.
        //@Configuration도 스프링이 관리
        //스프링이 설정파일을 보고 알아서 빈을 생성해줍니다.
    
        private final DataSource dataSource;
        
        //DataSource는 스프링 빈이기 때문에 생성자가 하나인 경우 @Autowired 생략 가능합니다.
        public SpringConfig(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Bean
        public MemberService memberService() {
            return new MemberService(memberRepository());
        }
    
        @Bean
        public MemberRepository memberRepository() {
    //        return new MemoryMemberRepository();//구현체
    //        return new JdbcMemberRepository(dataSource);//순수 JDBC
            return new JdbcTemplateMemberRepository(dataSource);// JdbcTemplate
        }
    }