Search
Duplicate
📒

[Database Study] 04-3. 커넥션 풀(DBCP), DataSource

상태
미진행
수업
Database Study
주제
JDBC
4 more properties
참고

커넥션 풀

NOTE
JDBC를 사용한다면 데이터베이스에 접근할 때 마다 매번 Connecion을 얻어와야하고, 아래와 같은 불필요한 Connecion 과정을 거쳐야 한다.
1.
어플리케이션 로직은 DB 드라이버를 통해 Connection을 조회한다
2.
DB 드라이버는 DB와 TCP/IP Connection을 연결한다. 물론 이과정에서 3 way handshake와 같은 TCP/IP 연결을 위한 네트워크 동작이 발생한다
3.
DB 드라이버는 TCP/IP 커넥션이 연결되면 ID, PW와 같은 부가 정보를 전달한다
4.
DB는 ID, PW를 통해 내부 인증을 완료하고, 내부에 DB Session을 생성한다
5.
DB는 Connecion 생성이 완료되었다고 응답을 보낸다
6.
DB 드라이버는 Connecion객체를 생성해서 클라이언트에게 반환한다.
커넥션의 문제점
데이터베이스에 접근할 떄마다, Connecion 생성/삭제 과정이 추가된다면, 서비스에서 ”SQL 처리시간 + Connecion 생성시간”이 추가되기 떄문에 응답속도에 영향을 준다
이로인해 Connecion Pool이 등장한다!

커넥션 풀

NOTE
Connecion Pool ⇒ 이름 그대로 Connecion을 관리하는 pool(수영장)이다!
Connecion Poll 초기화 및 연결 상태
커넥션 풀의 내부에는 연결되어 있는 커넥션이 존재한다.
1.
어플리케이션을 시작하는 시점에 Connecion Pool은 필요한 만큼의 Connecion을 확보 Connecion Pool에서 보관한다
2.
보통 Connecion의 개수는, 서비스의 특징과 스펙에 따라 다르지만 Default는 10개이다
3.
Connecion Pool에 들어있는 Connecion은 TCP/IP로 DB와 Connecion이 연결되어 있는상태이기 때문에 언제든지 SQL을 DB에 전달할 수 있다.
Connecion Pool 사용과정 1
애플리케이션 로직에서 이제는 DB 드라이버를 통해서 새로운 Connecion을 얻는게 아니다.
이제는 Connecion Pool에서 이미 생성되어 있는 Connecion을 객체 참조로 가져다 사용한다.
Connecion PoolConnecion을 요청하면 Connecion Pool이 가지고 있는 Connecion중 하나를 반환해준다.
Connecion Pool 사용과정 2
애플리케이션 로직은 Connecion Pool에서 받은 Connecion을 사용해서 SQL을 데이터베이스에 전달하고 그 결과를 받아 처리한다.
Connecion을 모두 사용하고 나면 Connecion을 종료하는 것이 아니라, 다음에 다시 사용할 수 있도록 해당 Connecion을 그대로 Connection Pool에 반환하면 된다.
여기서 주의할 점은 Connecion을 종료할때 살아있는 상태로 반환한다는 것이다.
정리
적절한 Connection Pool의 숫자는 서비스와 서버에 따라 다르기 때문에 성능 테스트를 통해서 정해야한다
Connection Pool은 서버당 최대 Connecion개수를 제한할 수 있다 이는 무한정으로 연결이 생성되는 것을 막아주어서, DB를 보호하는 효과가 있다
Connection Pool 사용이점이 매우 크기 떄문에 실무에서 항상 사용한다
Connection Pool을 직접 구현할 수도 있지만, 뛰어난 오픈소스가 많기에 오픈소스를 권장한다
대표적으로 commons-dbcp2 , tomcat-jdbc pool , HikariCP 등이 있는데 HikariCp만 기억하면 된다 (대부분 HikariCP를 쓰기 때문)

DataSource 이해

NOTE
Connecion을 얻는 방법은 JDBC의 DriverManager를 사용하거나, Connection Pool을 사용하는 등 다양한 방법이 존재한다
DriveManger를 통해 Connecion 얻기
HikariCP를 통해서 얻으려고 변경할려고함 → 코드변경이 필요해짐..
위 그림과 같이 DriveManger를 통해 Connection을 얻가나 HikariCP같은 Connecion Pool을 사용하도록 변경하면 Connection을 획득하는 코드도 변경되어야 한다
Connection을 얻는 방법을 추상화해서 해결한다!

커넥션을 획득하는 방법 추상화 (DataSource)

NOTE
DataSource ⇒ Connection을 얻는 방법을 추상화하는 인터페이스이다!
JDBC처럼 DataSource로 추상화시킴
public interface DataSource { Connection getConnection() throws SQLException; }
Java
복사
getConnection을 통한 추상화 인터페이스
DataSource 핵심 기능만 축약
[정리]
대부분의 Connection Pool DataSource 인터페이스를 이미 구현해두었다. 따라서 개발자는 DataSource에 의존하도록 로직을 작성하면 된다
DriverManagerDataSource 인터페이스를 사용하지 않는다.
따라서 DriverManager는 직접 사용해야한다.
DriverManager를 사용하다가 DataSource 기반의 Connection Pool을 사용하도록 변경하면 코드를 수정해야 한다, ⇒ 이러한 문제 해결을 위해 스프링은 DriverManagerDataSource를 사용하기위해 DriverManagerDataSource라는 DataSource를 구현한 클래스를 제공한다.

DataSource 예제

DriverManager

NOTE
@Slf4j public class ConnectionTest { @Test void driverManager() throws SQLException { Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD); Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD); log.info("connection={}, class={}", con1, con1.getClass()); log.info("connection={}, class={}", con2, con2.getClass()); } }
Java
복사
기존에 개발했던 DriveManager를 통해서 Connection 획득 코드
@Test void dataSourceDriverManager() throws SQLException { // DriverManagerDataSource - 항상 새로운 Connecion 획득 DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD); useDataSource(dataSource); } private void useDataSource(DataSource dataSource) throws SQLException{ Connection con1 = dataSource.getConnection(); // Pool 1개 획득 Connection con2 = dataSource.getConnection(); // 획득 Pool 1개를 획득 (대기?) log.info("connecion={}, class={}", con1, con1.getClass()); log.info("connecion={}, class={}", con2, con2.getClass()); }
Java
복사
스프링이 제공하는 DriverManagerDataSource 코드
기존의 코드와 비슷하지만 DataSource를 통해서 Connection을 얻어올수 있다.
DataSource 와 DriveManger 방법의 차이
파라미터 차이
DriveMangerConnection을 얻을 때 마다 URL, USERNAME, PASSWORD 와 같은 설정 정보를 넘겨줘야 했다
DataSource간단하게 호출만 해주면된다
설정과 사용의 분리
이런 분리를 통해 유지보수와, 사용성이 증가된다.

Connection Pool(hikariCP)

NOTE
@Test void dataSourceConnectionPool() throws SQLException, InterruptedException { // HikariDataSource 설정 // 커넥션 풀링: HikariProxyConnection(Proxy) -> JdbcConnection(Target) HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setMaximumPoolSize(10); dataSource.setPoolName("MyPool"); useDataSource(dataSource); Thread.sleep(1000); }
Java
복사
Connection Pool 사용코드
Connection Pool의 최대 사이즈를 10으로 지정하고 이름은 MyPool로 지정했다
Connection Pool에서 Connection을 생성하는 작업은 실행속도에 영향을 주지 않기 위해 별도의 쓰레드에서 진행된다.

DataSourcr 적용

Repository에 DataSource 적용

NOTE
/** * JDBC - DataSource 사용, JdbcUtils 사용 */ public MemberRepositoryV1(DataSource dataSource) { this.dataSource = dataSource; } private void close(Connection con, Statement stmt, ResultSet resultSet) { //JdbcUtils 에서 제공하는 close 메소드 사용 JdbcUtils.closeResultSet(resultSet); .closeStatement(stmt); .closeConnection(con); } private Connection getConnection() throws SQLException { Connection connection = dataSource.getConnection(); log.info("get connection={}, clas={}", connection, connection.getClass()); return connection; }
JavaScript
복사
Repository DataSource 적용코드
DataSource를 사용해서 Connection을 받는다
JdbcUtils를 이용해서 조금 더 편리하게 Connection을 닫을 수 있음

TEST

NOTE
@Slf4j class MemberRepositoryV1Test { MemberRepositoryV1 repositoryV1; @BeforeEach void beforeEach(){ //기본 DriverManager - 항상 새로운 커넥션 회득 //DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD); //이러면 속도가 너무 느리니, 커넥션 풀을 이용 //커넥션 풀링 HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); repositoryV1 = new MemberRepositoryV1(dataSource); } @Test void crud() throws SQLException, InterruptedException { //save Member memberV0 = new Member("memberV0", 100000); repositoryV1.save(memberV0); //findById Member findMember = repositoryV1.findById(memberV0.getMemberId()); log.info("findMember = {}", findMember); //2개는 다른 인스턴스지만, isEqualTo 가 equals() 를 호출해 값만 비교 하기 때문에 true //@Data 롬복은, 모든 상태 값을 비교할수 있는 equals 와 고유한 hashCode 를 자동으로 만들어준다. assertThat(findMember).isEqualTo(memberV0); //update int updateMoney = 200000; repositoryV1.update(memberV0.getMemberId(), updateMoney); Member updateMember = repositoryV1.findById(memberV0.getMemberId()); assertThat(updateMember.getMoney()).isEqualTo(updateMoney); //delete repositoryV1.delete(memberV0.getMemberId()); assertThatThrownBy(() -> repositoryV1.findById(memberV0.getMemberId())) .isInstanceOf(NoSuchElementException.class); //커넥션 풀 채우는 거 볼려고 Thread.sleep(1000); } }
Java
복사
Test 코드 (beforeEach에서 DataSource를 주입해줌)