[커넥션 풀]
1. 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회(getConnection() )
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 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환.
커넥션을 새로 만드는 것은 과정도 복잡하고 시간도 많이 많이 소모. DB는 물론이고 애플리케이션 서버에서도 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용 ->느려져
[커넥션 풀 초기화] - 커넥션을 미리 생성해두고 재사용
[DataSource]
커넥션 획득하는 방법
1.DriverManager를 통해서 커넥션 생성해서 얻어오기
2.풀에서 조회
DataSource 는 커넥션을 획득하는 방법을 추상화 하는 인터페이스.
커넥션 풀 의 코드를 직접 의존하는 것이 아니라 DataSource 인터페이스에만 의존하도록 애플리케이션 로직을 작성.
DriverManager 는 DataSource 인터페이스를 사용하지 않는다. 따라서 DriverManager 는 직접 사용.
[DriverManager]
<ConnectionTest> - 드라이버 매니저를 통해서 커넥션 획득
@Slf4j
public class ConnectionTest {
//커넥션을 각각 가져오는지 ->서로 다른 커넥션
@Test
void driverManager() throws SQLException {
Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD); //커넥션 하나를 db랑 얻게됨
Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD); //2개 얻고
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
//spring이 제공하는 datasource가 적용된 deivermanager사용
@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();
Connection con2 = dataSource.getConnection();
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
}
DriverManager 는 커넥션을 획득할 때 마다
DriverManager.getConnection(URL, USERNAME, PASSWORD)
DriverManager.getConnection(URL, USERNAME, PASSWORD)
URL , USERNAME , PASSWORD 같은 파라미터를 계속 전달
DataSource 를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파리미터를 넘겨두고, 커넥션을 획득할 때는 단순히 dataSource.getConnection() 만 호출.DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);여기서 다 세팅함
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
설정과 사용을 분리함!!
[DataSource]
<ConnectionTest 추가> - 데이터소스 커넥션 풀 사용
@Test
void dataSourceConnectionPool() throws SQLException,InterruptedException {
//커넥션 풀링 using히카리
HikariDataSource dataSourcea = new HikariDataSource();
dataSourcea.setJdbcUrl(URL);
dataSourcea.setUsername(USERNAME);
dataSourcea.setPassword(PASSWORD);
dataSourcea.setMaximumPoolSize(10);
dataSourcea.setPoolName("MyPool");
useDataSource(dataSourcea);
Thread.sleep(1000);
}
HikariDataSource dataSource = new HikariDataSource() : HikariDataSource 는 DataSource 인터페이스를 구현.
[DataSource]
<MemberRepositoryV1>
/**
* JDBC - DataSource 사용,JdbcUtils 사용
*/
@Slf4j
public class MemberRepositoryV1 {
//DataSource의존관계 주입
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; //db에 쿼리 날려
try {
con = getConnection(); //드라이버 매니저로 커넥션 획득
pstmt = con.prepareStatement(sql); //sql넘기기, sql예외는 try-catch로
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); //연결 닫기
}
}
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); //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 { //data가 없다
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 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; //커넥션 가져오기
}
}
<MemberRepositoryV1Test>
@Slf4j
class MemberRepositoryV1Test {
MemberRepositoryV1 repository;
@BeforeEach
void beforeEach(){
//기본 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);
repository = new MemberRepositoryV1(dataSource);
repository = new MemberRepositoryV1(dataSource);
}
@Test
void crud() throws Exception{
//save
Member member = new Member("memberV0", 10000);
repository.save(member);
//findById
Member findMember = repository.findById(member.getMemberId());
log.info("findMember={}", findMember);//@Data에서 toString
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);
}
}
커넥션 풀을 사용하면 커넥션을 재사용할 수 있다.
DI + OCP : DriverManagerDataSource HikariDataSource 로 변경해도 MemberRepositoryV1 의 코드는 전혀 변경하지 않아도 됨. MemberRepositoryV1 는 DataSource 인터페이스에만 의존.
'Spring 강의 > DB' 카테고리의 다른 글
[스프링 DB 2편] - (1) 데이터 접근 기술 (0) | 2022.08.28 |
---|---|
[스프링 DB 1편] - (3) 트랜잭션 (0) | 2022.04.20 |
[스프링 DB 1편] - (1) JDBC 이해 (0) | 2022.04.18 |