ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 08. 커넥션풀과 데이터소스 이해 - DataSource 적용
    Spring/스프링 DB 1편 - 데이터 접근 핵심 원리 2023. 10. 18. 03:30

    DataSource 적용

     

    기존 코드를 유지하기 위해 기존 코드를 복사해서 새로 만들자.

    MemberRepositoryV0 -> MemberRepositoryV1

    MemberRepositoryV0Test -> MemberRepositoryV1Test

     

     

    MemberRepositoryV1

    package hello.jdbc.repository;
    
    import hello.jdbc.connection.DBConnectionUtil;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.jdbc.support.JdbcUtils;
    
    import javax.sql.DataSource;
    import java.sql.*;
    import java.util.NoSuchElementException;
    
    /**
     * JDBC - DataSource 사용, JdbcUtils 사용
     */
    @Slf4j
    public class MemberRepositoryV1 {
    
        private final DataSource dataSource;
    
        public MemberRepositoryV1(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public void delete (String memberId) throws SQLException {
    
            String sql = "delete from member where member_id = ?";
    
            Connection con = null;
            PreparedStatement pstmt = null;
    
            try {
                con = getConnection();
                pstmt = con.prepareStatement(sql);
                pstmt.setString(1, memberId);
                pstmt.executeUpdate();
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        public void update (String memberId, int money) throws SQLException {
            String sql = "update member set money = ? where member_id = ?";
    
            Connection con = null;
            PreparedStatement pstmt = null;
    
            try {
                con = getConnection();
                pstmt = con.prepareStatement(sql);
                pstmt.setInt(1, money);
                pstmt.setString(2, memberId);
                int resultSize = pstmt.executeUpdate();
                log.info("resultSize={}", resultSize);
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        public Member findById(String memberId) throws SQLException {
            String sql = "select * from member where member_id = ?";
    
            Connection con = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
    
            try {
                con = getConnection();
                pstmt = con.prepareStatement(sql);
                pstmt.setString(1, memberId);
    
                rs = pstmt.executeQuery();
    
                if (rs.next()) {
                    Member member = new Member();
                    member.setMemberId(rs.getString("member_id"));
                    member.setMoney(rs.getInt("money"));
    
                    return member;
                } else {
                    throw new NoSuchElementException("member not found memberId=" + memberId);
                }
    
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            }
        }
    
        public Member save(Member member) throws SQLException {
            String sql = "insert into member(member_id, money) values (?, ?)";
    
            Connection con = null;
            PreparedStatement pstmt = null;
    
            try {
                con = getConnection();
                pstmt = con.prepareStatement(sql);
                pstmt.setString(1, member.getMemberId());
                pstmt.setInt(2, member.getMoney());
                pstmt.executeUpdate();
    
                return member;
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            } finally {
                close(con, pstmt, null);
            }
        }
    
        private void close(Connection con, Statement stmt, ResultSet rs) {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(stmt);
            JdbcUtils.closeConnection(con);
        }
    
        private Connection getConnection() throws SQLException {
            Connection con = dataSource.getConnection();
            log.info("get connection={}, class={}", con, con.getClass());
            return con;
        }
    }

    • DataSource 의존관계 주입
      • 외부에서 DataSource를 주입받아서 사용한다. 이제 직접 만든 DBConnectionUtil을 사용하지 않아도 된다.
      • DataSource는 표준 인터페이스 이기 때문에 DriverManagerDataSource에서 HikariDataSource로 변경되어도 해당 코드를 변경하지 않아도 된다.
    • JdbcUtils 편의 메서드
      • 스프링은 JDBC를 편리하게 다룰 수 있는 JdbcUtils라는 편의 메서드를 제공한다.
      • JdbcUtils을 사용하면 커넥션을 좀 더 편리하게 닫을 수 있다.

     

     

    MemberRepositoryV1Test - DriverManagerDataSource 사용

    package hello.jdbc.repository;
    
    import com.zaxxer.hikari.HikariDataSource;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import java.sql.SQLException;
    import java.util.NoSuchElementException;
    
    import static hello.jdbc.connection.ConnectionConst.*;
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.assertj.core.api.Assertions.assertThatThrownBy;
    
    @Slf4j
    class MemberRepositoryV1Test {
    
        MemberRepositoryV1 repository;
    
        @BeforeEach
        void beforeEach() {
            // 기본 DriverManager - 항상 새로운 커넥션을 획득
            DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
    
            // 커넥션 풀링
    //        HikariDataSource dataSource = new HikariDataSource();
    //        dataSource.setJdbcUrl(URL);
    //        dataSource.setUsername(USERNAME);
    //        dataSource.setPassword(PASSWORD);
    
            repository = new MemberRepositoryV1(dataSource);
        }
    
        @Test
        void crud() throws SQLException {
            // save
            Member member = new Member("memberV0", 10000);
            repository.save(member);
    
            // findById
            Member findMember = repository.findById(member.getMemberId());
            log.info("findMember={}", findMember);
            assertThat(findMember).isEqualTo(member);
    
            // update : money 10000 -> 20000
            repository.update(member.getMemberId(), 20000);
            Member updatedMember = repository.findById(member.getMemberId());
            assertThat(updatedMember.getMoney()).isEqualTo(20000);
    
            // delete
            repository.delete(member.getMemberId());
            assertThatThrownBy(() -> repository.findById(member.getMemberId()))
                    .isInstanceOf(NoSuchElementException.class);
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    MemberRepositoryV1은 DataSource 의존관계 주입이 필요하다.

     

     

    로그

    DriverManagerDataSource를 사용하면 conn0~5 번호를 통해서 항상 새로운 커넥션이 생성되어서 사용되는 것을 확인할 수 있다.

     

     

    MemberRepositoryV1Test - HikariDataSource 사용

    package hello.jdbc.repository;
    
    import com.zaxxer.hikari.HikariDataSource;
    import hello.jdbc.domain.Member;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import java.sql.SQLException;
    import java.util.NoSuchElementException;
    
    import static hello.jdbc.connection.ConnectionConst.*;
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.assertj.core.api.Assertions.assertThatThrownBy;
    
    @Slf4j
    class MemberRepositoryV1Test {
    
        MemberRepositoryV1 repository;
    
        @BeforeEach
        void beforeEach() {
            // 기본 DriverManager - 항상 새로운 커넥션을 획득
    //        DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
    
            // 커넥션 풀링
            HikariDataSource dataSource = new HikariDataSource();
            dataSource.setJdbcUrl(URL);
            dataSource.setUsername(USERNAME);
            dataSource.setPassword(PASSWORD);
    
            repository = new MemberRepositoryV1(dataSource);
        }
    
        @Test
        void crud() throws SQLException {
            // save
            Member member = new Member("memberV0", 10000);
            repository.save(member);
    
            // findById
            Member findMember = repository.findById(member.getMemberId());
            log.info("findMember={}", findMember);
            assertThat(findMember).isEqualTo(member);
    
            // update : money 10000 -> 20000
            repository.update(member.getMemberId(), 20000);
            Member updatedMember = repository.findById(member.getMemberId());
            assertThat(updatedMember.getMoney()).isEqualTo(20000);
    
            // delete
            repository.delete(member.getMemberId());
            assertThatThrownBy(() -> repository.findById(member.getMemberId()))
                    .isInstanceOf(NoSuchElementException.class);
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

     

     

    로그

    커넥션 풀 사용 시 conn0 커넥션이 재사용된 것을 확인할 수 있다.

    테스트는 순서대로 실행되기 때문에 커넥션을 사용하고 다시 돌려주는 것을 반복한다. 따라서 conn0만 사용된다.

    웹 애플리케이션에 동시에 여러 요청이 들어오면 여러 쓰레드에서 커넥션 풀의 커넥션을 다양하게 가져가는 상황을 확인할 수 있다.

     

    DI

    DriverManagerDataSource -> HikariDataSource로 변경해도 MemberRepositoryV1의 코드는 전혀 변경하지 않아도 된다. MemberRepositoryV1는 DataSource 인터페이스에만 의존하기 때문이다. 이것이 DataSource를 사용하는 장점이다.(DI + OCP)

     

     

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

     

    스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의

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

    www.inflearn.com

Designed by Tistory.