자바로 SQL문을 실행하고 데이터베이스를 조작하려면 JDBC를 통해 DBMS와 연결을 시켜줘야 한다. 이때 JDBC의 API인 커넥션(Connection) 객체를 만들고 클라이언트 > 미들웨어 > DBMS > 미들웨어 > 클라이언트
로 이어지는 연결 과정이 필요하다. 그런데 이 방법은 클라이언트의 요청이 있을 때마다 커넥션 객체를 생성해야 하기 때문에 비효율적이다. 이러한 문제를 해결하기 위해 커넥션 풀(Connection Pool)을 사용한다.
Connection Pool
커넥션 풀은 DBMS로부터 커넥션 해놓은 객체를 풀(pool)에 저장해두고 클라이언트 요청이 오면 커넥션을 빌려주고 작업이 마무리되면 다시 커넥션을 풀에 반납하는 방식으로 작동한다. 클라이언트의 요청이 있을 때마다 DBMS를 찾지 않아도 된다는 의미다. 여기서 풀이란 웅덩이라는 의미가 맞다. 하나의 웅덩이에 객체를 저장하는 그림을 상상하면 된다.
커넥션 풀은 어떻게 사용할까. 커넥션 풀 클래스를 생성하고 해당 커넥션 풀에서 커넥션을 꺼내와 사용하면 된다. 아래는 오라클과 연결한 커넥션 풀 클래스의 소스 코드다. 조금 복잡해보이긴한데, 실무에서 직접 코딩해서 사용하는 것 같지는 않고 이미 있는 소스를 가져다 쓰는 듯 하다.
import java.sql.*;
import java.util.*;
public final class ConnectionPool {
static {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
private ArrayList<Connection> free;
private ArrayList<Connection> used;
private String url;
private String user;
private String password;
private int initialCons = 0;
private int maxCons = 0;
private int numCons = 0;
private static ConnectionPool cp;
// ConnectionPool 객체 리턴
public static ConnectionPool getInstance(String url, String user, String password, int initialCons, int maxCons) {
try {
if (cp == null) {
synchronized (ConnectionPool.class) {
cp = new ConnectionPool(url, user,
password, initialCons, maxCons);
}
}
} catch (SQLException sqle) {
sqle.printStackTrace();
}
return cp;
}
private ConnectionPool(String url, String user, String password, int initialCons, int maxCons) throws SQLException {
this.url = url;
this.user = user;
this.password = password;
this.initialCons = initialCons;
this.maxCons = maxCons;
if (initialCons < 0)
initialCons = 5;
if (maxCons < 0)
maxCons = 10;
free = new ArrayList<Connection>(initialCons);
used = new ArrayList<Connection>(initialCons);
while (numCons < initialCons) {
addConnection();
}
}
private void addConnection() throws SQLException {
free.add(getNewConnection());
}
private Connection getNewConnection() throws SQLException {
Connection con = null;
try {
con = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
++numCons;
return con;
}
public synchronized Connection getConnection() throws SQLException {
if (free.isEmpty()) {
while (numCons < maxCons) {
addConnection();
}
}
Connection _con = free.get(free.size() - 1);
free.remove(_con);
used.add(_con);
return _con;
}
public synchronized void releaseConnection(Connection _con) throws SQLException {
boolean flag = false;
if (used.contains(_con)) {
used.remove(_con);
numCons--;
flag = true;
} else {
throw new SQLException();
}
try {
if (flag) {
free.add(_con);
numCons++;
} else {
_con.close();
}
} catch (SQLException e) {
try {
_con.close();
} catch (SQLException e2) {
e2.printStackTrace();
}
}
}
public void closeAll() {
for (int i = 0; i < used.size(); i++) {
Connection _con = (Connection) used.get(i);
used.remove(i--);
try {
_con.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
for (int i = 0; i < free.size(); i++) {
Connection _con = (Connection) free.get(i);
free.remove(i--);
try {
_con.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
}
public int getMaxCons() {
return maxCons;
}
public int getNumCons() {
return numCons;
}
}
눈에 띄는 특징은 싱글톤(Singleton Pattern) 패턴을 사용한다는 점이다. 코드로 살펴보자.
private ConnectionPool(String url, String user, String password, int initialCons, int maxCons) throws SQLException {
this.url = url;
this.user = user;
this.password = password;
this.initialCons = initialCons;
this.maxCons = maxCons;
if (initialCons < 0)
initialCons = 5;
if (maxCons < 0)
maxCons = 10;
free = new ArrayList<Connection>(initialCons);
used = new ArrayList<Connection>(initialCons);
while (numCons < initialCons) {
addConnection();
}
}
위 코드는 접근제어자를 private으로 지정해 다른 클래스에서 ConnectionPool 생성자에 접근할 수 없게 만든 것이 확인된다. 그대신
public static ConnectionPool getInstance(String url, String user, String password, int initialCons, int maxCons) {
try {
if (cp == null) {
synchronized (ConnectionPool.class) {
cp = new ConnectionPool(url, user,
password, initialCons, maxCons);
}
}
} catch (SQLException sqle) {
sqle.printStackTrace();
}
return cp;
}
위 코드를 통해 주소값을 반환하는 static ConnectionPool 객체를 한 번만 만들고 이를 여러 클래스에서 사용할 수 있게끔 구현됐다. 이러한 방식을 싱글톤 패턴이라고 한다.
사용법
사실상 커넥션 풀의 코드를 직접 작성할 일은 없기 때문에 간단한 사용법 절차만 알아도 활용할 수 있다.
- getInstance로 계정 커넥션
- getConnection 메서드로 커넥션 풀에서 커넥션 꺼내오기
- releaseConnection 메서드로 커넥션 반환하기
- closeAll 메서드로 커넥션 풀 종료
getInstance로 계정 커넥션
ConnectionPool cp = ConnectionPool.getInstance("jdbc:oracle:thin:@localhost:1521:xe",
"c##scott",
"tiger", 5, 10);
// url, id, password, 초기 커넥션수, 최대 커넥션수
getConnection 메서드로 커넥션 풀에서 커넥션 꺼내오기
Connection con = cp.getConnection();
releaseConnection 메서드로 커넥션 반환하기
if (con != null) {
cp.releaseConnection(con);
}
closeAll 메서드로 커넥션 풀 종료
cp.closeAll();
'Database' 카테고리의 다른 글
DATABASE, TABLE, USER 차이와 구분 (0) | 2022.04.23 |
---|---|
HeidiSQL을 사용하는 이유 (0) | 2022.03.26 |
[오라클/ORACLE] JDBC로 DB 연동 | 자바로 SQL문 실행하기 (0) | 2021.06.05 |
[데이터베이스/DB] 트랜잭션 - 기능을 단위로 묶는 이유 (0) | 2021.06.04 |
[오라클/ORACLE] PL/SQL - 트리거(TRIGGER) | 테이블 연결 후 자동화 (0) | 2021.06.03 |