Java 프레임워크 만들기 - JSP

자바 아파치 공통 데이터베이스 커넥션 풀 3 - Java Apache Commons Database Connection Pool (Commons DBCP2), 이클립스(Eclipse)

carrotweb 2021. 6. 24. 22:40
728x90
반응형

이번에는 "GenericObjectPool"를 효율적으로 관리할 수 있게 "PoolableConnectionFactory"와 "GenericObjectPoolConfig"의 설정들을 설명하고 추가하겠습니다.

커넥션 유효 검사 쿼리 및 응답 시간

setValidationQuery()

풀(Pool)에서 관리되는 커넥션들이 유효한지 검사하기 위한 쿼리문을 설정합니다. 기본적으로 공백입니다.

String validationQuery = "select 1";

// 커넥션이 유효한지 검사하기 위한 쿼리
poolableConnectionFactory.setValidationQuery(validationQuery);

다음은 데이터베이스별 유효 검사 쿼리문입니다.

"Oracle" : "select 1 from dual"

"Microsoft SQL Server" : "select 1'

"MySQL" or "MariaDB" : "select 1"

setValidationQueryTimeout()

풀(Pool)에서 관리되는 커넥션들이 유효한지 검사하는 쿼리가 실행되고 응답을 기다리는 시간을 설정합니다. 기본적으로 "-1"이고 단위는 초(seconds)입니다. "-1"이면 응답에 대한 제한 시간이 없습니다.

int validationQueryTimeoutSeconds = -1;

// 커넥션이 유효한지 검사하는 쿼리가 실행되고 응답을 기다리는 시간
poolableConnectionFactory.setValidationQueryTimeout(validationQueryTimeoutSeconds);

 

 

커넥션 구문 풀링

setPoolStatements()

커넥션마다 "PreparedStatement"을 풀링할지 여부를 설정합니다. 기본적으로 "false"입니다.

boolean poolStatements = true;

// 커넥션마다 PreparedStatement 풀링 여부
poolableConnectionFactory.setPoolStatements(poolStatements);

 

setMaxOpenPreparedStatements()

커넥션마다 최대로 풀링할 "PreparedStatement"의 수를 설정합니다. 기본적으로 "8"입니다.

int maxOpenPreparedStatements = 30; // DEFAULT_MAX_TOTAL_PER_KEY = 8

// 커넥션마다 최대로 풀링할 PreparedStatement의 개수
poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);

 

 

커넥션 라이프

setMaxConnLifetimeMillis()

커넥션의 생성후 최대로 이용 가능한 시간을 설정합니다. 기본적으로 "-1"입니다. 단위는 밀리초(Millisecond)입니다. "-1"이면 제한이 없습니다.

long maxConnLifetimeMillis = -1;

// 커넥션의 생성후 최대로 이용 가능한 시간
poolableConnectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis);

 

 

커넥션 수 관리

setMaxTotal()

풀(Pool)에서 동시에 사용할 수 있는 최대 커넥션의 수를 설정합니다. 기본적으로 "8"입니다.

int maxTotal = 8; // DEFAULT_MAX_TOTAL = 8

// 동시에 사용할 수 있는 최대 커넥션 개수
genericObjectPoolConfig.setMaxTotal(maxTotalConnection);

 

setMaxIdle()

풀(Pool)에 커넥션이 반환될 때 최대로 유지될 수 있는 커넥션의 수를 설정합니다. 기본적으로 "8"입니다.

int maxIdle = 8; // DEFAULT_MAX_IDLE = 8;

// 커넥션이 반환될 때 최대로 유지될 수 있는 커넥션 개수
genericObjectPoolConfig.setMaxIdle(maxIdleConnection);

 

setBlockWhenExhausted()

풀(Pool)에서 관리되는 모든 커넥션들이 사용중일 때 커넥션이 반환될 때까지 대기할지 여부를 설정합니다. 기본적으로 "true"입니다.

boolean blockWhenExhausted = true; // DEFAULT_BLOCK_WHEN_EXHAUSTED = true

// 풀에서 관리되는 모든 커넥션들이 사용중일 때 커넥션이 반환될 때까지 대기 여부 
genericObjectPoolConfig.setBlockWhenExhausted(blockWhenExhausted);

 

setMaxWaitMillis()

풀(Pool)에서 사용되지 않는 커넥션을 가져오기 위해 대기하기 최대 시간을 설정합니다. 기본적으로 "-1"입니다. 단위는 밀리초(Millisecond)입니다. "-1"이면 무제한 대기합니다. "setBlockWhenExhausted()"가 "true"일때 적용됩니다.

long maxWaitMillis = -1L; // DEFAULT_MAX_WAIT_MILLIS = -1L

// 커넥션을 가져오기 위해 대기하기 최대 시간
genericObjectPoolConfig.setMaxWaitMillis(maxWaitMills);

 

setMinIdle()

풀(Pool)에 커넥션이 반환될 때 최소로 유지될 수 있는 커넥션의 수를 설정합니다. 기본적으로 "0"입니다.

int minIdleConnection = 0;

// 커넥션이 반환될 때 최소로 유지될 수 있는 커넥션 개수
genericObjectPoolConfig.setMinIdle(minIdleConnection);

 

 

커넥션 유효성 검사

setTestOnBorrow()

풀(Pool)에서 커넥션을 가져올 때 커넥션이 유효한가를 검사할지 여부를 설정합니다. 기본적으로 "false"입니다.

boolean testOnBorrow = false; // DEFAULT_TEST_ON_BORROW = false

// 풀에서 커넥션을 가져올 때 커넥션이 유효한지 검사하는 여부
genericObjectPoolConfig.setTestOnBorrow(testOnBorrow);

 

setTestOnReturn()

풀(Pool)에 커넥션을 반환할 때 커넥션이 유효한가를 검사할지 여부를 설정합니다. 기본적으로 "false"입니다.

boolean testOnReturn = false; // DEFAULT_TEST_ON_RETURN = false

// 풀에 커넥션을 반환할 때 커넥션이 유효한지 검사하는 여부
genericObjectPoolConfig.setTestOnReturn(testOnReturn);

 

setTestOnCreate()

풀(Pool)에서 커넥션을 생성한 후 커넥션이 유효한가를 검사할지 여부를 설정합니다. 기본적으로 "false"입니다.

boolean testOnCreate = false; // DEFAULT_TEST_ON_CREATE = false

// 풀에서 커넥션을 생성한 후 커넥션이 유효한지 검사하는 여부
genericObjectPoolConfig.setTestOnCreate(testOnCreate);

 

setTestWhileIdle()

풀(Pool)에서 사용되지 않는 커넥션들에 대해 유효한가를 검사할지 여부를 설정합니다. 기본적으로 "false"입니다. 커넥션이 유효하지 않으면 제거됩니다.

boolean testWhileIdle = true; // DEFAULT_TEST_WHILE_IDLE = false

// 풀에서 사용되지 않는 커넥션들에 대해 유효한지 검사 여부 (유효하지 않으면 제거)
genericObjectPoolConfig.setTestWhileIdle(testWhileIdle);

 

커넥션 유효성 검사를 모두 하게되면 시스템이 지연될 수 있습니다. 그레서 "setTestOnBorrow()"과 "setTestOnReturn()", "setTestOnCreate()"은 "false"로 설정하고, "setTestWhileIdle()"은 "true"로 설정하는것이 좋고 커넥션이 끊어지는 현상도 막을 수 있습니다.

커넥션 추출(Evictor 스레드 설정)

setEvictionPolicyClassName()

풀(Pool)에서 커넥션을 추출할때 이용할 추출 정책 클래스 명을 설정합니다. 기본적으로 "DefaultEvictionPolicy"를 이용합니다.

String evictionPolicyClassName = DefaultEvictionPolicy.class.getName(); // DefaultEvictionPolicy.class.getName()

// 추출 정책 클래스 명
genericObjectPoolConfig.setEvictionPolicyClassName(evictionPolicyClassName);

 

다음은 "DefaultEvictionPolicy"의 추출 대상 선정 기준입니다.

public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
	if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() && config.getMinIdle() < idleCount)
		|| config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
		return true;
	}
	return false;
}

풀(Pool)에서 사용되지 않는 커넥션이 추출되기 전에 최소 유지 시간을 초과하고 풀(Pool)에 커넥션이 반환될 때 최대로 유지할 커넥션 개수보다 현재 커넥션 개수가 많으면 추출 대상이 됩니다. 또는 풀(Pool)에서 사용되지 않는 커넥션이 최소 유지 시간을 초과되면 추출 대상이 됩니다.

setTimeBetweenEvictionRunsMillis()

풀(Pool)에서 사용되지 않는 커넥션들에 대해 추출하는 Evictor 스레드의 검사 주기를 설정합니다. 기본적으로 "-1"입니다. 단위는 밀리초(Millisecond)입니다. "-1"이면 검사하지 않습니다.

long timeBetweenEvictionRunsMillis = -1L; // DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L

// 풀에서 사용되지 않는 커넥션들에 대해 추출하는 Evictor 스레드의 검사 주기
genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

Evictor 스레드는 풀(Pool)에 있는 커넥션들을 정리하는 별도의 스레드로 실행됩니다.

setNumTestsPerEvictionRun()

Evictor 스레드의 검사 주기 때마다 풀(Pool)에서 사용되지 않는 커넥션 중 한번에 검사할 커넥션의 수를 설정합니다. 기본적으로 "3"입니다.

int numTestsPerEvictionRun = 3; // DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3

// Evictor 스레드의 검사 주기 때마다 풀에서 사용되지 않는 커넥션 중 검사할 커넥션 수
genericObjectPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);

 

setMinEvictableIdleTimeMillis()

풀(Pool)에서 사용되지 않는 커넥션이 최소로 유지될 수 있는 시간을 설정합니다. 기본적으로 "1800000"입니다. 단위는 밀리초(Millisecond)입니다. 최소 유지 시간이 초과되면 제거됩니다.

long minEvictableIdleTimeMillis = 1000L * 60L * 30L; // DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L

// 풀에서 사용되지 않는 커넥션이 최소로 유지될 수 있는 시간 (최소 유지 시간이 초과되면 제거)
genericObjectPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);

 

setSoftMinEvictableIdleTimeMillis()

풀(Pool)에서 사용되지 않는 커넥션이 추출되기 전에 최소로 유지될 수 있는 시간을 설정합니다. 기본적으로 "-1"입니다. 단위는 밀리초(Millisecond)입니다.

long softMinEvictableIdleTimeMillis = -1; // DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1

// 풀에서 사용되지 않는 커넥션이 추출되기 전에 최소로 유지될 수 있는 시간
genericObjectPoolConfig.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);

 

Evictor 스레드가 실행되면 풀(Pool)에 락(lock)을 한 후 실행되기 때문에 검사 주기가 짧으면 시스템이 지연될 수 있습니다. Evictor 스레드 검사 주기를 길게하는 것이 좋습니다.

또한 "setNumTestsPerEvictionRun()"을 크게 설정하면 Evictor 스레드 검사 주기때마다 검사해야할 커넥션 개수가 많아져 시스템이 지연될 수 있습니다. "setNumTestsPerEvictionRun()"는 작게 설정하는 것이 좋습니다.

위에서 설명한 추가 설정들을 적용하겠습니다.

1. "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=password
initialSize=8
maxWaitMillis=-1
maxTotal=8
maxIdle=8
validationQuery=select 1
validationQueryTimeoutSeconds=-1
poolStatements=True
maxOpenPreparedStatements=40
maxConnLifetimeMillis=-1
blockWhenExhausted=True
minIdle=0
testOnBorrow=False
testOnReturn=False
testOnCreate=False
testWhileIdle=True
evictionPolicyClassName=org.apache.commons.pool2.impl.DefaultEvictionPolicy
timeBetweenEvictionRunsMillis=-1
numTestsPerEvictionRun=3
minEvictableIdleTimeMillis=1800000
softMinEvictableIdleTimeMillis=-1

 

2. 프로퍼티의 키가 값이 없을 경우 프로퍼티 키와 기본값을 설정할 수 있게 "setPropertyInit()"메소드를 생성합니다.

/**
 * 프로퍼티 키가 비어 있을 경우에 초기값을 설정합니다.
 * @param propertyKey 프로퍼티 키
 * @param defaultValue 포로퍼티 기본값
 */
private void setPropertyInit(String propertyKey, String defaultValue) {
	String propertyValue = databaseInfo.getProperty(propertyKey);
	if (propertyValue == null || propertyValue.trim().isEmpty()) {
		if (defaultValue == null || defaultValue.trim().isEmpty()) {
			System.out.println("데이터베이스 프로퍼티에 " + propertyKey + "가 없어 기본값(공백)으로 설정합니다.");
			defaultValue = "";
		} else {
			System.out.println("데이터베이스 프로퍼티에 " + propertyKey + "가 없어 기본값(" + defaultValue + ")으로 설정합니다.");
		}
		databaseInfo.setProperty(propertyKey, defaultValue);
	}
}

 

3. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.hom.project.test2.database"에 있는"TestDatabasePoolManager.java"의 "loadProperties()"메소드에 설정들을 추가하고 기본값을 설정합니다.

/**
 * 데이터베이스 매니저 프로퍼티를 로드하고 검증합니다.
 * @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;
			}
			setPropertyInit("initialSize", "8");
			setPropertyInit("maxWaitMillis", "0");
			setPropertyInit("maxTotal", "8");
			setPropertyInit("maxIdle", "8");
			setPropertyInit("validationQuery", "");
			setPropertyInit("validationQueryTimeoutSeconds", "-1");
			setPropertyInit("poolStatements", "False");
			setPropertyInit("maxOpenPreparedStatements", "-1");
			setPropertyInit("maxConnLifetimeMillis", "-1");
			setPropertyInit("blockWhenExhausted", "True");
			setPropertyInit("minIdle", "0");
			setPropertyInit("minIdleConnection", "0");
			setPropertyInit("testOnBorrow", "False");
			setPropertyInit("testOnReturn", "False");
			setPropertyInit("testOnCreate", "False");
			setPropertyInit("testWhileIdle", "False");
			setPropertyInit("evictionPolicyClassName", "org.apache.commons.pool2.impl.DefaultEvictionPolicy");
			setPropertyInit("timeBetweenEvictionRunsMillis", "-1");
			setPropertyInit("numTestsPerEvictionRun", "3");
			setPropertyInit("minEvictableIdleTimeMillis", "1800000");
			setPropertyInit("softMinEvictableIdleTimeMillis", "-1");

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

 

4. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.hom.project.test2.database"에 있는"TestDatabasePoolManager.java"의 "initDatabasePool()"메소드에 "PoolableConnectionFactory"의 설정을 추가합니다.

	String validationQuery = databaseInfo.getProperty("validationQuery");
	String validationQueryTimeoutSecondsInfo = databaseInfo.getProperty("validationQueryTimeoutSeconds");
	int validationQueryTimeoutSeconds = Integer.parseInt(validationQueryTimeoutSecondsInfo);

	// 커넥션이 유효한지 검사하기 위한 쿼리
	poolableConnectionFactory.setValidationQuery(validationQuery);
	// 커넥션이 유효한지 검사하는 쿼리가 실행되고 응답을 기다리는 시간
	poolableConnectionFactory.setValidationQueryTimeout(validationQueryTimeoutSeconds);

	String poolStatementsInfo = databaseInfo.getProperty("poolStatements");
	boolean poolStatements = Boolean.parseBoolean(poolStatementsInfo);
	String maxOpenPreparedStatementsInfo = databaseInfo.getProperty("maxOpenPreparedStatements");
	int maxOpenPreparedStatements = Integer.parseInt(maxOpenPreparedStatementsInfo);

	// 커넥션마다 PreparedStatement 풀링 여부
	poolableConnectionFactory.setPoolStatements(poolStatements);
	// 커넥션마다 최대로 풀링할 PreparedStatement의 개수
	poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);

	String maxConnLifetimeMillisInfo = databaseInfo.getProperty("maxConnLifetimeMillis");
	long maxConnLifetimeMillis = Long.parseLong(maxConnLifetimeMillisInfo);

	// 커넥션의 생성후 최대로 이용 가능한 시간
	poolableConnectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis);

 

"initDatabasePool()"메소드에 "GenericObjectPoolConfig"의 설정을 추가합니다.

	String blockWhenExhaustedInfo = databaseInfo.getProperty("blockWhenExhausted");
	boolean blockWhenExhausted = Boolean.parseBoolean(blockWhenExhaustedInfo);
	String minIdleConnection = databaseInfo.getProperty("minIdle");
	int minIdle = Integer.parseInt(minIdleConnection);

	// 풀에서 관리되는 모든 커넥션들이 사용중일 때 커넥션이 반환될 때까지 대기 여부 
	genericObjectPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
	// 커넥션이 반환될 때 최소로 유지될 수 있는 커넥션 개수
	genericObjectPoolConfig.setMinIdle(minIdle);

	// 풀에서 사용되지 않는 커넥션들에 대한 유효성 검사
	String testOnBorrowInfo = databaseInfo.getProperty("testOnBorrow");
	boolean testOnBorrow = Boolean.parseBoolean(testOnBorrowInfo);
	String testOnReturnInfo = databaseInfo.getProperty("testOnReturn");
	boolean testOnReturn = Boolean.parseBoolean(testOnReturnInfo);
	String testOnCreateInfo = databaseInfo.getProperty("testOnCreate");
	boolean testOnCreate = Boolean.parseBoolean(testOnCreateInfo);
	String testWhileIdleInfo = databaseInfo.getProperty("testWhileIdle");
	boolean testWhileIdle = Boolean.parseBoolean(testWhileIdleInfo);

	// 풀에서 커넥션을 가져올 때 커넥션이 유효한지 검사하는 여부
	genericObjectPoolConfig.setTestOnBorrow(testOnBorrow);
	// 풀에 커넥션을 반환할 때 커넥션이 유효한지 검사하는 여부
	genericObjectPoolConfig.setTestOnReturn(testOnReturn);
	// 풀에서 커넥션을 생성한 후 커넥션이 유효한지 검사하는 여부
	genericObjectPoolConfig.setTestOnCreate(testOnCreate);
	// 풀에서 사용되지 않는 커넥션들에 대해 유효한지 검사 여부 (유효하지 않으면 제거)
	genericObjectPoolConfig.setTestWhileIdle(testWhileIdle);

	// 풀에서 사용되지 않는 커넥션들에 대한 추출(제거)
	String evictionPolicyClassName = databaseInfo.getProperty("evictionPolicyClassName");
	String timeBetweenEvictionRunsMillisInfo = databaseInfo.getProperty("timeBetweenEvictionRunsMillis");
	long timeBetweenEvictionRunsMillis = Long.parseLong(timeBetweenEvictionRunsMillisInfo);
	String numTestsPerEvictionRunInfo = databaseInfo.getProperty("numTestsPerEvictionRun");
	int numTestsPerEvictionRun = Integer.parseInt(numTestsPerEvictionRunInfo);
	String minEvictableIdleTimeMillisInfo = databaseInfo.getProperty("minEvictableIdleTimeMillis");
	long minEvictableIdleTimeMillis = Long.parseLong(minEvictableIdleTimeMillisInfo);
	String softMinEvictableIdleTimeMillisInfo = databaseInfo.getProperty("softMinEvictableIdleTimeMillis");
	long softMinEvictableIdleTimeMillis = Long.parseLong(softMinEvictableIdleTimeMillisInfo);

	// 추출 정책 클래스 명 설정
	genericObjectPoolConfig.setEvictionPolicyClassName(evictionPolicyClassName);
	// 풀에서 사용되지 않는 커넥션들에 대해 추출하는 Evictor 스레드의 검사 주기 (-1이면 검사하지 않음)
	genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
	// Evictor 스레드의 검사 주기 때마다 풀에서 사용되지 않는 커넥션 중 검사할 커넥션 수
	genericObjectPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
	// 풀에서 사용되지 않는 커넥션이 최소로 유지될 수 있는 시간 (최소 유지 시간이 초과되면 제거)
	genericObjectPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
	// 풀에서 사용되지 않는 커넥션이 추출되기 전에 최소로 유지될 수 있는 시간
	genericObjectPoolConfig.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);

 

쿼리 실행 지연 테스트를 위해 "TestLoginDaoImpl.java"의 "selectMember()"메소드에서 커넥션을 반환("testDatabaseManager.closeConnection(connection)")하기 전에 프로시저(Procedure)를 특정 아이디에서만 실행되게 하는 코드가 있었습니다. 이제는 주석처리나 삭제하시면 됩니다.

728x90
반응형