들어가며

  • 저번 포스트에서 DataSource 인터페이스를 사용하여 커넥션을 얻는 것에 대해 알아보았다.
  • 이번 포스트에서는 배운 방식을 레포지토리에 적용하여 어떻게 변경되는지 알아보자.

 

 

 

RepositoryV1 구현

  • 커넥션을 얻는 부분에서 많은 변경이 발생된다.
public class MemberRepositoryV1{

    private final DataSource dataSource;
    
    public MemberRepositoryV1(DataSource dataSource){
        this.dataSource = dataSource;
    }

    public Member save(Member member) throws SQLException{
        ...
    }
    
    public Member findById(String memberId) throws SQLException{
        ...
    }
    
    public void update(String memberId, int money) throws SQLExceptions{
        ...
    }
    
    public void delete(String memberId, int money) throws SQLExceptions{
        ...
    }

    public void close(Connection conn, Statement stmt, ResultSet rs){
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        JdbcUtils.closeConnection(conn);
    }
    
    private Connection getConnection(){
        return dataSource.getConnection();  
    }

}
  • 데이터를 저장, 조회, 수정, 삭제를 하는 부분은 변경이 발생하지 않는다. getConnection()메서드를 통해 커넥션을 얻는 구조이기 때문에 getConnection() 메서드의 내용을 수정해주면 되기 때문이다.
  • 보면 dataSource를 DI를 통해 주입받는 것을 확인할 수 있다.
    • DI 덕분에 만약 해당 레포지토리를 사용하려 할 때 구현체가 변경되더라도 구현체를 꽂아주는 설정 파일만 수정하면 해당 레포지토리 코드를 수정할 필요가 사라진다.(DI + OCP)
    • 만약 스프링을 사용한다고 가정한다면 다음과 같이 구현해주면 된다.
@Repository
@RequiredArgsConstructor
public class MemberRepositoryV1{

    private final DataSource dataSource;

    public Member save(Member member) throws SQLException{
        ...
    }
    
    public Member findById(String memberId) throws SQLException{
        ...
    }
    
    public void update(String memberId, int money) throws SQLExceptions{
        ...
    }
    
    public void delete(String memberId, int money) throws SQLExceptions{
        ...
    }

    public void close(Connection conn, Statement stmt, ResultSet rs){
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        JdbcUtils.closeConnection(conn);
    }
    
    private Connection getConnection(){
        return dataSource.getConnection();  
    }

}
@Configuration
public class AppConfiguration{

    @Bean
    public DataSource hikariDataSource(){
        return new HikariDataSource();
    }
    
    ...
}
  • 컴포넌트 스캔을 사용한다면 클래스로 만들어 @Component를 통해서도 스프링 프레임워크의 DI 기능을 사용할 수 있을 것이다.
  • close() 메서드를 유심히 보자 JdbcUtils라는 객체를 통해 연결을 닫는데 사용되는 많은 코드들을 매우 짧게 줄일 수 있었다. JdbcUtils 클래스는 JDBC를 편리하게 다룰 수 있도록 여러 편의 메서드를 제공하는데 그것들을 사용한 것이다.

 

 

 

레포지토리 테스트

class MemberRepositoryV0Test{
    MemberRepositoryV1 memberRepository;
    
    @BeforeEach
    void beforeEach() throws Exception{
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
        
        memberRepository = new MemberRepositoryV1(dataSource);
        
        Thread.sleep(1000);
    }
    
    @Test
    void crud() throws SQLException{
        Member member = new Member("memberA", 10000);
        
        repository.save(member);
        
        Member memberA = repository.findById("memberA");
        Assertions.assertThat(memberA).isEqualTo(member);
        
        repository.update(member.getMemberId(), 12000);
        
        memberA = repository.findById("memberA");
        
        Assertions.assertThat(memberA).isEqualTo(member);
        Assertions.assertThat(memberA.getMoney()).isEqualTo(12000);
        
        repository.delete("memberA");
        Assertions.assertThatThrownBy(() -> repository.findById("memberA"))
                .isInstanceOf(NoSuchElementException.class);
        
    }
}
  • @BeforeEach 어노테이션은 테스트 프레임워크에서 테스트를 수행하기 전에 수행되는 메서드임을 뜻한다. 즉 crud 메서드가 실행되기 전에 HikariDataSource객체가 생성되고, 초기화된 뒤 memberRepository로 주입이 수행된다.
  • 로그를 사용하여 확인해보면 커넥션 풀을 사용하여 데이터를 삽입, 조회, 수정, 삭제를 하고 있음을 알 수 있다. 다만 현재는 요청이 동시다발적으로 들어오지 않아 하나의 커넥션만 사용되고 있음을 유의해야 한다.
복사했습니다!