Java 프레임워크 만들기 - JSP

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

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

커넥션 풀에서 웹 사이트에 접속되는 사용자가 많아져 처리해야 하는 쿼리가 많아지거나 쿼리 문제로 인해 커넥션이 반환이 되지 않을 경우 커넥션을 무한 대기하는 문제가 발생할 수 있습니다. 그래서 커넥션 최대 대기 시간(maxWait)을 설정하여 나올 수 있게 처리하겠습니다.

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=1
maxWait=1000

"maxWait"는 커넥션 풀에서 사용 기능한 커넥션이 없을 때 커넥션을 최대로 대기하는 시간(밀리초)

테스트를 위해 최초 생선되는 커넥션 수(initialSize)는 1개로, 커넥션 최대 대기 시간(maxWait)을 1초(1000ms)로 설정합니다.

2. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.hom.project.test2.database"에 있는"TestDatabasePoolManager.java"의 "loadProperties()"메소드에 커넥션 대시 시간(maxWait)을 추가하고 "initialSize"와 "maxWait"가 없을 경우 기본값으로 설정하게 합니다.

/**
 * 데이터베이스 매니저 프로퍼티를 로드하고 검증합니다.
 * @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 maxWait = databaseInfo.getProperty("maxWait");
			if (maxWait == null || maxWait.trim().isEmpty()) {
				System.out.println("데이터베이스 프로퍼티에 maxWait가 없어 기본값(무제한)으로 설정합니다.");
				databaseInfo.setProperty("maxWait", "0");
			}

			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;
}

"initialSize"는 기본값으로 커넥션 수를 8개로 설정합니다.

"maxWait" 는 기본값으로 무제한 대기로 0으로 설정합니다.

커넥션를 가져오기전에 시간(startWaitTime)과 컨넥션이 없어 대기하는 시간(endWaitTime)을 비교하여 최대 대기 시간으로 초과하면 커넥션을 가져오지 않고 리턴하게 합니다.

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

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

	if (initDatabasePool) {
		String maxWait = databaseInfo.getProperty("maxWait");
		int maxWaitTime = Integer.parseInt(maxWait);

		long startWaitTime = System.currentTimeMillis();

		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() + "] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함");

				long endWaitTime = System.currentTimeMillis();
				if (maxWaitTime > 0 && (endWaitTime - startWaitTime) >= maxWaitTime) {
					System.out.println("[" + Thread.currentThread().getName() + "] 대기 시간(" + (endWaitTime - startWaitTime) + "ms)이 초과되어 데이터베이스 커넥션 객체 가져오기를 중지함");
					break;
				} else {
					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;
}

 

4. "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-4] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@49755752]를 가져옴
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-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 사용하지 않는 데이터베이스 커넥션 객체가 없어 대기함
[http-nio-8080-exec-5] 대기 시간(1097ms)이 초과되어 데이터베이스 커넥션 객체 가져오기를 중지함
[http-nio-8080-exec-5] 데이터베이스 커넥션 객체를 가져오지 못함
1098ms 소요됨
ServletRequest에 속성 추가 - 속성명 : errorMessage, 속성 값 : 아이디/패스워드가 정확하지 않습니다.
[RequestMapping] URL : /loginprocess.do요청을 com.home.project.test2.controller.TestLoginController@35977eda의 loginProcess메소드로 실행함
{중간 생략}
testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-4] 데이터베이스 커넥션 객체[com.home.project.test2.database.TestDatabaseConnection@49755752]를 반환함
2417ms 소요됨

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

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

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

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

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

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

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

두번째 웹 브러우저가 커넥션 최대 대기 시간을 초과하여 커넥션 객체를 가져오는 것을 중지합니다.

[http-nio-8080-exec-5] 대기 시간(1097ms)이 초과되어 데이터베이스 커넥션 객체 가져오기를 중지함
[http-nio-8080-exec-5] 데이터베이스 커넥션 객체를 가져오지 못함
1098ms 소요됨
ServletRequest에 속성 추가 - 속성명 : errorMessage, 속성 값 : 아이디/패스워드가 정확하지 않습니다.

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

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

이처럼 커넥션 풀에 대한 최대 대기 시간을 설정할 수 있어 무한 대기의 문제점을 해결할 수 있었습니다.

728x90
반응형