-
[김영한 스프링] 08. 커넥션풀과 데이터소스 이해 - DataSource 적용Spring/스프링 DB 1편 - 데이터 접근 핵심 원리 2023. 10. 18. 03:30
DataSource 적용
기존 코드를 유지하기 위해 기존 코드를 복사해서 새로 만들자.
MemberRepositoryV0 -> MemberRepositoryV1
MemberRepositoryV0Test -> MemberRepositoryV1Test
MemberRepositoryV1
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; /** * JDBC - DataSource 사용, JdbcUtils 사용 */ @Slf4j public class MemberRepositoryV1 { private final DataSource dataSource; public MemberRepositoryV1(DataSource dataSource) { this.dataSource = dataSource; } 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); } } 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 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); 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 { throw new NoSuchElementException("member not found memberId=" + memberId); } } catch (SQLException e) { log.error("db error", e); throw e; } } public Member save(Member member) throws SQLException { String sql = "insert into member(member_id, money) values (?, ?)"; Connection con = null; PreparedStatement pstmt = null; try { con = getConnection(); pstmt = con.prepareStatement(sql); 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); } } 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 의존관계 주입
- 외부에서 DataSource를 주입받아서 사용한다. 이제 직접 만든 DBConnectionUtil을 사용하지 않아도 된다.
- DataSource는 표준 인터페이스 이기 때문에 DriverManagerDataSource에서 HikariDataSource로 변경되어도 해당 코드를 변경하지 않아도 된다.
- JdbcUtils 편의 메서드
- 스프링은 JDBC를 편리하게 다룰 수 있는 JdbcUtils라는 편의 메서드를 제공한다.
- JdbcUtils을 사용하면 커넥션을 좀 더 편리하게 닫을 수 있다.
MemberRepositoryV1Test - DriverManagerDataSource 사용
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() { // 기본 DriverManager - 항상 새로운 커넥션을 획득 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 { // save Member member = new Member("memberV0", 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); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
MemberRepositoryV1은 DataSource 의존관계 주입이 필요하다.
로그
DriverManagerDataSource를 사용하면 conn0~5 번호를 통해서 항상 새로운 커넥션이 생성되어서 사용되는 것을 확인할 수 있다.
MemberRepositoryV1Test - HikariDataSource 사용
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() { // 기본 DriverManager - 항상 새로운 커넥션을 획득 // 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 { // save Member member = new Member("memberV0", 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); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
로그
커넥션 풀 사용 시 conn0 커넥션이 재사용된 것을 확인할 수 있다.
테스트는 순서대로 실행되기 때문에 커넥션을 사용하고 다시 돌려주는 것을 반복한다. 따라서 conn0만 사용된다.
웹 애플리케이션에 동시에 여러 요청이 들어오면 여러 쓰레드에서 커넥션 풀의 커넥션을 다양하게 가져가는 상황을 확인할 수 있다.
DI
DriverManagerDataSource -> HikariDataSource로 변경해도 MemberRepositoryV1의 코드는 전혀 변경하지 않아도 된다. MemberRepositoryV1는 DataSource 인터페이스에만 의존하기 때문이다. 이것이 DataSource를 사용하는 장점이다.(DI + OCP)
출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1
'Spring > 스프링 DB 1편 - 데이터 접근 핵심 원리' 카테고리의 다른 글
[김영한 스프링] 10. 트랜잭션 이해 - 트랜잭션 DB 예제 (1) 2023.10.19 [김영한 스프링] 09. 트랜잭션 이해 - 개념 이해 & 데이터베이스 연결 구조와 DB 세션 (1) 2023.10.19 [김영한 스프링] 07. 커넥션풀과 데이터소스 이해 - DataSource 예제 (0) 2023.10.18 [김영한 스프링] 06. 커넥션풀과 데이터소스 이해 - 커넥션 풀, DataSource 이해 (0) 2023.10.18 [김영한 스프링] 05. JDBC 이해 - JDBC 개발 조회, 수정, 삭제 (1) 2023.10.18 - DataSource 의존관계 주입