본문 바로가기

Back-end/Java&Spring

Spring JDBC (2) - 커넥션 풀과 데이터 소스

DB 커넥션 획득 과정

 

 

 

 

Bidirectional Database Connection Diagram
Application Layer
DB Driver
DB

 

 

 

DB 커넥션의 과정은 다음 그림과 같이

 

1. 애플리케이션 로직이 DB 드라이버를 통해 커넥션 조회

2. 드라이버가 DB와 TCP/IP 커넥션 연결, 부가정보 전달

3. DB가 인증 후 세션 생성하고 다시 드라이버에 완료 응답

4. 드라이버는 커넥션 객체를 만들어 클라이언트에 반환

 

이렇게 복잡한 과정을 거친다. 

 

이런 과정은 매번 거친다는건 자원적, 시간적으로 매우 손해이기 때문에 각 DB에 맞는 커넥션을 미리 만들어두고 사용하는 커넥션 풀이라는 방법을 고안하게 되었다.

 

 

커넥션 풀

 

 

 

 

Connection Pool Database Diagram
Application Layer
Connection Pool (여러개의 커넥션이 존재)
DB

 

 

애플리케이션을 시작하는 시점에 커넥션을 미리 확보해 커넥션 풀에 저장해놓으면

애플리케이션 로직은 커넥션 풀을 통해서 커넥션을 객체 참조로 가져다 쓰기만 하면 된다!

 

- 커넥션 풀은 또한 서버당 최대 커넥션 수를 제한할 수도 있어 DB 보호의 효과도 있어 이점이 크다.

 

- 대표적인 커넥션 풀 오픈소스로는 HikariCP가 있다. 

 

DataSource

커넥션을 획득하는 방법을 변경할 때 ( ex . DriverManager -> HikariCP ) 커넥션을 획득하는 코드가 변경되기 마련인데, 이를 해결하기 위해선 커넥션을 획득하는 방법을 추상화하는 방법이 있다.

 

DataSource 인터페이스를 통해 추상화 할 수 있다.

 

저번 JDBC(1) 에서 짠 MemberRepository 에 DataSource를 적용해보면

 

package com.example.jdbc.repository;

import com.example.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;

@Slf4j
public class MemberRepositoryV1 {
    private final DataSource dataSource;
    public MemberRepositoryV1(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    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);
    }

    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;
        } finally {
            close(con, pstmt, rs);
        }
    }

    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 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);
        }
    }
    private Connection getConnection() throws SQLException {
        Connection con = dataSource.getConnection();
        log.info("get connection={}, class={}", con, con.getClass());
        return con;
    }
}

 

- 이렇게 DataSource 의존관계를 주입받아 사용하기 때문에
DBConnectionUtil을 사용하지 않아도 된다.

 

- 또한 DataSource 는 표준 인터페이스 이기 때문에 DriverManagerDataSource 에서 HikariDataSource 로 변경되어도 해당 코드를 변경하지 않아도 된다.

 

- JdbcUtils 메서드는 커넥션을 좀 더 편리하게 닫을 수 있게 해준다.

 

출처

김영한 - Spring DB 1

'Back-end > Java&Spring' 카테고리의 다른 글

Spring JPA (2) - JPA 동작 과정  (4) 2024.09.24
Spring JPA (1) - JPA 이해  (0) 2024.09.23
Spring JDBC (4) - 예외 처리  (2) 2024.09.12
Spring JDBC (3) - 트랜잭션  (0) 2024.09.11
Spring JDBC(1) - JDBC 이해  (1) 2024.09.11