JDBC와 커넥션 풀, 그리고 데이터소스

Hanjmo·2023년 7월 31일
0

JDBC

JDBC(Java DataBase Connectivity)는 데이터베이스에 접속할 수 있도록 해주는 자바 API다.

🔎 등장 배경

중요한 데이터를 데이터베이스에 저장하기 위해 애플리케이션 서버가 데이터베이스를 사용하는 방식은 다음과 같다.

  1. 커넥션을 연결한다. (주로 TCP/IP를 사용)
  2. 연결된 커넥션을 통해 SQL을 데이터베이스에 전달한다.
  3. 전달한 SQL에 따른 결과를 응답받는다.

하지만 수십개가 넘는 각각의 DB마다 커넥션 연결, SQL 전달, 결과를 응답받는 방식이 모두 다르기 때문에 크게 2가지 문제가 발생한다.

  1. DB를 다른 종류의 DB로 변경하면 코드도 함께 변경해야 한다.
  2. 개발자는 각각의 DB마다 사용법을 새로 학습해야 한다.

이 문제를 해결하기 위해서 자바에서 JDBC 표준 인터페이스를 만들었고, 각각의 DB 벤더에서 자신의 DB에 맞게 JDBC 인터페이스를 구현해서 라이브러리로 제공하기 시작했다. 여기서 제공되는 라이브러리를 JDBC 드라이버라고 한다.

🔎 JDBC 예제

String sql = "select * from member where member_id = ?";

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

JDBC를 사용하기 전에 SQL문을 정의해놓고, Connection, PreparedStatement, ResultSet을 선언한다.
(try-catch문 안에서 커넥션을 연결해야 하기 때문에 미리 선언해서 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("error", e);
	throw e;
} finally {
	close(con, pstmt, rs);
}

getConnection()을 통해 커넥션을 획득하고, 정의해놓은 SQL문을 DB에 전달한다.
전달한 SQL문에 대한 응답인 ResultSet을 받아와서 Member 객체를 생성 후 반환한다.

여기서 Connection, PrepareStatement, ResultSet 모두 사용 후 종료해야 하기 때문에 finally 부분에서 close()를 작성한다.

커넥션을 종료하는 close() 메서드는 다음과 같이 작성되어 있다.

private void close(Connection con, Statement stmt, ResultSet rs) {
	if (rs != null) {
		try {
			rs.close();
		} catch (SQLException e) {
			log.error("error", e);
		}
	}

	if (stmt != null) {
		try {
			stmt.close();
		} catch (SQLException e) {
			log.error("error", e);
		}
	}
    
    if (con != null) {
		try {
			con.close();
		} catch (SQLException e) {
			log.error("error", e);
		}
	}
}

🔎 커넥션 획득에서 발생하는 문제

위에서 보듯 데이터베이스 커넥션을 획득하는 과정은 꽤 복잡하고, 시간도 오래 걸린다. 특히 사용자가 애플리케이션을 사용할 때 SQL 실행 시간뿐만 아니라, 커넥션을 새로 생성하는 시간도 추가되어 응답 속도가 느려진다.

이러한 문제를 해결하고자 커넥션 풀이 등장하게 되었다.

커넥션 풀

커넥션 풀(Connection Pool)은 말 그대로 커넥션을 관리하는 풀로, 다음과 같은 방식으로 동작한다.

  1. 애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼의 커넥션을 생성해서 보관한다.
  2. 커넥션 풀에 들어 있는 커넥션은 DB와 TCP/IP로 연결되어 있기 때문에 언제든지 SQL을 즉시 전달할 수 있다.

커넥션 풀 덕분에 애플리케이션 로직에서 원할 때마다 JDBC 드라이버가 아닌 커넥션 풀에서 커넥션을 획득할 수 있다.
여기서 주의할 점은 커넥션을 사용하고나서 종료하는 것이 아니라, 커넥션 풀에 다시 반환한다는 것이다.

데이터소스

커넥션을 얻기 위해서 JDBC DriverManager를 사용하거나 커넥션 풀을 사용하는 등 다양한 방법이 존재한다.

만약 커넥션 획득 방법을 변경하려 한다면, 코드 자체도 변경해야 한다는 불편함이 존재한다. 이를 해결하고자 커넥션 획득 방법을 DataSource라는 인터페이스로 추상화한 것이다.

🔎 데이터소스 예제

// 기본 DriverManager - 항상 새로운 커넥션을 획득
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);

// 커넥션 풀링
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);

// dataSource 주입
repository = new MemberRepositoryV1(dataSource);

DataSource를 만들고 URL, USERNAME, PASSWORD를 입력하는 부분인 설정DataSourcegetConnection()을 호출하는 부분인 사용을 분리함으로써 향후 변경에 더 유연하게 대처할 수 있다.

0개의 댓글