이번에는 스프링 프레임워크에서도 "DataSource"로 사용하고 있는 아파치 공통 데이터베이스 커넥션 풀(Apache Commons DBCP)를 이용하겠습니다.
이번에 사용할 "Commons DBCP"의 버전은 2.8.x입니다. "Commons DBCP 2.x"부터는 JDK 8 버전, JDBC 4.2 버전부터 사용 가능합니다. 만약 JDK 6버전을 사용하신다면 "Commons DBCP 1.4"를 사용하시기 바랍니다.
"Commons DBCP"의 버전별로 JDK 버전과 JDBC 버전을 확인하시기 바랍니다.
DBCP 2.7.0 compiles and runs under Java 8 only (JDBC 4.2)
DBCP 2.6.0 compiles and runs under Java 8 only (JDBC 4.2)
DBCP 2.5.0 compiles and runs under Java 8 only (JDBC 4.2)
DBCP 2.4.0 compiles and runs under Java 7 only (JDBC 4.1)
DBCP 1.4 compiles and runs under Java 6 only (JDBC 4)
DBCP 1.3 compiles and runs under Java 1.4-5.0 only (JDBC 3)
1. "https://mvnrepository.com"를 접속하고 검색에 "commons-dbcp2"를 입력하고 검색합니다.
"Apache Commons DBCP"를 클릭합니다.
"Version"중에서 최신 버전인 "2.8.0"를 클릭합니다.
"Maven"탭에 있는 스크립트를 복사합니다.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.8.0</version>
</dependency>
또는 아파치 사이트(https://commons.apache.org/proper/commons-dbcp/dependency-info.html)에서 "Maven" 스크립트를 복사합니다.
만약 Maven대신 Jar파일로 다운로드 받아서 사용하신다면 "http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi"에서 "Commons DBCP"의 버전별로 다운 받으시면 됩니다.
2. "test2" 프로젝트의 "pom.xml"을 오픈하여 붙여넣기하고 저장합니다. 그러면 Maven에서 "commons-dbcp2-2.8.0.jar", "commons-pool2-2.8.1.jar", "commons-logging-1.2.jar"파일들을 자동으로 다운로드 받습니다.
다운로드가 완료되면 자동으로 빌드하고 "test2" 프로젝트의 [Java Resources > Libraries > Maven Dependencies]에 "commons-dbcp2-2.8.0.jar", "commons-pool2-2.8.1.jar", "commons-logging-1.2.jar"파일이 연결되어 있는 것을 확인할 수 있습니다.
이전에 만든 데이터베이스 커넥션 풀에서 사용되는 프로퍼티 속성 명들은 "Commons DBCP 1.x"를 기준으로 작성된 것이기 때문에 우리가 사용할 "Commons DBCP 2.x"을 사용하려면 변경된 속성 명으로 변경해야 합니다.
"Commons DBCP 1.x"를 기준으로 설명하기 때문에 "Commons DBCP 2.x"을 사용하려면 바뀐 속성 이름으로 수정해야 합니다.
"maxActive" -> "maxTotal"
"maxWait" -> "maxWaitMills"
3. "test2" 프로젝트의 "Java Resources"에 "src/main/resources"에서 "/com/home/project/test2/config/database-maria.properties"파일에 수정합니다.
id=databaseMaria
driverClassName=org.mariadb.jdbc.Driver
url=jdbc:mariadb://localhost:3306/test
username=root
password=kiki2000
initialSize=8
maxWaitMillis=30000
maxTotal=8
maxIdle=8
"initialSize", "maxWaitMillis", "maxTotal", "maxIdle"는 테스트를 위해 설정한 값임으로 실제 사용하실때는 설정값을 시스템 환경에 맞게 조정하시길 바랍니다.
4. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.hom.project.test2.database"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "TestDatabaseConectionPool.java"를 생성합니다.
기존 데이터베이스 매니저와 동일하게 데이터베이스 프로퍼티(properties) 파일을 읽어 저장하고 프로퍼티로 부터 읽어들인 정보로 데이터베이스 드라이버를 로딩하는 부분이 동일합니다.
자바 데이터베이스 커넥션 풀 매니저 만들기 1 ~ 5를 참조하세요.
프로퍼티 필드와 드라이버가 로딩되었는지, 커넥션 풀이 생성되었는지 확인하기 위해 부울값 필드를 추가하고 커넥션 풀 생성 여부 확인 메서드를 추가합니다.
/**
* 데이터베이스 프로퍼티
*/
private Properties databaseInfo = new Properties();
/**
* 데이터베이스 드라이버 초기화 여부
*/
private boolean initDriver = false;
/**
* 데이터베이스 커넥션 풀 생성 여부
*/
private boolean initDatabasePool = false;
/**
* 데이터베이스 커넥션 풀 생성 여부를 가져옵니다.
* @return 데이터베이스 커넥션 풀 생성 여부
*/
public boolean isInitDatabasePool() {
return initDatabasePool;
}
데이터베이스 프로퍼티(properties) 파일을 읽어 프로퍼티로 로드하게 메소드를 추가합니다.
/**
* 데이터베이스 매니저 프로퍼티를 로드하고 검증합니다.
* @param databaseManagerPath 데이터베이스 매니저 프로퍼티 경로
* @return 프로퍼티 로드 여부
*/
private boolean loadProperties(String databaseManagerPath) {
boolean result = true;
InputStream inputStream = getClass().getResourceAsStream(databaseManagerPath);
if (inputStream != null) {
try {
databaseInfo.load(inputStream);
System.out.println("데이터베이스 프로퍼티를 읽음");
String id = databaseInfo.getProperty("id");
if (id == null || id.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 id가 없음");
result = false;
}
String driverClassName = databaseInfo.getProperty("driverClassName");
if (driverClassName == null || driverClassName.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 driverClassName이 없음");
result = false;
}
String url = databaseInfo.getProperty("url");
if (url == null || url.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 url이 없음");
result = false;
}
String username = databaseInfo.getProperty("username");
if (username == null || username.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 username이 없음");
result = false;
}
String password = databaseInfo.getProperty("password");
if (password == null || password.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 password가 없음");
result = false;
}
String initialSize = databaseInfo.getProperty("initialSize");
if (initialSize == null || initialSize.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 initialSize가 없어 기본값(8)으로 설정합니다.");
databaseInfo.setProperty("initialSize", "8");
}
String maxWaitMillis = databaseInfo.getProperty("maxWaitMillis");
if (maxWaitMillis == null || maxWaitMillis.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 maxWaitMillis가 없어 기본값(무제한)으로 설정합니다.");
databaseInfo.setProperty("maxWaitMillis", "0");
}
String maxTotal = databaseInfo.getProperty("maxTotal");
if (maxTotal == null || maxTotal.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 maxTotal이 없어 기본값(8)으로 설정합니다.");
databaseInfo.setProperty("maxTotal", "8");
}
String maxIdle = databaseInfo.getProperty("maxIdle");
if (maxIdle == null || maxIdle.trim().isEmpty()) {
System.out.println("데이터베이스 프로퍼티에 maxIdle가 없어 기본값(8)으로 설정합니다.");
databaseInfo.setProperty("maxIdle", "8");
}
if (!result) {
System.out.println("데이터베이스 프로퍼티 정보가 정확하지 않음");
}
} catch (IOException e) {
System.out.println("데이터베이스 프로퍼티를 읽지 못함");
result = false;
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
System.out.println("데이터베이스 프로퍼티를 읽지 못함");
result = false;
}
return result;
}
"id"로 여러개의 데이터베이스들을 구분하여 관리 할 수 있도록 메소드를 추가합니다.
/**
* 데이터베이스 프로퍼티에 id를 가져옵니다.
* @return id
*/
public String getId() {
return databaseInfo.getProperty("id") == null ? "" : databaseInfo.getProperty("id").trim();
}
프로퍼티에서 드라이버(driver) 클래스 명을 가져와 JDBC 드라이버(driver) 클래스를 로드하게 메소드를 추가합니다.
/**
* 데이터베이스 드라이버를 로드합니다.
*/
private void initDriver() {
String driverClassName = databaseInfo.getProperty("driverClassName");
try {
Class<?> dbDriver = Class.forName(driverClassName);
System.out.println("데이터베이스 드라이버(" + dbDriver.toString() + ")가 로딩됨");
initDriver = true;
} catch (ClassNotFoundException e) {
System.out.println("데이터베이스 드라이버가 로딩되지 않음");
e.printStackTrace();
}
}
"commons-dbcp2"와 "Commons-pool2"를 이용하여 데이터베이스 커넥션 풀을 생성하는 메소드를 추가합니다.
"commons-dbcp2"를 이용하여 데이터베이스와 연결하여 커넥션을 생성하는 커넥션 팩토리(ConnectionFactory)를 생성합니다.
String url = databaseInfo.getProperty("url");
String username = databaseInfo.getProperty("username");
String password = databaseInfo.getProperty("password");
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(url, username, password);
"commons-dbcp2"를 이용하여 커넥션 팩토리로 부터 커넥션을 생성하고 관리하는 풀을 생성하는 풀(Pool)이 가능한 커넥션 팩토리(PoolableConnectionFactory)를 생성합니다.
PoolableConnectionFactory PoolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null);
"Commons-pool2"를 이용하여 풀(Pool) 생성과 관리에 필요한 설정 정보를 생성합니다.
GenericObjectPoolConfig<PoolableConnection> genericObjectPoolConfig= new GenericObjectPoolConfig<PoolableConnection>();
풀에서 동시에 사용할 수 있는 최대 커넥션 개수(maxTotal)와 커넥션이 반환될 때 최대로 유지될 수 있는 커넥션 개수(maxIdle)를 설정합니다.
String maxTotal = databaseInfo.getProperty("maxTotal");
int maxTotalConnection = Integer.parseInt(maxTotal);
String maxIdle = databaseInfo.getProperty("maxIdle");
int maxIdleConnection = Integer.parseInt(maxIdle);
String maxWaitMillisInfo = databaseInfo.getProperty("maxWaitMillis");
long maxWaitMillis = Long.parseLong(maxWaitMillisInfo);
genericObjectPoolConfig.setMaxTotal(maxTotalConnection);
genericObjectPoolConfig.setMaxIdle(maxIdleConnection);
genericObjectPoolConfig.setMaxWaitMillis(maxWaitMillis);
"Commons-pool2"를 이용하여 풀(Pool)이 가능한 커넥션 팩토리(PoolableConnectionFactory)와 풀 설정 정보로 풀(Pool)을 생성합니다.
GenericObjectPool<PoolableConnection> genericObjectPool = new GenericObjectPool<PoolableConnection>(PoolableConnectionFactory, genericObjectPoolConfig);
생성된 풀(Pool)을 풀(Pool)이 가능한 커넥션 팩토리(PoolableConnectionFactory)에 풀로 적용합니다.
PoolableConnectionFactory.setPool(genericObjectPool);
"commons-dbcp2"의 풀(Pool)을 위한 풀링 드라이버(PoolingDriver)를 등록합니다.
try {
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
} catch (ClassNotFoundException e) {
bResult = false;
e.printStackTrace();
}
등록된 풀링 드라이버(PoolingDriver)에 사용할 커넥션 풀 명과 생성된 풀(Pool)을 등록합니다. 커넥션 풀 명을 "maria"로 입력합니다.
"jdbc:apache:commons:dbcp:"는 "PoolingDriver.class"에서 정의된 database URL 접두사임으로 반드시 "jdbc:apache:commons:dbcp:"로 시작되어야 합니다.
/** My URL prefix */
public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
드라이브 매니저에서 등록된 풀링 드라이버(PoolingDriver)를 가져와 드라이버에 "maria"란 명으로 풀(Pool)을 등록합니다.
드라이버 매니저(DriverManager)란 JDBC 드라이버들을 관리하는 클래스로 드라이버를 등록하고 해제하고 디비와 연결해주는 기능을 합니다.
PoolingDriver driver = null;
try {
driver = (PoolingDriver)DriverManager.getDriver("jdbc:apache:commons:dbcp:");
driver.registerPool("maria", genericObjectPool);
} catch (SQLException e) {
bResult = false;
e.printStackTrace();
}
"jdbc:apache:commons:dbcp:maria"으로 "DriverManager.getConnection()"메소드를 통해 풀(Pool)에 있는 커넥션을 가져옵니다.
여기서 확인할 수 있는 것은 "Commons-pool2의 "GenericObjectPoolConfig"에서는 "initialSize"를 설정하는 부분이 없습니다. "Commons-pool2"은 기본적으로 초기에 풀(Pool)을 채우지 않습니다.
스프링에서 사용하고 있는 것은 "commons-dbcp2"의 "BasicDataSource"입니다. 이후 "BasicDataSource"에 대해 설명하겠습니다.
전체 소스입니다.
/**
* 데이터베이스 커넥션 풀을 생성합니다.
*/
private boolean initDatabasePool() {
System.out.println("데이터베이스 커넥션 풀을 생성 시작");
String url = databaseInfo.getProperty("url");
String username = databaseInfo.getProperty("username");
String password = databaseInfo.getProperty("password");
// 데이터베이스와 연결하여 커넥션을 생성하는 커넥션 팩토리
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(url, username, password);
// 커넥션 팩토리로 부터 커넥션을 생성하고 관리하는 풀을 생성하는 풀이 가능한 커넥션 팩토리
PoolableConnectionFactory PoolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null);
// commons-pool2를 이용하여 풀(Pool)을 생성과 관리에 필요한 설정 정보
GenericObjectPoolConfig<PoolableConnection> genericObjectPoolConfig = new GenericObjectPoolConfig<PoolableConnection>();
String maxTotal = databaseInfo.getProperty("maxTotal");
int maxTotalConnection = Integer.parseInt(maxTotal);
String maxIdle = databaseInfo.getProperty("maxIdle");
int maxIdleConnection = Integer.parseInt(maxIdle);
String maxWaitMillisInfo = databaseInfo.getProperty("maxWaitMillis");
long maxWaitMillis = Long.parseLong(maxWaitMillisInfo);
// 동시에 사용할 수 있는 최대 커넥션 개수
genericObjectPoolConfig.setMaxTotal(maxTotalConnection);
// 커넥션이 반환될 때 최대로 유지될 수 있는 커넥션 개수
genericObjectPoolConfig.setMaxIdle(maxIdleConnection);
// 커넥션을 가져오기 위해 대기하기 최대 시간
genericObjectPoolConfig.setMaxWaitMillis(maxWaitMillis);
// commons-pool2를 이용하여 풀(Pool) 생성
GenericObjectPool<PoolableConnection> genericObjectPool = new GenericObjectPool<PoolableConnection>(PoolableConnectionFactory, genericObjectPoolConfig);
// PoolabeConnectionFactory에도 풀(Pool) 적용
PoolableConnectionFactory.setPool(genericObjectPool);
boolean bResult = false;
// 풀(Pool)을 위한 풀링 드라이버 로딩
try {
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
bResult = true;
} catch (ClassNotFoundException e) {
System.out.println("풀링 드라이버가 로딩되지 않음");
e.printStackTrace();
}
if (bResult) {
// 로딩된 풀링 드라이버에 사용할 커넥션 풀 명("maria")과 풀(Pool) 등록
PoolingDriver driver = null;
try {
driver = (PoolingDriver)DriverManager.getDriver("jdbc:apache:commons:dbcp:");
driver.registerPool("maria", genericObjectPool);
initDatabasePool = true;
System.out.println("데이터베이스 커넥션 풀을 생성함");
} catch (SQLException e) {
e.printStackTrace();
System.out.println("시스템 문제로 인해 데이터베이스 커넥션 풀을 생성하지 못함");
}
}
}
클래스 생성자를 통해 데이터베이스 프로퍼티 파일을 읽고 데이터베이스 커넥션 풀을 생성하게 생성자를 추가합니다.
/**
* 생성자
* @param databaseManagerPath 데이터베이스 매니저 프로퍼티 경로
*/
public TestDatabaseConectionPool(String databaseManagerPath) {
if (loadProperties(databaseManagerPath)) {
initDriver();
if (initDriver) {
initDatabasePool();
}
}
}
드라이버 매니저에서 관리되는 데이터베이스 커넥션 풀에서 커넥션을 가져오는 메소드를 추가합니다.
/**
* 데이터베이스 커넥션 풀에서 데이터베이스 연결 객체를 가져옵니다.
* @return 데이터베이스 연결 객체
*/
public Connection getConnection() {
Connection connection = null;
String jdbcDriver = "jdbc:apache:commons:dbcp:maria";
try {
connection = DriverManager.getConnection(jdbcDriver);
System.out.println("[" + Thread.currentThread().getName() + "] 데이터베이스 커넥션 객체[" + connection.toString() + "]를 가져옴");
} catch (SQLException e) {
e.printStackTrace();
System.out.println("[" + Thread.currentThread().getName() + "] 데이터베이스 커넥션 객체를 가져오지 못함");
}
return connection;
}
커넥션을 반환하는 메소드를 추가합니다.
/**
* 데이터베이스 커넥션 풀에 데이터베이스 연결 객체를 반환합니다.
* @param connection 데이터베이스 연결 객체
*/
public void closeConnection(Connection connection) {
try {
System.out.println("[" + Thread.currentThread().getName() + "] 데이터베이스 커넥션 객체[" + connection.toString() + "]를 반환함");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
데이터베이스 커넥션 풀을 파괴하는 메소드를 추가합니다.
/**
* 데이터베이스 커넥션 풀을 파괴합니다.
*/
public void destroyDatabasePool() {
if (initDatabasePool) {
PoolingDriver driver = null;
try {
driver = (PoolingDriver)DriverManager.getDriver("jdbc:apache:commons:dbcp:");
driver.closePool("maria");
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("데이터베이스 커넥션 풀을 파괴함");
}
}
이어서 이전에 만든 데이터베이스 커넥션 풀과 교체하여 적용하겠습니다.