스프링 DB 정리.ZIP
JDBC 이해
JDBC 등장 전
개발자가 애플리케이션을 개발할 때 중요한 데이터들은 데이터베이스에 저장하게된다.
- 클라이언트(Web, App)가 애플리케이션 서버에 데이터를 조회하거나 데이터를 저장한다.
- 애플리케이션 서버는 데이터 베이스와 커넥션을 연결한다. (TCP/IP를 사용하여 커넥션을 연결한다)
- 애플리케이션 서버는 데이터베이스가 이해할 수 있는 SQL 문을 연결된 커넥션을 통해 DB로 전달한다.
- 데이터베이스는 전달된 SQL문을 수행하고 그 결과를 응답하여 애플리케이션 서버에 전달한다.
데이터베이스의 변경
만약 개발자의 요청으로 데이터베이스가 변경된다면 어떻게 될까?
각각의 데이터베이스는 커넥션을 연결, SQL 전달, 결과를 응답하는 방법이 모두 다르다.
여기에 큰 2가지 문제점이 존재한다.
- 데이터베이스를 다른 종류로 변경하면 애플리케이션 서버에 개발된 데이터베이스 관련 코드도 모두 변경해야한다.
- 각각의 데이터베이스마다 커넥션 연결, SQL 전달, 결과를 응답받는 방법을 새로 학습해야한다.
이러한 문제를 해결하기 위해 JDBC 라는 자바 표준이 등장하였다.
-> 각기 다른 데이터베이스의 상호작용 하는방법을 통일된 방법으로 제공한다.
JDBC 표준 인터페이스
JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접속할 수 있는 자바 API이다.
JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.
JDBC는 다음 3가지 기능을 표준 인터페이스로 정의하여 제공한다.
java.sql.Connection
: 연결java.sql.Statement
: SQL을 담은 내용java.sql.ResultSet
: SQL 요청 응답
JDBC 드라이버란 무엇일까?
- JDBC 인터페이스를 각각의 DB 회사에서 자신의 DB에 맞도록 구현하여 라이브러리로 제공하는 것
- MYSQL DB에 접근할 수 있는 라이브러리를 MYSQL JDBC 드라이버라고 한다.
- Oracle DB에 접근할 수 있는 라이브러리를 Oracle JDBC 드라이버라고 한다.
MYSQL 드라이버 사용
Oracle 드라이버 사용
JDBC 등장으로 앞서 이야기했던 2가지 문제점이 해결되었다.
- 데이터베이스를 다른 종류로 변경하면 애플리케이션 서버에 개발된 데이터베이스 관련 코드도 모두 변경해야한다.
- 애플리케이션 로직은 JDBC 표준 인터페이스에만 의존하게 되어 다른 종류의 데이터베이스로 변경하여도 JDBC 드라이버만 변경하기만 하면된다. 즉 다른 종류의 데이터베이스를 사용해도 애플리케이션의 코드를 변경하지 않아도 된다.
- 각각의 데이터베이스마다 커넥션 연결, SQL 전달, 결과를 응답받는 방법을 새로 학습해야한다.
- 개발자는 JDBC 표준 인터페이스 사용법만 학습하면 된다.
데이터베이스 연결
DriverManager
데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection()
메서드를 사용하면 된다.
위 메서드는 라이브러리에 있는 데이터베이스 드라이버를 찾아 해당 드라이버가 제공하는 커넥션을 반환해준다.
DriverManager
는 라이브러리에 등록된 드라이버를 관리하고 커넥션을 흭득하는 기능을 제공한다.
DriverManager 동작과정
- 애플리케이션 로직에서 커넥션이 필요하면
DriaverManager.getConnection()
메서드를 호출한다. - DriverManager은 라이브러리에 등록된 드라이브 목록을 자동으로 인식한다.
- 이 드라이버들에게 순서대로 정보를 전달하여 커넥션을 흭득할 수 있는지 확인한다.
- URL : 예) jdbc:h2:tcp//localhost/~/test
- USERNAME, PASSWORD 등 데이터베이스 접속에 필요한 정보를 전달한다.
- 각각의 드라이버는 전달된 정보를 확인하여 본인이 처리할 수 있는지 확인한다.
- 위 순서로 진행되며 찾은 커넥션 구현체를 클라이언트에게 반환한다.
JDBC는 java.sql.Connection
이라는 표준 커넥션 인터페이스를 정의하였고
각각의 데이터베이스 회사는JDBC Connection 인터페이스를 구현한 Connection 구현체를 제공하기 때문에 위같은 순서로 동작할 수 있다.
DriverManager을 이용하여 커넥션을 흭득하는 소스코드
@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;
}
}
DriverManager을 이용하여 커넥션을 흭득하는 테스트코드
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
인터페이스를 구현하고 있다.
데이터베이스 변경 MYSQL -> H2
이전에 DriverManger는 라이브러리에 등록된 드라이버를 관리하고 커넥션을 흭득하는 기능을 제공한다고 이야기 하였다. 그렇다면 라이브러리에 H2드라이버를 추가하고 드라이버에게 전달하는 정보를 수정하면 간단하게 데이터베이스를 변경할 수 있을거라 생각하였다.
build.gradle
파일에 H2 데이터베이스 드라이버를 추가하였다.
드라이버 정보를 다르게 하여 H2 커넥션 흭득하는 소스코드
@Test
void driverManagerTest_1() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");
System.out.println("connection = "+connection);
System.out.println("connectionClass = " + connection.getClass());
assertThat(connection).isNotNull();
}
커넥션 풀
데이터베이스 커넥션을 흭득 할 때는 다음과 같은 과정을 거친다.
- 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회한다.
- DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. 이 과정에서 3 way handshake 같은 네트워크 동작 발생한다.
- DB 드라이버는 TCP/IP 커넥션이 연결되면, ID, PW와 기타 부가정보를 DB에 전달한다.
- DB는 ID, PW를 통해 내부 인증을 완료하고 내부에 DB 세션을 생성한다.
- DB 커넥션 생성이 완료했다는 응답을 보낸다.
- DB 드라이버는 커넥션 객체를 생성하여 클라이언트에 반환한다.
이렇게 커넥션을 새로 만들게 되면 과정도 복잡하고 시간이 많이 소요된다. 애플리케이션 서버에서도 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용해야한다. 고객이 애플리케이션을 사용할때 SQL을 실행하는 시간 뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 때문에 결과적으로 응답속도에 영향을 준다.
이러한 문제를 해결하기 위해 커넥션 풀이라는 방법을 사용한다.
커넥션 풀이란 데이터베이스 커넥션을 미리 생성하고 풀에 보관해두는 기술이다. 애플리케이션이 데이터베이스와 연결이 필요할때마다 이 풀에서 커넥션을 사용하고 사용이 끝난 연결은 다시 풀에 반환하는 방식으로 동작한다.
커넥션 풀 초기화
- 애플리케이션이 시작하는 시점에 커넥션 풀은 필요한만큼 커넥션을 미리 확보한다. -> 보통 기본값은 10개이다.
커넥션 풀의 연결상태
- 커넥션 풀에 들어 있는 커넥션은 TCP/IP로 DB와 커넥션이 연결된 상태이다. -> 즉시 SQL을 DB에 전달가능하다.
커넥션 풀 사용 (1)
- 더 이상 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 흭득하지 않고 커넥션 풀에게 요청하여 커넥션을 흭득한다.
- 이미 커넥션 풀에 생성되어있는 커넥션을 객체 참조로 가져다 쓴다.
- 커넥션 풀은 커넥션을 요청하면 자신이 가지고 있는 커넥션 중 하나를 반환해준다.
커넥션 풀 사용 (2)
- 애플리케이션 로직은 커넥션 풀에서 받은 커넥션을 사용하여 SQL문을 DB에 전달한다.
- 커넥션을 모두 사용하고 나면 종료하는것이 아니라 커넥션 풀에 반환한다.
DataSource
커넥션을 얻는 방법으로는 DriverManager
를 사용하거나 커넥션 풀을 사용하는 등의 여러 방법이 존재하였다.
만약 DriverManager를 통해 커넥션을 흭득하다 커넥션 풀을 이용하여 커넥션을 이용하여 커넥션을 흭득하고 싶다면?
- 커넥션을 흭득하는 애플리케이션 코드를 변경해야되는 문제가 발생한다.
- 커넥션을 흭득하는 코드의 사용법 달라진다.
이러한 문제를 해결하기 위해 커넥션을 흭득하는 방법을 추상화하여 문제를 해결하였다.
- 자바에서 이러한 문제를 해결하기 위해
javax.sql.DataSource
라는 인터페이스를 제공한다. DataSource
는 커넥션을 흭득하는 방법을 추상화한 인터페이스 이다.- 이 인터페이스의 핵심 기능은 커넥션 조회하나이다.
DataSource 사용
DataSource를 사용하여 커넥션을 얻어오는 소스코드
@Test
void dataSourceTest() throws SQLException {
DataSource dataSource = new DriverManagerDataSource("jdbc:h2:tcp://localhost/~/test", "sa", "");
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
System.out.println("connection = "+ con1);
System.out.println("connectionClass = " + con1.getClass());
System.out.println("connection = "+ con2);
System.out.println("connectionClass = " + con2.getClass());
}
- DataSource를 사용하면 처음 객체를 생성할때만 파라미터로 정보를 넘겨준다.
- 커넥션을 흭득할때 단순히
dataSource.getConnection()
메서드를 사용하면 된다. - 설정과 사용을 분리하여 유연한 코드를 작성할 수 있다.
- 설정 : DataSource 객체 생성
- 사용 :
dataSource.getConnection()
메서드로 커넥션 흭득
커넥션 변경 DriverManager -> HikariCP
HikariCP 커넥션 풀을 사용하는 소스코드
@Test
void dataSourceTest_1() throws SQLException {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:h2:tcp://localhost/~/test");
dataSource.setUsername("sa");
dataSource.setPassword("");
dataSource.setMaximumPoolSize(10); // default -> 10개
dataSource.setPoolName("myPool");
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
System.out.println("connection = "+ con1);
System.out.println("connectionClass = " + con1.getClass());
System.out.println("connection = "+ con2);
System.out.println("connectionClass = " + con2.getClass());
}
- DriverManager를 사용하는 코드에서 HikariCP로 변경하면 단순히 설정부분을 변경하면 된다.
- Repository 에서는 DataSource를 의존하고 커넥션을 얻는 부분에서는
dataSource.getConnection()
사용한다. - Datasource를 외부에서 주입받아 DI + OCP를 지킬 수 있다.
댓글남기기