Java 프레임워크 만들기 - JSP

자바 데이터베이스 커넥션 풀 매니저 만들기 3 - Java Database Connection Pool Manager, 이클립스(Eclipse)

carrotweb 2021. 6. 17. 22:19
728x90
반응형

지금까지 만든 데이터베이스 커넥션 풀은 최초에 데이터베이스와 연결되는 커넥션 수(initialSize) 만큼 연결되게 되어 있습니다. 만약에 웹 사이트에 접속되는 사용자가 많아지거나 웹 페이지에서 처리해야 하는 쿼리가 많아지거나 쿼리가 복잡해 커넥션 반환이 느려지면 동시에 사용되는 커넥션 수가 최초에 생성된 커넥션 수 보다 많이 필요하게 됩니다.

현재까지 생성되어 있는 커넥션을 동시에 다 사용하고 있다면 다음에 요청되는 것은 커넥션을 받을 수 없게 됩니다. 그래서 사용중인 커넥션이 반환될 때까지 대기(wait)거나 추가로 커넥션(Connection)을 생성하여 처리해야 합니다.

참고로 "TestDatabasePoolManager.java"는 모든 서비스(serive) 클래스에서 사용하고 있어 "getConnection()"메소드에서 커넥션을 가져와서 넘겨주기전에 커넥션 사용 처리할 때 "Synchronized" 키워드를 사용하여 동시 접근을 할 수 없게 처리하였습니다.

사용중인 커넥션이 반환될 때까지 대기(wait)하기 위해서는 사용되지 않는 커넥션이 나올 때까지 무한 반복해야 합니다. 무한 반복을 위해 "while(true)"와 "Thread.sleep()"를 사용합니다. 그리고 어떤 웹 요청에서 처리되고 있는지 확인하기 위해 현재 스레드의 이름("Thread.currentThread().getName()"메소드)을 출력하게 수정합니다.

1. "test2" 프로젝트의 "Java Resources/src/main/java"에서 com.hom.project.test2.database"에 있는 "TestDatabasePoolManager.java"의 "getConnection()"메소드를 수정합니다.

/**
 * 데이터베이스 커넥션 풀에서 데이터베이스 연결 객체를 가져옵니다.
 * @return 데이터베이스 연결 객체
 */
public Connection getConnection() {
	Connection connection = null;

	if (initDatabasePool) {
		while(true) {
			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("[" + Thread.currentThread().getName() + "] 데이터베이스 커넥션 객체[" + connectionObject.toString() + "]를 가져옴");
						}
					}
					if (connection != null) {
						break;
					}
				}
			}
			if (connection == null) {
				System.out.println("[" + Thread.currentThread().getName() + "] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				break;
			}
		}
		if (connection == null) {
			System.out.println("[" + Thread.currentThread().getName() + "] 데이터베이스 커넥션 객체를 가져오지 못함");
		}
	} else {
		System.out.println("생성된 데이터베이스 커넥션 풀이 없음");
	}

	return connection;
}

참고로, Servlet의 처리되는 웹 요청은 각각의 스레드(Thread)로 동작되게 되어 있습니다.

커넥션 풀에서 커넥션을 대기하여 가져오는 테스트를 하기 위해서 최초 생성되는 데이터베이스와 연결되는 커넥션 수를 1개로 제한합니다.

2. "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=1

 

커넥션 풀에서 다른 웹 요청이 커넥션을 대기하기 위해서는 쿼리 처리시간이 긴 쿼리가 필요합니다. 그래서 마리아디비에 1,000,000번 루프를 실행하는 프로시저(Procedure)로 생성합니다.

3. 디비버(DBeaver) 툴을 실행하고 쿼리 스크립트에서 1,000,000번 루프를 실행하는 프로시저(Procedure)로 생성합니다.

기존에 "testWhileProc" 프로시저(Procedure)가 있으면 삭제합니다. 처음 생성하는 프로시저(Procedure)라 실행하지 않아도 됩니다.

DROP PROCEDURE IF EXISTS testWhileProc;

"CREATE PROCEDURE"로 프로시저(Procedure)를 생성합니다.

"DECLARE"로 정수형 "i"변수를 정의하고 "SET"으로 "i"변수를 0으로 초기화합니다.

"while"문으로 "i"변수가 1,000,000이 넘으로 "LEAVE"를 통해 "while"문을 종료합니다.

최종적으로 "i"변수를 리턴합니다.

여기서는 단순하게 루프를 실행하기 때문에 리턴(OUT 변수)는 처리하지 않았습니다.

CREATE PROCEDURE testWhileProc()
BEGIN
	DECLARE i INT;
	SET i = 0;

	myWhile: WHILE(i < 1000000) DO
		IF (i > 1000000) THEN
			LEAVE myWhile;
		END IF;
		SET i = i + 1;
	END WHILE;
	SELECT i;
END;

생성된 프로시저(Procedure)를 테스트하기 위해 프로시저(Procedure)를 CALL로 호출합니다.

CALL testWhileProc();

 

생성된 프로시저(Procedure)를 선택하고 "SQL문 실행"버튼을 클릭하여 실행시킵니다.

 

CALL문을 선택하고 "SQL문 실행"버튼을 클릭하여 실행시킵니다. 1,000,000번 루프가 실행되는 쿼리 시간이 약 2.1초로 커넥션 테스트하기에는 적당합니다.

 

 

커넥션 대기를 하기 위해 생성된 프로시저(Procedure)를 추가합니다.

4. "TestLoginDaoImpl.java"의 "selectMember()"메소드에서 커넥션을 반환("testDatabaseManager.closeConnection(connection)")하기 전에 생성된 프로시저(Procedure)를 특정 아이디에서만 실행되게 추가합니다.

if (userVO.getId().equals("testid")) {
	try {
		preparedStatement = connection.prepareStatement("CALL testWhileProc()");
		System.out.println("testWhileProc : PreparedStatement를 생성함");
		preparedStatement.executeQuery();
		preparedStatement.close();
		System.out.println("testWhileProc : PreparedStatement를 종료함");
	} catch (SQLException e) {
		e.printStackTrace();
	}
}

 

5. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다. 두개의 웹 브라우저에서 "http://localhost:8080/test2/testform.do"를 입력합니다. 두개의 웹 브라우저에 아이디에는 "testid"를 패스워드에는 "testpwd"를 입력합니다.

두개의 웹 브라우저 "로그인"버튼을 순차적으로 클릭합니다.

"Console"탭을 보면 커넥션을 대기하고 있다고 반환되는 커넥션을 받아 처리되는것을 확인 할 수 있습니다.

[Console]

contextPath : /test2
requestURI : /test2/loginprocess.do
changed requestURI : /loginprocess.do
ServletRequest에 속성 추가 - 속성명 : returnUrl, 속성 값 : /test2/testform.do
id : testid, password : testpwd
[http-nio-8080-exec-3] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 가져옴
PreparedStatement를 생성함
Query[sql : 'SELECT MBR_ID, MBR_PWD, MBR_PWD_SALT, MBR_NM FROM MBR_ACCOUNT_TB WHERE MBR_ID=?', parameters : ['testid']]를 실행함
PreparedStatement를 종료함
testWhileProc : PreparedStatement를 생성함
{중간 생략}
contextPath : /test2
requestURI : /test2/loginprocess.do
changed requestURI : /loginprocess.do
ServletRequest에 속성 추가 - 속성명 : returnUrl, 속성 값 : /test2/testform.do
id : testid, password : testpwd
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-3] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 반환함
2853ms 소요됨
{중간 생략}
[http-nio-8080-exec-4] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 가져옴
PreparedStatement를 생성함
Query[sql : 'SELECT MBR_ID, MBR_PWD, MBR_PWD_SALT, MBR_NM FROM MBR_ACCOUNT_TB WHERE MBR_ID=?', parameters : ['testid']]를 실행함
PreparedStatement를 종료함
testWhileProc : PreparedStatement를 생성함
testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-4] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 반환함
4476ms 소요됨

첫번째 웹 브라우저의 스레드 명은 "http-nio-8080-exec-3"이고 두번째 웹 브라우저는 "http-nio-8080-exec-4"입니다.

"로그인"버튼을 먼저 누른 첫번째 웹 브라우저가 먼저 커넥션을 가져갑니다.

[http-nio-8080-exec-3] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 가져옴

그리고 쿼리를 실행합니다.

PreparedStatement를 생성함
PreparedStatement를 종료함
testWhileProc : PreparedStatement를 생성함

두번째 웹 브라우저가 커넥션을 요청하지만 커넥션이 없어 대기합니다.

[http-nio-8080-exec-4] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함

첫번째 웹 브러우저가 쿼리를 끝내고 커넥션을 반환합니다.

testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-3] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 반환함
2853ms 소요됨

두번째 웹 브라우저가 먼저 커넥션을 가져갑니다.

[http-nio-8080-exec-4] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@3f865f5c]를 가져옴

이처럼 커넥션 풀을 대기하게 하였습니다.

그렇지만, 쿼리 문제로 인해 커넥션이 반환이 되지 않는다면 무한 대기로 되기때문에 문제가 있습니다.

이어서 대기시간을 설정하여 나올 수 있게 처리하겠습니다.

728x90
반응형