이전 만든 데이터베이스 매니저는 단일 또는 복수 개의 데이터베이스 정보 프로퍼티를 읽어 데이터베이스 드라이버를 로드하고 DriverManager(드라이버 매니저)를 통해 연결(Connection)하여 사용할 수 있게 하였습니다.
자바 데이터베이스 매니저 만들기(https://carrotweb.tistory.com/70)를 참조하세요.
데이터베이스 매니저를 이용하여 데이터베이스를 연결하고 쿼리하는 되는 문제가 없습니다. 그러나 데이터베이스를 쿼리 할 때마다 데이터베이스 연결과 종료를 매번 해야 합니다. 이는 쿼리를 처리할 때마다 데이터베이스 접속과 종료 시간이 필요하게 됨으로 여러 명이 접속하여 수많은 쿼리를 처리하게 될 경우가 데이터베이스 접속 시간으로 인해 시스템 처리가 늘어지게 됩니다.
그래서 데이터베이스 접속 시간으로 줄이기 위해 미리 데이터베이스와 여러 개 연결하여 커넥션(Connection)들을 만들어 보관하고 데이터베이스 접속 요청이 들어오면 미리 연결된 커넥션(Connection)을 넘겨 쿼리가 바로 되도록 합니다. 그리고 쿼리가 처리되고 데이터베이스와의 접속 종료 요청이 들어오면 커넥션을 다시 보관합니다. 이렇게 커넥션(Connection)을 관리하는 것을 커넥션 풀(Connection Pool)이라 합니다. 컨텍션이 보관되는 장소가 풀(Pool)입니다.
그럼 데이터베이스 커넥션 풀(Connection Pool)을 만들어 적용하겠습니다.
1. "test2" 프로젝트의 "Java Resources"에 "src/main/resources"에서 "/com/home/project/test2/config/database-maria.properties"파일에 커넥션 풀(Connection Pool) 크기를 추가합니다.
id=databaseMaria
driverClassName=org.mariadb.jdbc.Driver
url=jdbc:mariadb://localhost:3306/test
username=root
password=password
initialSize=20
"initialSize"는 최초 생성되는 데이터베이스와 연결되는 커넥션 수입니다.
데이터베이스 커넥션 풀(Connection Pool)에서 연결된 데이터베이스 커넥션과 사용 여부를 판단하기 위해 커넥션 객체를 생성합니다.
2. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.hom.project.test2.database"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "TestDatabaseConnection.java"를 생성합니다.
데이터베이스 커넥션 필드와 커넥션이 사용 중인지 확인하기 위해 부울 값 필드, 멤버 필드에 대한 "Getter/Setter"메서드를 추가합니다.
/**
* 데이터베이스 연결 객체
*/
private Connection connection = null;
/**
* 데이터베이스 연결 객체 사용 여부
*/
private boolean connect = false;
/**
* 데이터베이스 연결 객체를 가져옵니다.
* @return 데이터베이스 연결 객체
*/
public Connection getConnection() {
return connection;
}
/**
* 데이터베이스 연결 객체를 설정합니다.
* @param connection 데이터베이스 연결 객체
*/
public void setConnection(Connection connection) {
this.connection = connection;
}
/**
* 데이터베이스 연결 객체 사용 여부를 가져옵니다.
* @return 데이터베이스 연결 객체 사용 여부
*/
public boolean isConnect() {
return connect;
}
/**
* 데이터베이스 연결 객체 사용 여부를 설정합니다.
* @param connect 데이터베이스 연결 객체 사용 여부
*/
public void setConnect(boolean connect) {
this.connect = connect;
}
클래스 생성자를 통해 데이터베이스 커넥션과 사용 여부가 설정되도록 생성자를 추가합니다.
/**
* 생성자
* @param connection 데이터베이스 연결 객체
* @param connect 데이터베이스 연결 객체 사용 여부
*/
public TestDatabaseConnection(Connection connection, boolean connect) {
super();
this.connection = connection;
this.connect = connect;
}
이전에 생성자 자동 생성 방법을 소개하지 않은 것 같아 소개해드립니다.
이클립스 메뉴 [Source > Generate Constructor using Fields...]를 선택하면 자동으로 생성할 수 있습니다.
Generate Constructor using Fields"에서 "Select fields to initialize"에서 멤버 필드를 선택하고 "Insertion point"는 기본 생성자로 설정하고 "Generate"버튼을 클릭합니다.
커넥션 객체들을 관리하는 데이터베이스 커넥션 풀(Connection Pool) 매니저를 생성합니다.
3. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.hom.project.test2.database"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "TestDatabasePoolManager.java"를 생성합니다.
기존 데이터베이스 매니저와 동일하게 데이터베이스 프로퍼티(properties) 파일을 읽어 저장하고 프로퍼티로부터 읽어 들인 정보로 데이터베이스 드라이버를 로딩하는 부분이 동일합니다.
자바 데이터베이스 매니저 만들기(https://carrotweb.tistory.com/70)를 참조하세요.
프로퍼티 필드와 드라이버가 로딩되었는지, 커넥션 풀이 생성되었는지 확인하기 위해 부울 값 필드를 추가하고 커넥션 풀 생성 여부 확인 메서드를 추가합니다.
/**
* 데이터베이스 프로퍼티
*/
private Properties databaseInfo = new Properties();
/**
* 데이터베이스 드라이버 초기화 여부
*/
private boolean initDriver = false;
/**
* 데이터베이스 커넥션 풀 생성 여부
*/
private boolean initDatabasePool = false;
/**
* 데이터베이스 커넥션 풀 생성 여부를 가져옵니다.
* @return 데이터베이스 커넥션 풀 생성 여부
*/
public boolean isInitDatabasePool() {
return initDatabasePool;
}
그리고 데이터베이스 커넥션 객체들을 보관하기 위해 "Vector"로 필드를 추가합니다.
/**
* 데이터베이스 커넥션 풀
*/
private Vector<TestDatabaseConnection> connections = null;
참고로 필드를 "ArrayList"가 아닌 "Vector"로 한 이유는 "ArrayList"는 내부적으로 인덱스(순서)가 있어 중간에 추가(add)와 삭제(remove)할 경우에는 추가되거나 삭제된 인덱스를 기준으로 이후 인덱스 전체가 앞(삭제 경우) 또는 뒤로(추가 경우) 이동되어야 하므로 속도가 떨어집니다. 여기에서는 많이 사용되는 것이 아니기 때문에 큰 차이는 없겠지만 객체를 배열로 관리해야 하고 추가/삭제가 많으면 "Vector"로 하시는 것이 좋습니다.
데이터베이스 프로퍼티(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가 없음");
result = false;
}
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();
}
}
"DriverManager"의 "getConnection"메서드를 이용하여 데이터베이스에 연결하는 메서드를 추가합니다.
/**
* 데이터베이스에 연결합니다.
* @return 데이터베이스 연결 객체
*/
public Connection createConnection() {
if (!initDriver) {
System.out.println("데이터베이스 드라이버가 로딩되지 않음");
return null;
}
Connection connection = null;
String url = databaseInfo.getProperty("url");
String username = databaseInfo.getProperty("username");
String password = databaseInfo.getProperty("password");
try {
connection = DriverManager.getConnection(url, username, password);
System.out.println("데이터베이스에 연결됨");
} catch (SQLException e) {
System.out.println("데이터베이스에 연결하지 못함");
e.printStackTrace();
}
return connection;
}
데이터베이스 커넥션 풀을 생성하는 메소드를 추가합니다.
/**
* 데이터베이스 커넥션 풀을 생성합니다.
* @return 데이터베이스 커넥션 풀 생성 여부
*/
private boolean initDatabasePool() {
boolean bResult = true;
String initialSize = databaseInfo.getProperty("initialSize");
int poolCount = Integer.parseInt(initialSize);
connections = new Vector<TestDatabaseConnection>(poolCount);
if (connections != null) {
System.out.println("데이터베이스 커넥션 풀을 생성 시작");
for (int poolIndex = 0; poolIndex < poolCount; poolIndex++) {
Connection connection = createConnection();
if (connection != null) {
TestDatabaseConnection connectionObject = new TestDatabaseConnection(connection, false);
connections.addElement(connectionObject);
System.out.println("데이터베이스와 연결된 커넥션 객체[" + (poolIndex + 1) + "]를 생성함");
} else {
bResult = false;
System.out.println("데이터베이스와 연결된 커넥션 객체[" + (poolIndex + 1) + "]를 생성하지 못함");
}
}
if (bResult) {
initDatabasePool = true;
System.out.println("데이터베이스 커넥션 풀을 생성함");
} else {
System.out.println("시스템 문제로 인해 데이터베이스 커넥션 풀을 생성하지 못함");
destroyDatabasePool();
}
} else {
bResult = false;
System.out.println("데이터베이스 커넥션 풀을 생성 하지 못함");
}
return bResult;
}
클래스 생성자를 통해 데이터베이스 프로퍼티 파일을 읽고 데이터베이스 커넥션 풀을 생성하게 생성자를 추가합니다.
/**
* 생성자
* @param databaseManagerPath 데이터베이스 매니저 프로퍼티 경로
*/
public TestDatabasePoolManager(String databaseManagerPath) {
if (loadProperties(databaseManagerPath)) {
initDriver();
if (initDriver) {
initDatabasePool();
}
}
}
데이터베이스 커넥션 풀에서 커넥션을 가져오는 메서드를 추가합니다. 이전 데이터베이스 매니저에서 데이터베이스와 연결에 사용한 메서드와 동일하게 합니다.
/**
* 데이터베이스 커넥션 풀에서 데이터베이스 연결 객체를 가져옵니다.
* @return 데이터베이스 연결 객체
*/
public Connection getConnection() {
Connection connection = null;
if (initDatabasePool) {
Iterator<TestDatabaseConnection> iterator = connections.iterator();
while(iterator.hasNext()) {
TestDatabaseConnection connectionObject = iterator.next();
if (connectionObject != null) {
synchronized(this) {
if (!connectionObject.isConnect()) {
connection = connectionObject.getConnection();
connectionObject.setConnect(true);
System.out.println("데이터베이스 커넥션 객체[" + connectionObject.toString() + "]를 가져옴");
}
}
if (connection != null) {
break;
}
}
}
if (connection == null) {
System.out.println("데이터베이스 커넥션 객체를 가져오지 못함");
}
} else {
System.out.println("생성된 데이터베이스 커넥션 풀이 없음");
}
return connection;
}
커넥션을 반환하는 메소드를 추가합니다. 이전 데이터베이스 매니저에서 데이터베이스와의 연결 종료에 사용한 메서드와 동일하게 합니다.
/**
* 데이터베이스 커넥션 풀에 데이터베이스 연결 객체를 반환합니다.
* @param connection 데이터베이스 연결 객체
*/
public void closeConnection(Connection connection) {
if (initDatabasePool) {
Iterator<TestDatabaseConnection> iterator = connections.iterator();
while(iterator.hasNext()) {
TestDatabaseConnection connectionObject = iterator.next();
if (connectionObject != null) {
if (connectionObject.getConnection() == connection) {
try {
connection.clearWarnings();
} catch (SQLException e) {
e.printStackTrace();
}
connectionObject.setConnect(false);
System.out.println("데이터베이스 커넥션 객체[" + connectionObject.toString() + "]를 반환함");
break;
}
}
}
}
}
데이터베이스 커넥션 풀을 파괴하는 메소드를 추가합니다.
/**
* 데이터베이스 커넥션 풀을 파괴합니다.
*/
public void destroyDatabasePool() {
if (initDatabasePool) {
for (int poolIndex = 0; poolIndex < connections.size(); poolIndex++) {
TestDatabaseConnection connectionObject = (TestDatabaseConnection)connections.elementAt(poolIndex);
if (connectionObject != null) {
try {
connectionObject.getConnection().close();
System.out.println("데이터베이스와 연결을 종료하고 커넥션 객체[" + (poolIndex + 1) + "]를 삭제함");
} catch (SQLException e) {
System.out.println("데이터베이스와 연결을 종료하고 커넥션 객체[" + (poolIndex + 1) + "]를 삭제하지 못함");
e.printStackTrace();
}
}
}
connections.removeAllElements();
initDatabasePool = false;
System.out.println("데이터베이스 커넥션 풀을 파괴함");
}
}