Java 프레임워크 만들기 - JSP

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

carrotweb 2021. 6. 23. 20:39
728x90
반응형

"DriverManager.getConnection()"에서는 "jdbc:apache:commons:dbcp:maria"의 URL만으로 데이터베이스 커넥션 풀(Pool)을 가져오는 방법은 매니저 드라이버(DriverManager)에 로드되어 등록된 드라이버 중에서 URL에서 풀(Pool) 명을 추출해 풀(Pool)명으로 등록된 풀(Pool)를 찾고 풀((Pool)에서 커넥션 객체를 가져옵니다.

다음은 매니저 드라이버(DriverManager)의 "getConnection()"메소드 소스의 일부 부분입니다.

for(DriverInfo aDriver : registeredDrivers) {
	Connection con = aDriver.driver.connect(url, info);
}

"registeredDrivers"는 매니저 드라이버(DriverManager)가 드라이버를 보관하는 배열입니다.

다음은 풀링 드라이버(PoolingDriver)의 소스의 일부 부분입니다.

public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";

public boolean acceptsURL(final String url) throws SQLException {
	return url == null ? false : url.startsWith(URL_PREFIX);
}

public Connection connect(final String url, final Properties info) throws SQLException {
	if (acceptsURL(url)) {
		final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
		final Connection conn = pool.borrowObject();
	}
}

public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
	final ObjectPool<? extends Connection> pool = pools.get(name);
}

"acceptsURL()"메소드 처럼 URL은 무조건 "jdbc:apache:commons:dbcp:"으로 시작되어야 합니다.

URL에서 "jdbc:apache:commons:dbcp:"이후 문자열를 가져와 "registerPool()"메소드로 등록한 풀(Pool)중에서 풀(Pool) 명이 같은 풀(Pool)를 가져옵니다.

풀(Pool)에서 커넥션을 가져(borrowObject)옵니다.

이어서 아파치 공통 데이터베이스 커넥션 풀(Apache Commons DBCP)를 이용하여 적용하겠습니다.

4. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.home.project.test2.listener.TestServletContextListener"의 "contextInitialized()"메소드에 있는 "databaseMappingMap"의 인자를 "TestDatabasePoolManager"에서

// DBId-Database 맵
Map<String, TestDatabasePoolManager> databaseMappingMap = new HashMap<String, TestDatabasePoolManager>();

String databaseManagers = sce.getServletContext().getInitParameter("DatabaseManager");
if (databaseManagers != null && !databaseManagers.trim().isEmpty()) {
	String[] databaseManagerPaths = databaseManagers.trim().split("\n|,|;|\\s+");
	for (String databaseManagerPath : databaseManagerPaths) {
		databaseManagerPath = databaseManagerPath.trim();
		if (!databaseManagerPath.isEmpty()) {
			TestDatabasePoolManager testDatabaseManager = new TestDatabasePoolManager(databaseManagerPath);
			if (testDatabaseManager.isInitDatabasePool()) {
				databaseMappingMap.put(testDatabaseManager.getId(), testDatabaseManager);
			}
		}
	}
}

"TestDatabaseConectionPool"로 수정합니다.

// DBId-Database 맵
Map<String, TestDatabaseConectionPool> databaseMappingMap = new HashMap<String, TestDatabaseConectionPool>();

String databaseManagers = sce.getServletContext().getInitParameter("DatabaseManager");
if (databaseManagers != null && !databaseManagers.trim().isEmpty()) {
	String[] databaseManagerPaths = databaseManagers.trim().split("\n|,|;|\\s+");
	for (String databaseManagerPath : databaseManagerPaths) {
		databaseManagerPath = databaseManagerPath.trim();
		if (!databaseManagerPath.isEmpty()) {
			TestDatabaseConectionPool testDatabaseManager = new TestDatabaseConectionPool(databaseManagerPath);
			if (testDatabaseManager.isInitDatabasePool()) {
				databaseMappingMap.put(testDatabaseManager.getId(), testDatabaseManager);
			}
		}
	}
}

 

"contextInitialized()"메소드에서 DAO를 "databaseMappingMap"의 형 변환을 "TestDatabasePoolManager"에서

if (repositoryeInstance != null) {
	Field[] Fields = findClass.getDeclaredFields();
	for (Field field : Fields) {
		TestAnnAutowired testAnnAutowired = field.getAnnotation(TestAnnAutowired.class);
		TestAnnQualifier testAnnQualifier = field.getAnnotation(TestAnnQualifier.class);
		if (testAnnAutowired != null && testAnnQualifier != null) {
			System.out.println("[Repository Field] " + field.getType().getName() + " 타입의 " + field.getName() + " 맴버 필드을 찾음");
			TestDatabasePoolManager testDatabaseManager = (TestDatabasePoolManager) databaseMappingMap.get(testAnnQualifier.value());
			if (testDatabaseManager != null) {
				field.setAccessible(true);
				try {
					field.set(repositoryeInstance, testDatabaseManager);
					System.out.println("[Repository Field] " + field.getName() + "에 의존성 주입을 처리함");
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
					System.out.println("[Repository Field] " + field.getName() + "에 의존성 주입을 처리하지 못함");
				}
			}
		}
	}
}

"TestDatabaseConectionPool"로 수정합니다.

if (repositoryeInstance != null) {
	Field[] Fields = findClass.getDeclaredFields();
	for (Field field : Fields) {
		TestAnnAutowired testAnnAutowired = field.getAnnotation(TestAnnAutowired.class);
		TestAnnQualifier testAnnQualifier = field.getAnnotation(TestAnnQualifier.class);
		if (testAnnAutowired != null && testAnnQualifier != null) {
			System.out.println("[Repository Field] " + field.getType().getName() + " 타입의 " + field.getName() + " 맴버 필드을 찾음");
			TestDatabaseConectionPool testDatabaseManager = (TestDatabaseConectionPool) databaseMappingMap.get(testAnnQualifier.value());
			if (testDatabaseManager != null) {
				field.setAccessible(true);
				try {
					field.set(repositoryeInstance, testDatabaseManager);
					System.out.println("[Repository Field] " + field.getName() + "에 의존성 주입을 처리함");
				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
					System.out.println("[Repository Field] " + field.getName() + "에 의존성 주입을 처리하지 못함");
				}
			}
		}
	}
}

 

"contextDestroyed()"메소드에서 "databaseMappingMap"의 인자를 "TestDatabasePoolManager"에서

public void contextDestroyed(ServletContextEvent sce)  {
	System.out.println("ServletContext 파괴 실행"); 
	@SuppressWarnings("unchecked")
	Map<String, TestDatabasePoolManager> databaseMappingMap = (Map<String, TestDatabasePoolManager>)sce.getServletContext().getAttribute("DatabaseMappingMap");
	Iterator<String> keyIter = databaseMappingMap.keySet().iterator();
	while (keyIter.hasNext()) {
		String dbId = (String) keyIter.next();
		TestDatabasePoolManager testDatabaseManager = databaseMappingMap.get(dbId);
		testDatabaseManager.destroyDatabasePool();
	}
}

"TestDatabaseConectionPool"로 수정합니다.

public void contextDestroyed(ServletContextEvent sce)  {
	System.out.println("ServletContext 파괴 실행"); 
	@SuppressWarnings("unchecked")
	Map<String, TestDatabaseConectionPool> databaseMappingMap = (Map<String, TestDatabaseConectionPool>)sce.getServletContext().getAttribute("DatabaseMappingMap");
	Iterator<String> keyIter = databaseMappingMap.keySet().iterator();
	while (keyIter.hasNext()) {
		String dbId = (String) keyIter.next();
		TestDatabaseConectionPool testDatabaseManager = databaseMappingMap.get(dbId);
		testDatabaseManager.destroyDatabasePool();
	}
}

 

5. "test2" 프로젝트의 "Java Resources/src/main/java"에서 "com.home.project.test2.dao.TestLoginDaoImpl.java"의 멤버 필드 형을 "TestDatabasePoolManager"에서

@TestAnnAutowired
@TestAnnQualifier("databaseMaria")
private TestDatabasePoolManager testDatabaseManager;

"TestDatabaseConectionPool"로 수정합니다.

@TestAnnAutowired
@TestAnnQualifier("databaseMaria")
private TestDatabaseConectionPool testDatabaseManager;

 

위에서 설명한 것 처럼 매니저 드라이버(DriverManager)에 로드되어 등록되어 있는 드라이브를 출추해보기 위해 "contextInitialized()"메소드 끝에 추가합니다.

Enumeration<Driver> drivers = DriverManager.getDrivers();
if (drivers.hasMoreElements()) {
	while (drivers.hasMoreElements()) {
		Driver driver = drivers.nextElement();
		System.out.println("드라이버 : " + driver.toString());
		if (driver.getClass().getName().equals("org.apache.commons.dbcp2.PoolingDriver")) {
			PoolingDriver poolingDriver = (PoolingDriver)driver;
			String[] poolNames = poolingDriver.getPoolNames();
			for (String poolName : poolNames) {
				System.out.println("드라이버 풀 명 : " + poolName);
				try {
					@SuppressWarnings("unchecked")
					ObjectPool<Connection> pool = (ObjectPool<Connection>)poolingDriver.getConnectionPool(poolName);
					Connection connection = (Connection)pool.borrowObject();
					System.out.println("데이터베이스 커넥션 객체[" + connection.toString() + "]를 가져옴");
					pool.returnObject(connection);
					System.out.println("데이터베이스 커넥션 객체[" + connection.toString() + "]를 반환함");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}
드라이버 : org.mariadb.jdbc.Driver@183f7fc5
드라이버 : org.apache.commons.dbcp2.PoolingDriver@5a0ddf89
드라이버 풀 명 : maria
데이터베이스 커넥션 객체[674333676, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴
데이터베이스 커넥션 객체[674333676, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함

"Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭한 다음 "Console"탭을 보면 2개의 드라이버가 로드되어 등록된 것을 알 수 있습니다.

 

그리고 풀링 드라이버(PoolingDriver)에서 "maria"라는 풀(Pool)이 있고 풀(Pool)에서 커넥션 객체를 가져(borrowObject)오고 반환(returnObject)해 보았습니다.

이부분은 프로세스에 대해 설명을 하기위해 작성된 코드임으로 테스트해보시고 삭제하시면 됩니다.

다음에 "GenericObjectPool"를 이용해서 풀(Pool)를 생성해 보도록 하겠습니다.

6. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다.

"Console"탭을 보면 아파치 공통 데이터베이스 커넥션 풀이 생성된 것을 확인할 수 있습니다.

[Console]

데이터베이스 프로퍼티를 읽음
데이터베이스 드라이버(class org.mariadb.jdbc.Driver)가 로딩됨
데이터베이스 커넥션 풀을 생성 시작
데이터베이스 커넥션 풀을 생성함
[Repository] com.home.project.test2.dao.TestLoginDaoImpl 인스턴스를 생성함
[Repository Field] com.home.project.test2.database.TestDatabaseConectionPool 타입의 testDatabaseManager 맴버 필드을 찾음

 

두개의 웹 브라우저에서 "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-6] 데이터베이스 커넥션 객체[995050938, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴
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-7] 데이터베이스 커넥션 객체[1578250359, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴
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-6] 데이터베이스 커넥션 객체[995050938, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함
3091ms 소요됨
{중간 생략}
testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-7] 데이터베이스 커넥션 객체[1578250359, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함
2695ms 소요됨

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

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

[http-nio-8080-exec-6] 데이터베이스 커넥션 객체[995050938, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴

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

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

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

[http-nio-8080-exec-7] 데이터베이스 커넥션 객체[1578250359, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴

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

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

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

testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-6] 데이터베이스 커넥션 객체[995050938, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함
3091ms 소요됨

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

testWhileProc : PreparedStatement를 종료함
[http-nio-8080-exec-7] 데이터베이스 커넥션 객체[1578250359, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함
2695ms 소요됨
728x90
반응형