스프링 DB 1 정리 - 2. 커넥션풀과 데이터소스 이해 (22.8.18)

피아노과 개발자도전?·2022년 8월 18일
0

Today I learned

목록 보기
37/75
post-custom-banner

김영한 개발자님의 스프링 DB 1편 강의를 수강하고 정리한 내용입니다.

2. 커넥션풀과 데이터소스 이해


2.1. 커넥션 풀 이해

데이터베이스 커넥션을 획득할 때는 다음과 같은 복잡한 과정을 거친다.

  1. 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회한다.
  2. DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. 물론 이 과정에서 3 way handshake 같은 TCP/IP 연결을 위한 네트워크 동작이 발생한다.
  3. DB 드라이버는 TCP/IP 커넥션이 연결되면 ID, PW와 기타 부가정보를 DB에 전달한다.
  4. DB는 ID, PW를 통해 내부 인증을 완료하고, 내부에 DB 세션을 생성한다.
  5. DB는 커넥션 생성이 완료되었다는 응답을 보낸다.
  6. DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.

커넥션을 새로 만드는 것은 과정도 복잡하고 시간도 많이 많이 소모되는데, 커넥션 풀은 이런 복잡한 과정을 해결한다.

  • 애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 커넥션을 미리 확보해서 풀에 보관한다.

  • 애플리케이션은 커넥션 풀을 통해 이미 생성되어 있는 커넥션을 객체 참조로 그냥 가져다 쓰기만 하면 된다.

  • 사용후에는 커넥션을 종료하는 것이 아니라, 다음에 다시 사용할 수 있도록 해당 커넥션을 그대로 커넥션 풀에 반환한다.

  • 실무에서는 대부분 커넥션 풀 오픈소스인 hikariCP 를 사용한다.

2.2. DataSource 이해

애플리케이션 로직에서 DriverManager 를 사용해서 커넥션을 획득하다가 HikariCP 같은 커넥션 풀을 사용하도록 변경하면 애플리케이션 코드도 함께 변경해야 한다.

DataSource 는 커넥션을 획득하는 방법을 추상화 하는 인터페이스이다. 핵심 기능은 커넥션 조회 하나이다.

스프링은 DriverManagerDataSource 를 통해서 사용할 수 있도록 DriverManagerDataSource 라는 DataSource 를 구현한 클래스를 제공한다.

DriverManager 를 사용하다가 커넥션 풀을 사용하도록 코드를 변경해도 애플리케이션 로직은 변경하지 않아도 된다.

2.3. DataSource 예제1 - DriverManager

커넥션을 획득하는 2가지 방법

@Slf4j
public class ConnectionTest {
	@Test // DriverManager 를 통해서 커넥션을 획득
	void driverManager() throws SQLException {
		Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
	}

	@Test // DataSource 가 적용된 DriverManager 인 DriverManagerDataSource 를 사용
	void dataSourceDriverManager() throws SQLException {
 	//DriverManagerDataSource - 항상 새로운 커넥션 획득
    // 처음 객체를 생성할 때만 필요한 파리미터를 넘김
	DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
	useDataSource(dataSource);
	}
    
	private void useDataSource(DataSource dataSource) throws SQLException {
    // DataSource 를 사용하는 방식 
	Connection con1 = dataSource.getConnection();
	Connection con2 = dataSource.getConnection();
	}
}

설정과 사용의 분리

설정 : DataSource 를 만들고 필요한 속성들을 사용해서 URL , USERNAME , PASSWORD 같은 부분을 입력하는 것을 말한다. 이렇게 설정과 관련된 속성들은 한 곳에 있는 것이 향후 변경에 더 유연하게 대처할 수 있다.

사용 : 설정은 신경쓰지 않고, DataSourcegetConnection() 만 호출해서 사용하면 된다.

필요한 데이터를 DataSource 가 만들어지는 시점에 미리 다 넣어두게 되면, DataSource 를 사용하는 곳에서는 dataSource.getConnection() 만 호출하면 되므로, URL , USERNAME , PASSWORD 같은 속성들에 의존하지 않아도 된다.


2.4. DataSource 예제2 - 커넥션 풀

@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
 //커넥션 풀링: 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); //커넥션 풀에서 커넥션 생성 시간 대기
}

커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션 실행 속도에 영향을 주지 않기 위해 별도의 쓰레드에서 작동한다. 별도의 쓰레드에서 동작하기 때문에 테스트가 먼저 종료되어 버린다.

예제처럼 Thread.sleep 을 통해 대기 시간을 주어야 쓰레드 풀에 커넥션이 생성되는 로그를 확인할 수 있다.

실행 결과

#커넥션 풀 초기화 정보 출력
HikariConfig - MyPool - configuration:
HikariConfig - maximumPoolSize................................10
HikariConfig - poolName................................"MyPool"

#커넥션 풀 전용 쓰레드가 커넥션 풀에 커넥션을 10개 채움
[MyPool connection adder] MyPool - Added connection conn0: url=jdbc:h2:.. 
user=SA
[MyPool connection adder] MyPool - Added connection conn1: url=jdbc:h2:.. 
user=SA
[MyPool connection adder] MyPool - Added connection conn2: url=jdbc:h2:.. 
user=SA

...
[MyPool connection adder] MyPool - Added connection conn9: url=jdbc:h2:.. 
user=SA

#커넥션 풀에서 커넥션 획득1
ConnectionTest - connection=HikariProxyConnection@446445803 wrapping conn0: 
url=jdbc:h2:tcp://localhost/~/test user=SA, class=class 
com.zaxxer.hikari.pool.HikariProxyConnection

#커넥션 풀에서 커넥션 획득2
ConnectionTest - connection=HikariProxyConnection@832292933 wrapping conn1: 
url=jdbc:h2:tcp://localhost/~/test user=SA, class=class 
com.zaxxer.hikari.pool.HikariProxyConnection

#커넥션 풀에서 커넥션을 2개 획득하고 반환하지는 않아 풀에 있는 10개의 커넥션 중에 2개를 가지고 있는 상태
MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)

2.5. DataSource 적용

애플리케이션에 DataSource 를 적용해보자

@Slf4j
public class MemberRepositoryV1 {
	private final DataSource dataSource;
    
	public MemberRepositoryV1(DataSource dataSource) {
		this.dataSource = dataSource;
 	}
 
 //save()...
 //findById()...
 //update()....
 //delete()....
 
	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 를 주입 받아서 사용한다. 이제 직접 만든 DBConnectionUtil 을 사용하지 않아도 된다.

  • 스프링은 JDBC를 편리하게 다룰 수 있는 JdbcUtils 라는 편의 메서드를 제공한다.

profile
공부한 내용 정리
post-custom-banner

0개의 댓글