[Spring] 스프링 DB 1편 - 데이터 접근 핵심 원리 섹션2 커넥션풀과 데이터소스 이해
◼️ 커넥션 풀 이해
🟢DB 커넥션 획득 과정
- 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회
- DB 드라이버는 DB와 TCP/IP 커넥션을 연결.(이 과정에서 3 way handshake 같은 TCP/IP 연결 을 위한 네트워크 동작 발생.)
- DB 드라이버는 TCP/IP 커넥션이 연결되면 ID, PW와 기타 부가정보를 DB에 전달
- DB는 ID, PW를 통해 내부 인증을 완료하고, 내부에 DB 세션을 생성
- DB는 커넥션 생성이 완료되었다는 응답을 보낸다.
- DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환
😥커넥션을 새로 만드는 것은 과정도 복잡하고 시간이 많이 소모된다.
😉이런 문제를 해결하기 위해 커넥션을 미리 생성해두고 사용하는 커넥션 풀이라는 방법을 쓴다.
🟢커넥션 풀
- 적절한 커넥션 풀 숫자는 서비스의 특징과 애플리케이션 서버 스펙, DB 서버 스펙에 따라 다르기 때문에 성능 테스트를 통해서 정한다.
- 커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있다. 따라서 DB에 무한정 연결이 생성되는 것을 막아주어서 DB를 보호하는 효과도 있다.
- 대표적인 커넥션 풀 오픈소스는 commons-dbcp2 , tomcat-jdbc pool , HikariCP 등이 있다.
- 성능과 사용의 편리함 측면에서 최근에는 hikariCP 를 주로 사용한다. 스프링 부트 2.0 부터는 기본 커넥션 풀로 hikariCP 를 제공한다. 성능, 사용의 편리함, 안전성 측면에서 이미 검증이 되었기 때문에 커넥션 풀을 사용할 때는 고민할 것 없이 hikariCP 를 사용하면 된다.
◼️ DataSource 이해
커넥션을 획득하는 방법은 다양하다. JDBC DriverManager 를 직접 사용하거나, 커넥션 풀을 사용하는 방법등이 있다.
😥애플리케이션 로직에서 DriverManager → HikariCP 같은 커넥 션 풀을 사용하도록 변경하면 커넥션을 획득하는 애플리케이션 코드도 함께 변경해야 한다.
😉 자바에서는 이런 문제 해결을 위해 javax.sql.DataSource 라는 인터페이스를 제공한다.
DataSource 는 커넥션을 획득하는 방법을 추상화 하는 인터페이스이다.
public interface DataSource { //이 인터페이스의 핵심 기능은 커넥션 조회 하나이다.
Connection getConnection() throws SQLException;
}
대부분 커넥션 풀은 DataSource 인터페이스를 구현했다. 하지만 DriverManager는 DataSource 인터페이스를 사용하지않는다.
스프링은 DriverManager 도 DataSource 를 통해 사용할 수 있도록 DriverManagerDataSource 라는 DataSource 를 구현한 클래스를 제공한다.
◼️ DataSource 예제1 - DriverManager
🔶기존 DriverManager 를 통한 커넥션 획득
@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={}", con1, con2.getClass());
}
🔶데이터소스 드라이버 매니저 사용
@Test
void dataSourceDriverManager() throws SQLException{
//DriverManagerDatasource - 항상 새로운 커넥션을 획득
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
useDataSource(dataSource);
}
private void useDataSource(DataSource dataSource) throws SQLException{
Connection con1 = dataSource.getConnection(); // dataSource 사용할때는 설정정보 안 넣어도됨!
Connection con2 = dataSource.getConnection();
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
DriverManager는 커넥션을 획득할때마다 설정정보 파라미터를 전달해야했지만, DataSource는 처음 객체를 생성할때만 필요 파라미터를 넘겨주면되었다.이렇게 객체의 설정과 사용을 분리하는 것이 좋다.
◼️ 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 을 통해 대기 시간을 주어야 쓰레드 풀에 커넥션이 생성되는 로그를 확인할 수 있다.
}
커넥션 풀에서 커넥션을 생성하는 작업은 별도의 쓰레드에서 작동한다. 커넥션 풀에 커넥션을 채우는 것은 오래걸린다. 따라서 이렇게 별도의 쓰레드를 사용해서 커넥션 풀을 채워야 애플리케이션 실행 시간에 영향을 주지 않는다.
스프링부트 3.1이상에서는 기본 로그 레벨을 INFO 로 빠르게 설정하기 때문에 상세 로그를 확인할 수 없는데, 아래 파일을 만들어넣으면 기본 로그 레벨을 DEBUG 로 설정해서 강의 내용과 같이 로그를 확인할 수 있음.
//src/main/resources/logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
◼️ DataSource 적용
/**
* JDBC - DataSource, JdbcUtils 사용
*/
@Slf4j
public class MemberRepositoryV1 {
// 외부에서 DataSource 주입받아 사용. 구현체 변경되어도 코드 변경 X
private final DataSource dataSource;
public MemberRepositoryV1(DataSource dataSource) {
this.dataSource = dataSource;
}
//save()...
//findById()...
//update()....
//delete()....
// JdbcUtils; 스프링은 JDBC를 편리하게 다룰수있는 메서드 제공
// JdbcUtils 을 사용하면 커넥션을 좀 더 편리하게 닫을 수 있다.
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;
}
@Slf4j
class MemberRepositoryV1Test {
MemberRepositoryV1 repository;
@BeforeEach
void beforeEach() throws Exception {
//기본 DriverManager - 항상 새로운 커넥션 획득
//DriverManagerDataSource dataSource =
// new DriverManagerDataSource(URL, USERNAME, PASSWORD);
//커넥션 풀링: HikariProxyConnection -> JdbcConnection
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}
@Test
void crud() throws SQLException, InterruptedException {...
DriverManagerDataSource→HikariDataSource 로 변경해도 MemberRepositoryV1 의 코드는 전혀 변경하지 않아도 된다. MemberRepositoryV1 는 DataSource 인터페이스에만 의존하기 때문이다. 이것이 DataSource 를 사용하는 장점이다.
'Spring' 카테고리의 다른 글
[Spring] 스프링 DB 1편 - 데이터 접근 핵심 원리 섹션4 스프링과 문제 해결 - 트랜잭션 (0) | 2024.06.05 |
---|---|
[Spring] 스프링 DB 1편 - 데이터 접근 핵심 원리 섹션3 트랜잭션 이해 (0) | 2024.06.02 |
[Spring] 스프링 DB 1편 - 데이터 접근 핵심 원리 섹션1 JDBC 이해 (0) | 2024.05.28 |
[Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 섹션11 스프링 파일 업로드 (0) | 2024.05.21 |
[Spring] Spring Type Conversion 문서 정리 및 요약 (0) | 2024.05.18 |