Last Modified : 2012.02.24

Connection Pooling

자바 프로그램에서 데이터베이스에 연결을 시도하는 것(Connection 객체를 얻는것)은 시간이 많이 걸리는 작업이다.
만약, 일정량의 Connection 을 미리 생성시켜 저장소에 저장했다가 프로그램에서 요청이 있으면 저장소에서 Connection 꺼내 제공한다면 시간을 절약할 수 있을 것이다.
이러한 프로그래밍 기법을 Connection Pooling 이라 한다.

여기서 소개할 Connection Pooling 코드는 커넥션을 미리 생성하여 저장소에 저장한다.
요청이 있으면 저장소에 Connection 을 꺼내어 사용한다.
작업이 완료되었으면 close()메소드를 써서 자원을 반납하지 않고 사용한 Connection을 다시 다시 저장소로 저장한다.

1. 전체 클래스 요약

Log.java
로그 파일에 로그 메시지를 입력하기 위한 클래스

DBConnectionPool.java
특정 데이터베이스에 대한 커넥션 객체를 풀로 관리하는 클래스

DBConnectionPoolManager.java
DBConnectionPool 객체들을 관리하는 클래스

ConnectionManager.java
DBConnectionPoolManager 클래스에게 커넥션을 요청하는 추상클래스
추상 클래스로 만든 이유는 여러 데이터베이스를 고려했기 때문이다.
사용하는 데이터베이스에 따라 이 클래스를 상속하는 클래스를 만들어 사용한다.

OracleConnectionManager.java
오라클용 커넥션을 얻기위해 ConnectionManager 클래스를 상속한 클래스

oracle.properties
오라클용 커넥션풀 설정 파일
oracle.properties 에서 "oracle" 이란 문자열은 오라클용 커넥션풀 객체를 구별하는 이름으로 사용된다.
설정 내용을 자바 코드에 구현하는 것보다는 파일로 관리하기는 것이 재사용과 유지보수에 유리하다.

2. Connection Pooling 관련 소스

Log.java

package net.java_school.util;

import java.io.*;
import java.util.Date;

public class Log {
	public String logFile = "C:/jdbc/connection-pool.log";
	FileWriter fw = null;
	public static final String LINE_SEPARATOR = System.getProperty("line.separator");
	
	public Log() {
		try {
			fw = new FileWriter(logFile, true);
		} catch (IOException e){}
	}
	
	public void close() {
		try {
			fw.close();
		} catch (IOException e){}
	}
	
	public void close(FileWriter fw) {
		try {
			fw.close();
		} catch (IOException e){}
	}
	
	public void debug(String msg) {
		try {
			fw.write(new java.util.Date()+ " : ");
			fw.write(msg + LINE_SEPARATOR);
			fw.flush();
		} catch (IOException e) {
			System.err.println("IOException.......!!");
		}
	}
	
	public static void out(String msg) {
		System.out.println(new Date() + ": " + msg);
	}
	
	public static void err(String msg) {
		System.out.println(new Date() + ": " + msg);
	}
	
	public static void err(Throwable e, String msg) {
		System.err.println(new Date() + ": " + msg);
		e.printStackTrace(System.out);
	}
	
}

DBConnectionPool.java

package net.java_school.db.dbpool; 

import java.util.*; 
import java.sql.*; 
import java.util.Date; 

import net.java_school.util.Log;

// Connection Pool을 관리하는 클래스 
class DBConnectionPool {
	// 현재 사용 중인 Connection 개수
	private int checkedOut;
	
	// Free Connection List
	private Vector<Connection> freeConnections = new Vector<Connection>();
	
	// Connection 최대 개수
	private int maxConn;
	
	// Connection 초기 개수
	private int initConn;
	
	// Waiting time (pool에 connection이 없을때 기다리는 최대시간)
	private int maxWait;
	
	// Connection Pool Name
	private String name;
	
	// DB Password
	private String password;
	
	// DB URL
	private String URL;
	
	// DB UserID
	private String user;
	
	// Constructor
	public DBConnectionPool(String name, String URL, String user, String password, int maxConn, int initConn, int waitTime) {
		this.name = name;
		this.URL = URL;
		this.user = user;
		this.password = password;
		this.maxConn = maxConn;
		this.maxWait = waitTime;
		
		for (int i = 0; i < initConn; i++) {
			freeConnections.addElement(newConnection());
		}
	}
	
	// Connection 반납
	// @param con : 반납할 Connection
	public synchronized void freeConnection(Connection con) {
		freeConnections.addElement(con);
		checkedOut--;
		// Connection을 얻기 위해 대기하고 있는 thread에 알림
		notifyAll();
	}
	
	// Connection 을 얻음
	public synchronized Connection getConnection() {
		Connection con = null;
		// Connection이 Free List에 있으면 List의 처음 것을 얻음
		if (freeConnections.size() > 0) {
			con = (Connection) freeConnections.firstElement();
			freeConnections.removeElementAt(0);
			
			try {
				// DBMS에 의해 Connection이 close 되었으면 다시 요구
				if (con.isClosed()) {
					Log.err("Removed bad connection from " + name);
					con = getConnection();
				}
			} // 요상한 Connection 발생하면 다시 요구
			catch (SQLException e) {
				Log.err(e, "Removed bad connection from " + name);
				con = getConnection();
			}
		} // Connection이 Free List에 없으면 새로 생성
		else if (maxConn == 0 || checkedOut < maxConn) {
			con = newConnection();
		}
		
		if (con != null) {
			checkedOut++;
		}
		
		return con;
	}
	
	// Connection을 얻음
	// @param timeout : Connection을 얻기 위한 최대 기다림 시간
	public synchronized Connection getConnection(long timeout) {
		long startTime = new Date().getTime();
		Connection con;
		while ((con = getConnection()) == null) {
			try {
				wait(timeout * maxWait);
			} catch (InterruptedException e) {}
			if ((new Date().getTime() - startTime) >= timeout) {
				// 기다림 시간 초과
				return null;
			}
		}
		
		return con;
	}
	
	// Connection 생성
	private Connection newConnection() {
		Connection con = null;
		try {
			if (user == null) {
				con = DriverManager.getConnection(URL);
			} else {
				con = DriverManager.getConnection(URL, user, password);
			}
			Log.out("Created a new connection in pool " + name);
		} catch (SQLException e) {
			Log.err(e, "Can't create a new connection for " + URL + " user : " + user + " passwd : " + password);
			return null;
		}
		
		return con;
	}
	
}

DBConnectionPoolManager.java

package net.java_school.db.dbpool;

import java.sql.*;
import java.util.*;

import net.java_school.util.Log;

public class DBConnectionPoolManager {
	// 인스턴스를 하나만 유지하기 위해 static 으로 선언
	static private DBConnectionPoolManager instance = null;
	//JDBC 드라이버 관리 JDK 1.5 이상인 경우
	private Vector<String> drivers = new Vector<String>();
	
	// DB Connection Pool List JDK 1.5 이상의 경우
	private Hashtable<String,DBConnectionPool> pools = 
  	new Hashtable<String,DBConnectionPool>();
	
	// DBConnectionPoolManager의 instance를 얻음
	// @return DBConnectionManger
	static synchronized public DBConnectionPoolManager getInstance() {
		if (instance == null) {
			instance = new DBConnectionPoolManager();
		}
		
		return instance;
	}
	
	// Default Constructor
	private DBConnectionPoolManager() {}
	
	// 현재 Connection을 Free Connection List로 보냄
	// @param name : Pool Name
	// @param con : Connection
	public void freeConnection(String name, Connection con) {
		DBConnectionPool pool = (DBConnectionPool) pools.get(name);
		if (pool != null) {
			pool.freeConnection(con);
		}
		
		Log.out("One Connection of " + name + " was freed");
	}
	
	// Open Connection을 얻음. 현재 열린 커넥션이 없고 최대 커넥션 개수가
	// 사용 중이 아닐 때는 새로운 커넥션을 생성. 현재 열린 커넥션이 없고
	// 최대 커넥션 개수가 사용 중일 때 기본 대기 시간을 기다림
	// @param name : Pool Name
	// @return Connection : The connection or null
	public Connection getConnection(String name) {
		DBConnectionPool pool = (DBConnectionPool) pools.get(name);
		if (pool != null) {
			return pool.getConnection(10);
		}
		return null;
	}
	
	// Connection Pool을 생성
	// @param poolName : 생성할 Pool Name
	// @param url : DB URL
	// @param user : DB UserID
	// @param password : DB Password
	private void createPools(String poolName, String url, String user,
			String password, int maxConn, int initConn, int maxWait) {
		DBConnectionPool pool = new DBConnectionPool(poolName, url, user,	password, maxConn, initConn, maxWait);
		pools.put(poolName, pool);
		Log.out("Initialized pool " + poolName);
	}
	
	// 초기화 작업
	public void init(String poolName, String driver, String url,
			String user, String passwd, int maxConn, int initConn, int maxWait) {
		loadDrivers(driver);
		createPools(poolName, url, user, passwd, maxConn, initConn, maxWait);
	}
	
	// JDBC Driver Loading
	// @param driverClassName : 사용하고자 하는 DB의 JDBC 드라이버
	private void loadDrivers(String driverClassName) {
		try {
			Class.forName(driverClassName);
			drivers.addElement(driverClassName);
			Log.out("Registered JDBC driver " + driverClassName);
		} catch (Exception e) {
			Log.err(e, "Can't register JDBC driver: " + driverClassName);
		}
	}
	
	public Hashtable<String,DBConnectionPool> getPools() {
		return pools;
	}
	
	public int getDriverNumber() {
		return drivers.size();
	}
	
}

ConnectionManager.java

package net.java_school.db.dbpool;

import java.sql.*;
import java.io.*;
import java.util.*;

import net.java_school.util.Log;

public abstract class ConnectionManager {
	protected DBConnectionPoolManager connMgr = null;
	protected String poolName, dbServer, dbName, port, userID, passwd;
	int maxConn,initConn, maxWait;
	private Properties dbProperties;
	private String configFile;
	
	public ConnectionManager(String pool) {
		poolName = pool;
		// Property파일 디렉토리 지정
		configFile = "C:/jdbc/"+poolName+".properties";
		
		try {
			dbProperties = readProperties();
			dbServer = getProperty("dbServer");
			port = getProperty("port");
			dbName = getProperty("dbName");
			userID = getProperty("userID");
			passwd = getProperty("passwd");
			maxConn = Integer.parseInt(getProperty("maxConn"));
			initConn = Integer.parseInt(getProperty("initConn"));
			maxWait = Integer.parseInt(getProperty("maxWait"));
		} catch (IOException ioe) {
			Log.err("Error reading properties of " + configFile);
		}
	}
	
	public Connection getConnection() {
		return (connMgr.getConnection(poolName));
	}
	
	public void freeConnection(Connection conn) {
		connMgr.freeConnection(poolName, conn);
	}
	
	private String getProperty(String prop) throws IOException {
		return (dbProperties.getProperty(prop));
	}
	
	protected synchronized Properties readProperties() throws IOException {
		Properties tempProperties = new Properties();
		FileInputStream in = new FileInputStream(configFile);
		tempProperties.load(in);
		return tempProperties;
	}
	
	public int getDriverNumber() {
		return connMgr.getDriverNumber();
	}
	
}

OracleConnectionManager.java

package net.java_school.db.dbpool;

public class OracleConnectionManager extends ConnectionManager {

	public OracleConnectionManager() {
		super("oracle");
		String JDBCDriver = "oracle.jdbc.driver.OracleDriver";
		// 오라클용 JDBC thin driver
		String JDBCDriverType = "jdbc:oracle:thin";
		String url = JDBCDriverType + ":@" + dbServer + ":" + port + ":" + dbName;
		connMgr = DBConnectionPoolManager.getInstance();
		connMgr.init(poolName, JDBCDriver, url, userID, passwd, maxConn, initConn, maxWait);
	}
	
}

oracle.properties

############################################ 
# Database Connection Properties for Oracle
############################################ 

# Database Server Name OR IP address 
dbServer = 127.0.0.1

# The port number your DB server listents to. 
port = 1521

# Database Name 
dbName = orcl

# Database User 
userID = scott

# Database Password 
passwd = tiger

# Maximum Connection Number 
maxConn = 20

# Inital Connection Number 
initConn = 5

# Maximum Wait Time 
maxWait = 5

3. 사용법

Log.java 소스에서
public String logFile = "C:/jdbc/connection-pool.log"; 에 맞게 C:/jdbc 에 connection-pool.log 라는 내용이 빈 파일을 만든다.
ConnectionManager.java 소스에서
configFile = "C:/jdbc/"+poolName+".properties"; 에 맞게 C:/jdbc 에 oracle.properties 파일을 위에 oracle.properties 내용을 참고해서 만든다.