Programming/Database

데이터베이스 연동 원리 JDBC, DataSource, Connection Pool

Jan92 2022. 1. 30. 01:42

JDBC, Connection Pool, DataSoucre 개념과 원리 살펴보기

각각의 WAS에서 돌아가는 여러 개의 프로젝트들이 하나의 DB에 있는 데이터를 사용할 때 'Dirty Read', 'Non Repeatable Read', 'Phantom Read' 같은 문제점이 발생할 수 있다는 것을 알게 되고, 문제를 해결하기 위한 여러 가지 방법을 찾아보면서 Spring Boot에서 DB와 연결되어 데이터를 주고받는 동작 원리에 대한 이해가 부족하다고 느껴서 정리하게 된 내용입니다.

 

 


 

먼저 JDBC

JDBC는 Java Database Connectivity의 약자로 스프링 프레임워크를 사용하기 전에 일반 Java 언어에서 DB 연결을 위해 사용하는 것으로 자바와 데이터베이스를 연결하기 위한 Java 표준 SQL 인터페이스를 말합니다.

MySQL, MariaDB, PostgreSQL, SQL Server 등 다양항 DB 미들웨어 드라이버를 제공하며 Java 표준이기 때문에 JVM 위에서 운영되는 애플리케이션이라면 어디든지 사용할 수 있다는 장점이 있습니다.

 

JDBC가 없다면 애플리케이션에서 연결할 DB에 따른 설정 방식이 각각 다 다를 것입니다. 하지만 JDBC를 사용하기 때문에 DBMS의 종류(MySQL, MariaDB, Oracle 등)에 상관없이 JDBC를 통해 같은 방식으로 연결할 수 있게 됩니다.

 

 

JDBC의 동작 원리

Connection conn = null;
PreparedStatement  pstmt = null;
ResultSet rs = null;

try {
    sql = "SELECT * FROM T_BOARD"

    // 1. 드라이버 연결 DB 커넥션 객체를 얻음
    connection = DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD);

    // 2. 쿼리 수행을 위한 PreparedStatement 객체 생성
    pstmt = conn.createStatement();

    // 3. executeQuery: 쿼리 실행 후
    // ResultSet: DB 레코드 ResultSet에 객체에 담김
    rs = pstmt.executeQuery(sql);
    } catch (Exception e) {
    } finally {
        conn.close();
        pstmt.close();
        rs.close();
    }
}

JDBC의 동작 원리를 살펴보겠습니다. 먼저 사용할 Database Driver를 선택하여 Connection을 생성합니다.

(DriverManager 클래스의 getConnection() 메서드)

그리고 쿼리 수행을 위한 PreparedStatement 객체를 생성합니다. executeQuery() 메서드를 통해 쿼리를 실행 후 ResultSet으로 결과를 받는 경우가 기본적인 JDBC를 이용하는 방법입니다.

 

 

***

그러나 코드에서 볼 수 있는 것처럼 Connection, PreparedStatement, ResultSet 객체들을 사용하면 close() 메소드를 통해 닫아주어야 메모리 누수가 생기지 않고, 안전하게 종료할 수 있습니다.

이 문제를 좀 더 생각해보면 사용자가 많은 서비스에서 데이터베이스 연결이 있을 때마다 이와 같은 연결, 해제를 반복해야 하는데 이것은 번거롭고 효율성이 떨어지는 과정이 될 것입니다.

이러한 문제를 해결하기 위해서 사용하는 것이 바로 'Connection Pool'입니다.

 

 

 


 

Connection Pool

풀(Pool) 속에 데이터베이스와 연결할 연결 객체(Connection)를 미리 여러 개 만들어 둡니다. 데이터베이스에 접근 요청이 왔을 때 그중 하나의 연결 객체(Connection)를 할당하여 사용하고, 사용이 종료되면 연결 객체를 반납하는 방식입니다.

 

'DataBase Connection Pool, DBCP'라고도 하며, 위에서 언급한 것처럼 다수의 사용자가 이용하는 웹 애플리케이션의 경우 데이터베이스에 접근해야 하는 요청이 많이 발생할 것인데 그때마다 연결 객체를 만들고 해제하는 과정을 반복하게 되는 것은 비효율적입니다.

따라서 Connection Pool을 이용하여 미리 여러 개의 Connection을 만들어놓고, 요청 시 미리 만들어 놓은 Connection을 할당하고, 반납받는 형식을 통해 효과적으로 DB 연결 및 자원을 관리할 수 있게 됩니다.

 

커넥션 풀의 종류로는 HikariCP, tomcat-jdbc-pool, commons-dbcp 등이 있으며, Spring Boot 2.0 전에는 tomcat-jdbc-pool을 default로 사용하였으나, 2.0 이후에는 HikariCP가 default로 사용되고 있습니다.

(org.springframework.boot:spring-boot-starter-jdbc dependency를 추가함으로써 HikariCP, spring-jdbc를 사용할 수 있습니다.)

 

 

***

커넥션을 계속해서 재사용하기 때문에 생성되는 최대 커넥션 수를 제한적으로 설정하며, 커넥션 풀에 사용 가능한 커넥션이 없을 경우 사용자는 커넥션이 반환될 때까지 순서대로 대기합니다.

(이때 지정한 TimeOut 시간이 만료되면 예외를 던집니다.)

 

***

Connection Pool의 성능적인 부분을 고려할 때는 Thread와 함께 고려해야 합니다.

예를 들어서 Thread Pool의 크기보다 Connection Pool의 크기가 더 클 경우 트랜잭션을 처리하는 Thread가 사용하는 Connection 외에 남는 Connection은 실질적으로 메모리 공간만 차지하게 되기 때문입니다.

 

 

 


 

Configure a DataSource

DataSource는 DB Driver 연결, Connection 객체를 관리하는 역할을 하는 인터페이스입니다.

DBSource는 DB와 관계된 Connection 정보를 담고 있으며, Bean으로 등록하여 인자로 넘겨주는데 Spring은 이러한 과정을 통해 DataSource로부터 DB와의 연결을 획득합니다.

 

Java에서 javax.sql.DataSource 인터페이스를 사용하기 위해서는 구현체를 선택해야 하는데, spring-boot-starter-jdbc 또는 spring-boot-starter-data-jpa를 추가하면 Spring Boot 2.0 이후 버전부터는 DataSource 관리를 위한 구현체로 위에서 언급한 것처럼 HikariCP를 제공됩니다.

(DataSource의 기본적인 정의 자체는 JDBC 명세에 포함되어 있지만 실제 구현체는 다를 수 있습니다.)

 

 

HikariDataSource

***

Spring Boot의 AutoConfiture를 통해 xml 또는 properties, yml 파일의 spring.datasource.* 에 대한 설정이 DataSourceProperties에 전달되며, 이는 DataSource의 디폴트 구현체 클래스에 자동으로 설정됩니다.

(@EnableAutoConfituration 어노테이션의 기능, 해당 어노테이션은 @SpringBootApplication안에 같이 포함되어 있습니다.)

 

별도의 DataSourceConfig class를 통해 DataSource를 구현하여 Bean으로 등록했다면 xml, properties, yml 내의 spring.datasource.* 속성은 적용되지 않습니다.

 

 

DataSource 설정

Connection conn = null;

try {
	// dataSource는 생성자나 설정 메서드를 이용해서 주입받습니다.
	conn = dataSource.getConnection();
	...

JDBC API는 DriverManager 외에 DataSource를 이용하여 DB 연결을 구하는 방법을 정의하고 있습니다.

DataSource를 사용하면 위와 같은 방식으로 Conneciton을 구현합니다.

 

 

 


 

Spring JDBC

Spring에서는 기존의 JDBC를 사용할 때 Connection, PreparedStatement, ResultSet 등의 자주 사용하는 객체와 코드들을 클래스 화하여 스프링 애플리케이션에서 보다 편리하게 DB에 접근할 수 있는 인터페이스를 제공합니다.

 

Spring JDBC의 역할은 다음과 같습니다.

  • Connection을 열고 닫기
  • Statement를 준비하고 닫기
  • Statement의 실행
  • ResultSet의 반복 처리
  • 예외 처리 반환
  • Transaction 처리

 

JDBC, DataSource, Spring JDBC

즉, JDBC 사용을 단순화하고 일반적인 오류를 방지하는데 도움을 주는 인터페이스이며, 스프링에서 제공하는 SQL 연산들을 수행할 수 있도록 해주는 JDBC용 기본 템플릿이라고 할 수 있습니다.

(이 인터페이스의 구현체 중 하나가 JdbcTemplate입니다.)