서론
오늘은 김영한님의 스프링 DB강의를 들으면서 공부한 내용을 정리해보았다.
JDBC
JDBC의 개념은 위키백과에 다음과 같이 나와있다. JDBC(Java Databse Connectivity)는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API이다. JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.
일반적인 웹 어플리케이션은 회원 정보, 상품 정보 등 여러가지 데이터를 메모리에 저장하지 않는다. 메모리에 데이터를 저장하게 되면 휘발성 메모리 특성상, 애플리케이션이 종료되거나 서버가 재부팅되면 데이터가 사라지기 때문이다. 따라서 데이터를 영구적으로 저장하고 관리하기 데이터베이스를 사용한다. 이때 자바 기반의 애플리케이션에서는 JDBC를 사용하여 데이터베이스와 연결하고 데이터베이스와 상호작용할 수 있도록 도와준다.
즉, JDBC는 자바 기반의 어플리케이션에서 데이터베이스와 연결하고 상호작용할 수 있도록 도와주는 API이다.
JDBC 등장 전
JDBC 등장 전에는 애플리케이션 서버가 데이터베이스와 직접 커넥션을 연결하고 SQL 쿼리를 전달하는 과정을 수동으로 처리하여야 했다. 이 과정에서 개발자는 각 데이터베이스와 맞는 드라이버를 설정하고 SQL 쿼리를 작성하고 실행하였고, 결과를 처리하는 과정을 모두 수동으로 구현하여야 했다.
- 클라이언트(Web, App)가 애플리케이션 서버에 데이터를 조회하거나 데이터를 저장한다.
- 애플리케이션 서버는 데이터베이스와 커넥션을 연결한다. (TCP/IP 를 통하여 커넥션을 연결한다.)
- 애플리케이션 서버는 데이터베이스가 이해할 수 있는 SQL문을 연결된 커넥션을 통해 DB로 전달한다.
- 데이터베이스는 전달된 SQL문을 수행하고 그 결과를 다시 애플리케이션 서버에 전달해준다.
- 애플리케이션 서버는 다시 클라이언트에게 요청 결과를 반환해준다.
🚨 문제 발생 - 데이터베이스 변경
기존에 사용하던 데이터베이스가 문제가 생겨 다른 데이터베이스로 변경해야되는 상황이다. 이렇게 JDBC 사용이전에 데이터베이스를 변경해야 한다면 어떤 문제가 생길까?
먼저, 기존 데이터베이스와 연결을 위해 작성한 코드가 특정한 데이터베이스에 종속되어 있기 때문에, 새로운 데이터베이스에 맞는 커넥션 설정을 다시 해야 하며, 이를 위해 커넥션 URL, 사용자 인증 정보, 드라이버 설정 등을 변경해주어야 한다. 또한 데이터베이스마다 SQL문법이 다를 수 있기 때문에 기존에 작성한 SQL 쿼리문도 다시 작성해주어야 한다는 문제가 발생한다.
JDBC 등장 후
이러한 문제를 해결하기 위해서 JDBC라는 자바 표준이 등장하였다. JDBC는 각기 다른 데이터베이스의 연결부터 데이터베이스와 상호작용하는 방식을 표준화하여, 개발자가 데이터베이스마다 다른 커넥션 설정이나 SQL 문법 차이 등을 처리할 필요없이 일관된 방식으로 데이터베이스와 상호작용할 수 있도록 도와준다. 즉, MYSQL, Oracle, H2와 같은 다양한 데이터베이스들은 JDBC 표준 인터페이스를 구현하고 제공하며, 개발자는 이 JDBC 표준 인터페이스를 사용해 여러 데이터베이스를 동일한 방식으로 사용할 수 있도록 해준다.
📚 JDBC 드라이버
JDBC 드라이버는 자바 애플리케이션과 데이터베이스 간의 연결을 가능하게 해주는 소프트웨어 구성 요소이다. 각 데이터베이스 회사들은 JDBC 표준 인터페이스를 구현하여 드라이버를 제공한다. 쉽게 말하여 JDBC 인터페이스를 각각의 DB 회사에서 자신의 DB에 맞도록 구현하여 라이브러리로 제공하는것이라고 생각할 수 있다.
예를 들어, MySQL을 자바에서 사용할 때는 mysql-connector-java 드라이버를 사용한다. 이 드라이버를 프로젝트에 추가하면, 자바 애플리케이션이 JDBC를 통해 MySQL 데이터베이스에 연결하고, SQL 쿼리를 실행할 수 있게된다.
💻 JDBC 주요 인터페이스
- java.sql.connection : 데이터베이스와 연결을 관리하는 인터페이스이다. 이 인터페이스를 통해 데이터베이스와 연결할 수 있게된다.
- java.sql.Statement : SQL 문을 실행하는 인터페이스이다. 이 인터페이스는 SQL 쿼리를 담고 실행하는 역할을 하며, 이를 통해 데이터베이스에 명령을 보낼 수 있다.
- java.sql.ResultSet : SQL 쿼리의 실행 결과를 담는 인터페이스이다. 데이터베이스에서 쿼리한 결과를 반환받아 처리할 수 있게된다.
즉, 각 DB 회사들은 이 주요 인터페이스를 구현하여 JDBC 드라이버를 제공한다. 이를 통해 개발자는 다양한 데이터베이스를 JDBC를 사용해 일관된 방식으로 쉽게 연결하고 사용할 수 있게 된다. 즉, JDBC 덕분에 데이터베이스 종류에 관계없이 자바 애플리케이션에서 동일한 코드로 다양한 DBMS와 상호작용 할 수 있다.
데이터베이스 연결
자바 애플리케이션에서 자바 데이터베이스와 연결할려면 JDBC가 제공하는 DriverManager.getConnection() 메서드를 사용하면 된다. 이 메서드를 호출하면 프로젝트에 추가된 라이브러리에서 데이터베이스 드라이버를 찾아 해당 드라이버가 제공하는 커넥션을 반환한다. 이를 통해 애플리케이션은 데이터베이스와 연결할 수 있다.
Gradle 빌드 도구를 사용하여 H2 데이터베이스 의존성을 추가하면, 외부 라이브러리에 h2database 라이브러리가 포함됩니다. 이 라이브러리에는 H2 드라이버가 포함되어 있어, DriverManager.getConnection() 메서드를 호출하면 해당 드라이버를 자동으로 찾아 연결을 설정한다.
💡 DriverManager 동작과정
- 애플리케이션 로직에서 커넥션이 필요하면 DriaverManager.getConnection() 메서드를 호출하게 된다.
- DriverManager는 라이브러리에 등록된 드라이버 목록을 자동으로 인식한다.
- 각 드라이버에게 데이터베이스 접속 정보를 전달하여 해당 드라이버가 커넥션을 획득할 수 있는지 확인한다.
- 이때 전달되는 정보는 URL, 사용자명, 비밀번호 등 데이터베이스 접속에 필요한 정보이다.
- 각각의 드라이버는 전달된 정보를 확인하여 본인이 처리할 수 있는지 확인한다.
- 처리할 수 있는 드라이버를 찾으면, 해당 드라이버가 커넥션을 생성하고 이를 클라이언트에게 반환한다.
JDBC는 java.sql.Connection 이라는 표준 커넥션 인터페이스를 정의해두었고 각각의 데이터베이스 회사들은 이 인터페이스를 구현한Connection 구현체를 제공하기 때문에 위와 같이 동작할 수 있다.
⚙️ DriverManager를 사용하여 H2 데이터베이스 커넥션 흭득하기
@Slf4j
public class DBConnectionUtil {
private String url = "jdbc:h2:tcp://localhost/~/test";
private String username = "sa";
private String password = "";
public Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, username, password);
log.info("con = {} class = {}", connection, connection.getClass());
return connection;
}
}
이 코드를 실행한 후 콘솔에 출력된 로그를 보면, 커넥션이 정상적으로 반환된 것을 확인할 수 있다. 로그에는 커넥션 객체와 그 클래스 정보가 출력되며, 이를 통해 데이터베이스와의 연결이 성공적으로 이루어진 것을 확인할 수 있다. 이제 데이터베이스를 Oracle 데이터베이스로 변경해보자.
⚙️ DriverManager를 사용하여 Oracle 데이터베이스 커넥션 흭득하기
이전에 DriverManager는 라이브러리에 등록된 드라이버를 관리하고 해당 드라이버를 통해 데이터베이스와의 커넥션을 흭득한다고 하였다. 따라서 현재 프로젝트에 Oracle 데이터베이스와 연결하려며 Oracle JDBC 드라이버를 라이브러리에 추가해주어한다.
dependencies {
implementation 'com.oracle.database.jdbc:ojdbc8'
}
@Slf4j
public class DBConnectionUtil {
private String url = "jdbc:oracle:thin:@localhost:1521/xe";
private String username = "sys as sysdba";
private String password = "1234";
public Connection getConnection() throws SQLException {
Connection con = DriverManager.getConnection(url, username, password);
log.info("con = {} class = {}", con, con.getClass());
return con;
}
}
class DBConnectionUtilTest {
@Test
void driverManagerTest() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/xe"
,"sys as sysdba"
, "1234");
assertThat(connection).isNotNull();
}
}
테스트 결과를 확인해보면 connectionClass = class oracle.jdbc.driver.T4CConnectio 를 확인할 수 있다. 이 부분이 바로 Oracle 데이터베이스가 제공하는 Oracle 전용 커넥션이다. 이 커넥션은 JDBC 표준 커넥션 인터페이스인 java.sql.Connection 인터페이스를 구현하고 있다.
🏊♀️ 커넥션 풀
커넥션 풀을 이해하기 이전에 이전과 방법으로 데이터베이스에서 커넥션을 흭득하면 어떤 문제가 발생하는지 알아보자. 먼저 커넥션 풀을 사용 이전에 데이터베이스에서 커넥션을 얻어오는 방법이다.
- 애플리케이션 로직은 DriverMnager.getConnection() 메서드를 통해 DB 드라이버를 통해 커넥션을 조회한다.
- DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. 이 과정에서 3 way handshake 같은 네트워크 동작 발생한다.
- DB 드라이버는 TCP/IP 커넥션이 연결되면, ID, PW와 기타 부가정보를 DB에 전달한다.
- DB는 ID, PW를 통해 내부 인증을 완료하고 내부에 DB 세션을 생성한다.
- DB 커넥션 생성이 완료했다는 응답을 보낸다.
- DB 드라이버는 커넥션 객체를 생성하여 클라이언트에 반환한다.
이 방법을 사용하면 클라이언트가 요청할 때마다 매번 새로운 커넥션을 획득하기 위해 TCP/IP 연결, 인증, 세션 생성 등의 작업이 반복된다. 이로 인해 커넥션 생성 시간이 추가되어 성능이 저하될 수 있다. 따라서 클라이언트가 애플리케이션을 사용할 때, SQL 실행 외에도 커넥션 생성 시간이 더해지므로, 결과적으로 응답 시간이 길어지게 된다.
위와 같은 문제를 어떻게 해결할 수 있을까? 단순하게 미리 커넥션을 여러 개 만들어 두고, 사용할 때 꺼내 쓰고, 사용하지 않을 때는 보관하는 방식으로 해결할 수 있을것이다. 이 방식이 바로 커넥션 풀의 개념으로, 데이터베이스 커넥션을 미리 생성하고 풀에 보관해두는 기술이다. 애플리케이션이 데이터베이스와 연결이 필요할때마다 이 풀에서 커넥션을 사용하고 사용이 끝난 연결은 다시 풀에 반환하는 방식으로 동작한다.
💡 커넥션 풀 동작과정
- 애플리케이션이 시작될 때, 커넥션 풀은 설정된 개수만큼 미리 데이터베이스 커넥션을 생성하여 풀에 저장한다.
- 예를 들어, 초기 5개의 커넥션을 생성하도록 설정하였다면, 5개의 커넥션이 생성된다.
- 보통 기본값으로 10개가 생성되도록 설정되어있다.
- 클라이언트가 데이터베이스에 연결하도록 할 때, DriveManager를 통해 커넥션을 흭득하지 않고 커넥션 풀에게 요청한다.
- 커넥션 풀은 자신이 가지고 있는 커넥션 중 하나를 반환해준다.
- 사용 가능한 커넥션이 없으면, 커넥션 풀은 설정된 최대 커넥션 수에 따라 추가 커넥션을 생성하거나 대기할 수 있다.
- 클라이언트는 데이터베이스와 상호작용하며 SQL 쿼리 등을 실행하고, 작업이 완료되면 커넥션을 닫지 않고 반환한다.
- 커넥션을 닫는 대신, 다시 풀에 반환하여 다른 요청에서 재사용될 수 있도록 한다.
🎓 DataSource
JDBC에서 커넥션을 얻는 방법은 DriverManager를 사용하거나 커넥션 풀을 사용하는 방식 등이 존재한다.
DriverManager를 통해 커넥션을 획득하다가 커넥션 풀로 변경하면 애플리케이션 코드가 달라지는 문제가 발생한다. 이를 해결하기 위해, javax.sql.DataSource 인터페이스가 제공된다. DataSource는 커넥션을 획득하는 방법을 추상화하여, 설정과 사용을 분리하고 유연한 코드 작성이 가능하게 한다.
DataSource를 사용하면 설정 시 DataSource 객체를 생성하고, 사용 시 단순히 dataSource.getConnection() 메서드로 커넥션을 획득한다.
HikariCP를 사용하는 경우에도 DriverManager를 사용하던 코드를 쉽게 변경할 수 있으며, DataSource를 의존성 주입받아 OCP 원칙을 지킬 수 있다.
'TIL' 카테고리의 다른 글
[TIL - 2024-09-20] @Transaction 정리 (0) | 2024.09.20 |
---|---|
[TIL - 2024-09-19]트랜잭션 정리 (0) | 2024.09.19 |
[TIL - 2024-09-12] 스프링 테스트, 단위 테스트, 통합 테스트 (0) | 2024.09.12 |
[TIL - 2024-09-11] 싱글톤 패턴, 스프링 빈 생명주기 콜백, 스프링 빈 스코프, DL, Proxy (0) | 2024.09.11 |
[TIL - 2024-09-10] 제어의 역전, 의존성 주입, 스프링 컨테이너 (0) | 2024.09.10 |