커넥션 풀
데이터베이스 커넥션을 획득하기 위해서는 DB드라이버가 DB와 TCP/IP커넥션을 연결한다.
DB 드라이버는 TCP/IP 커넥션이 연결되면 ID/PW와 기타 정보를 DB에 전달한다.
그 후 DB 내부인증이 완료되면 DB세션이 생성되고 DB드라이버가 커넥션 객체를 생성하여 클라이언트에 반환한다.
하지만 이러한 방법은 과정도 복잡하고 시간도 오래걸린다.
이러한 문제를 해결해주는게 커넥션 풀이다.
DB 드라이버는 필요한만큼 커넥션을 미리 생성하여 풀에 보관한다.
이때 커넥션들을 모두 TCP/IP로 DB와 연결된 상태이다.
따라서 클라이언트는 필요할때마다 커넥션을 생성하는 것이 아닌, 커넥션 풀에서 커넥션을 객체 참조로 가져다 쓰기만 하면된다.
사용된 커넥션들은 다시 커넥션 풀로 이동하여 다시 사용할 수 있도록 한다.
대표적인 커넥션 풀 오픈소스
commons-dbcp2, tomcat-jdbc pool, hikariCP 등...
DataSource
자바에서는 DataSource라는 커넥션을 획득하는 방법을 추상화 하는 인터페이스가 존재한다.
이 인터페이스의 핵심 기능은 커넥션 조회이다.
DataSource를 사용하면 커넥션 풀을 변경하여도 애플리케이션 코드를 따로 변경할 필요 없이 해당 구현체로 갈아끼우기만 하면 된다.
커넥션 풀 사용해보기
<테스트>
private void useDataSource(DataSource dataSource)throws SQLException{
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
log.info("connection={}, class={}",con1,con2.getClass());
log.info("connection={}, class={}",con2,con2.getClass());
}
@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
//커넥션 풀링: HikariProxyConnection(Proxy) -> JdbcConnection(Target)
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL); //URL 설정
dataSource.setUsername(USERNAME); //USERNAME 설정
dataSource.setPassword(PASSWORD); //PASSWORD 설정
dataSource.setMaximumPoolSize(10); //커넥션 풀 최대 사이즈 지정
dataSource.setPoolName("MyPool"); //커넥션 풀 이름 설정
useDataSource(dataSource);
Thread.sleep(1000); //커넥션 풀에서 커넥션 생성 시간 대기
}
커넥션 풀에서 커넥션을 생성하는 작업은 별도의 쓰레드에서 작동한다.
왜냐하면 커넥션을 채우는것은 상대적으로 시간이 오래 걸리기 때문이다.
만약 한 쓰레드에서 실행된다면 애플리케이션을 실행할때 마다 커넥션풀을 다 채울때 까지 대기해야 한다.
DataSource 적용
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;
@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) throws SQLException {
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;
}
}
<테스트 코드>
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(){
//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 {
Member member=new Member("memberV4",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);
}
}
테스트를 실행하고 로그를 보면 conn0가 계속 재사용 되는것을 확인할 수 있는데,
이는 커넥션을 사용하고 다시 커넥션 풀에 돌려주는 것을 반복하기 때문이다.
18:56:54.666 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@503595296 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
18:56:54.689 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@175595853 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
18:56:54.696 [Test worker] INFO h.j.r.MemberRepositoryV1Test --
findMember=Member(memberId=memberV4, money=10000)
18:56:54.732 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@259077766 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
18:56:54.733 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
resultSize=1
18:56:54.734 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@1962398162 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
18:56:54.737 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@98412281 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
18:56:54.739 [Test worker] INFO h.jdbc.repository.MemberRepositoryV1 --
get connection=HikariProxyConnection@748975217 wrapping conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class com.zaxxer.hikari.pool.HikariProxyConnection
'BackEnd > Database' 카테고리의 다른 글
스프링 예외 추상화 (0) | 2025.02.04 |
---|---|
TransactionTemplate (0) | 2025.02.01 |
DB 락 (0) | 2025.01.19 |
데이터베이스 연결, JDBC개발 - 등록, 조회, 수정 ,삭제 (0) | 2025.01.12 |
JDBC란 무엇인가 (0) | 2025.01.11 |