Java 프레임워크 만들기 - JSP

자바 제네릭 메소드와 리플렉션을 이용한 자동 PreparedStatement 생성 2 - Java Generic Method Reflection PreparedStatement

carrotweb 2021. 7. 18. 19:45
728x90
반응형

두번째로 ResultSet 객체에서 한개의 행(row)에 한개의 컬럼(column)를 받아 전달하는 메소드인 executeQueryOneValue()메소드를 생성하겠습니다.

PreparedStatement에서 사용되는 쿼리문

SELECT COUNT(MBR_ID) AS CNT FROM MBR_ACCOUNT_TB WHERE

 

쿼리문처럼 조건 없이 전체를 카운트 할 수 있어 전달되는 객체가 없을 수 있습니다.

그래서 이전의 createPreparedStatement()메소드에서 전달되는 객체가 없는 경우를 위해 수정합니다.

/**
 * PreparedStatement 객체를 샹성합니다.
 * @param connection 데이터베이스 연결 객체
 * @param query 쿼리 문자열
 * @param parameterObject 파라미터 객체
 * @return PreparedStatement 객체
 */
private PreparedStatement createPreparedStatement(Connection connection, String query, Object parameterObject) {
	if (connection == null || (query == null || query.isEmpty())) {
		return null;
	}

	PreparedStatement preparedStatement = null;

	String preparedQuery = query;
	Matcher matcher = null;

	if (parameterObject != null) {
		// 쿼리문에서 파라메타(#{...})를 ?으로 변환
		Pattern pattern = Pattern.compile("(#\\{\\w+\\})");
		matcher = pattern.matcher(query);

		preparedQuery = matcher.replaceAll("?");
	}

	try {
		// 변환된 쿼리문으로 PreparedStatement 생성
		preparedStatement = connection.prepareStatement(preparedQuery);

		if (parameterObject != null) {
			Class<?> parameterObjectClass = parameterObject.getClass();
			Field[] Fields = parameterObjectClass.getDeclaredFields();

			// 쿼리문에서 파라메타(#{...})에 찾고 객체에서 동일한 이름의 맴버 필드를 찾아 PreparedStatement의 "?"에 대체할 값을 설정
			matcher = matcher.reset();

			int index = 1;
			while (matcher.find()) {
				String parameterName = matcher.group().replace("#{", "").replace("}", "");

				boolean isParameterValue = false;
				for (Field field : Fields) {
					if (parameterName.equals(field.getName())) {
						isParameterValue = setPreparedStatementParameter(preparedStatement, index++, field, parameterObject);
						break;
					}
				}

				if (!isParameterValue) {
					System.out.println("객체에서 " + parameterName + "와 같은 맴버 필드를 찾지 못함");
					preparedStatement.close();
					preparedStatement = null;
					break;
				}
			}
		}
	} catch (SQLException e) {
		preparedStatement = null;
		e.printStackTrace();
	}

	return preparedStatement;
}

 

데이터베이스 컨넥션 객체를 가져옵니다.

Connection connection = getConnection();

 

createPreparedStatement()메소드를 이용하여 PreparedStatement를 생성합니다.

 

PreparedStatement preparedStatement = createPreparedStatement(connection, query, parameterObject);
if (preparedStatement != null) {
	System.out.println("PreparedStatement를 생성함");
} else {
	System.out.println("PreparedStatement를 생성하지 못함");
}

자세한 설명은 "자바 패턴 매치와 리플렉션을 이용한 자동 PreparedStatement 생성 1"를 참조하세요.

생성된 PreparedStatement를 실행하여 ResultSet 객체를 받습니다.

ResultSet resultSet = preparedStatement.executeQuery();

 

ResultSet 객체에서 전체 행(row) 수가 한 개이고 컬럼(column)이 한 개일 때만 처리하게 합니다.

int resultSetCount = getResultSetCount(resultSet);
if (resultSetCount == 1) {
	ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
	if (resultSetMetaData.getColumnCount() == 1) {
	}
}

getMetaData()메소드는 ResultSet 객체에서 메타 데이터(행(row)의 컬럼(column)의 정보)를 가져옵니다.

메타 데이터에서 컬럼(column)의 수가 한 개인지 확인합니다.

ResultSet 객체에서 리턴되는 클래스에 맞게 값을 가져옵니다.

T resultObject = null;
String resultTypeClassName = resultTypeClass.getName();

if (resultTypeClassName.equals(String.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getString(index));
} else if (resultTypeClassName.equals(Integer.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getInt(index));
} else if (resultTypeClassName.equals(Short.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getShort(index));
} else if (resultTypeClassName.equals(Float.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getFloat(index));
} else if (resultTypeClassName.equals(Long.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getLong(index));
} else if (resultTypeClassName.equals(Boolean.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getBoolean(index));
} else if (resultTypeClassName.equals(Byte.class.getName())) {
	resultObject = resultTypeClass.cast(resultSet.getByte(index));
} else if (resultTypeClassName.equals(Date.class.getName())) {
	resultObject = resultTypeClass.cast(new Date(resultSet.getTimestamp(index).getTime()));
}

이미 ResultSet 객체에서 리턴되는 클래스에 맞게 값을 가져왔는데 케스팅을 더한 이유는 리턴되는 값을 객체가 제네릭(T)이기 때문에 케스팅을 하지 않으면 다음과 같은 오류가 발생합니다.

Type mismatch: cannot convert from {resultTypeClass} to T

{resultTypeClass}는 Integer, Short, Float, Long, Double, Boolean, Byte, Character과 같은 참조형 타입인 래퍼 클래스(Wrapper class)입니다. 케스팅을 해도 문제는 발생하지 않습니다.

전달 받고 싶은 타입을 기본형(자료형)타입이 아닌 참조형 타입인 래퍼 클래스(Wrapper class)를 사용하시면 됩니다.

int -> Integer, shot -> Short, float -> Float, long -> Long, bouble -> Double, boolean -> Boolean, byte -> Byte, char -> Character, String은 그냥 사용하시면 됩니다.

PreparedStatement를 종료합니다.

preparedStatement.close();

 

데이터베이스 컨넥션 객체를 반환하고 결과를 리턴합니다.

closeConnection(connection);

return resultObject;

 

 

1. "TestDatabaseDataSource.java"나 "TestDatabaseJNDI.java", "TestDatabaseConectionPool.java", "TestDatabasePoolManager.java", "TestDatabaseManager.java"에 추가하시면 됩니다.

executeQueryOneValue()메소드에서 전달되는 클래스로 결과를 생성하여 리턴하기 위해 제네릭 메소드로 생성합니다.

추가되는 전체 소스입니다.

/**
 * 쿼리문(SELECT)을 실행한다.
 * @param query 쿼리 문자열
 * @param parameterObject 파라미터 객체
 * @param resultTypeClass 결과 클래스
 * @return 결과 값
 */
public <T> T executeQueryOneValue(String query, Object parameterObject, Class<T> resultTypeClass) {
	T resultObject = null;

	long startTime = System.currentTimeMillis();

	Connection connection = getConnection();

	PreparedStatement preparedStatement = createPreparedStatement(connection, query, parameterObject);
	if (preparedStatement != null) {
		System.out.println("PreparedStatement를 생성함");
		try {
			ResultSet resultSet = preparedStatement.executeQuery();
			int resultSetCount = getResultSetCount(resultSet);
			if (resultSetCount == 1) {
				if (resultSet.next()) {
					ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
					if (resultSetMetaData.getColumnCount() == 1) {
						resultObject = getResultSetColumnValue(resultSet, 1, resultTypeClass);
					}
				}
				System.out.println("Query[" + preparedStatement.toString() + "]를 실행함");
			} else {
				System.out.println("Query[" + preparedStatement.toString() + "]를 실행한 결과 row가 " + resultSetCount + "개로 처리하지 않음");
			}
			resultSet.close();
		} catch (SQLException e) {
			resultObject = null;
			System.out.println("Query[" + preparedStatement.toString() + "]를 실행하지 못함");
			e.printStackTrace();
		}

		try {
			preparedStatement.close();
			System.out.println("PreparedStatement를 종료함");
		} catch (SQLException e) {
			System.out.println("PreparedStatement를 종료하지 못함");
			e.printStackTrace();
		}
	} else {
		System.out.println("PreparedStatement를 생성하지 못함");
	}

	closeConnection(connection);

	long endTime = System.currentTimeMillis();
	System.out.println((endTime - startTime) + "ms 소요됨");
	
	return resultObject;
}
/**
 * 메소드에 대한 설명을 입력해주세요. 
 * @param resultSet ResultSet 객체
 * @param index ResultSet 객체의 컬럼 인덱스(순서)
 * @param resultTypeClassName 결과 클래스 명
 * @return 결과 객체
 */
private <T> T getResultSetColumnValue(ResultSet resultSet, int index, Class<T> resultTypeClass) {
	T resultObject = null;

	String resultTypeClassName = resultTypeClass.getName();
	try {
		if (resultTypeClassName.equals(String.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getString(index));
		} else if (resultTypeClassName.equals(Integer.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getInt(index));
		} else if (resultTypeClassName.equals(Short.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getShort(index));
		} else if (resultTypeClassName.equals(Float.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getFloat(index));
		} else if (resultTypeClassName.equals(Long.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getLong(index));
		} else if (resultTypeClassName.equals(Boolean.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getBoolean(index));
		} else if (resultTypeClassName.equals(Byte.class.getName())) {
			resultObject = resultTypeClass.cast(resultSet.getByte(index));
		} else if (resultTypeClassName.equals(Date.class.getName())) {
			resultObject = resultTypeClass.cast(new Date(resultSet.getTimestamp(index).getTime()));
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}

	return resultObject;
}

 

2. "TestLoginDaoImpl.java"의 getMemberListCount()메소드를 executeQueryOneValue()메소드로 처리되게 변경합니다.

@Override
public int getMemberListCount() {
	int result = 0;

	String query = "SELECT COUNT(MBR_ID) AS CNT FROM MBR_ACCOUNT_TB";
	Connection connection = testDatabaseManager.getConnection();
	if (connection != null) {
		PreparedStatement preparedStatement = null;

		try {
			preparedStatement = connection.prepareStatement(query);
			System.out.println("PreparedStatement를 생성함");
		} catch (SQLException e) {
			System.out.println("PreparedStatement를 생성하지 못함");
			e.printStackTrace();
		}

		if (preparedStatement != null) {
			ResultSet resultSet = null;
			try {
				resultSet = preparedStatement.executeQuery();
				System.out.println("Query[" + preparedStatement.toString() + "]를 실행함");
			} catch (SQLException e) {
				System.out.println("Query[" + preparedStatement.toString() + "]를 실행하지 못함");
				e.printStackTrace();
			}

			try {
				if (resultSet.next()) {
					String count = resultSet.getString("CNT");
					result = Integer.parseInt(count);
				} else {
					System.out.println("resultSet이 없음");
				}
				resultSet.close();
			} catch (SQLException e) {
				System.out.println("resultSet를 가져오지 못함");
				e.printStackTrace();
			}

			try {
				preparedStatement.close();
				System.out.println("PreparedStatement를 종료함");
			} catch (SQLException e) {
				System.out.println("PreparedStatement를 종료하지 못함");
				e.printStackTrace();
			}
		}

		testDatabaseManager.closeConnection(connection);
	}

	return result;
}

변경된 소스입니다.

@Override
public int getMemberListCount() {
	int result = 0;

	String query = "SELECT COUNT(MBR_ID) AS CNT FROM MBR_ACCOUNT_TB";
	result = testDatabaseManager.executeQueryOneValue(query, null, Integer.class).intValue();

	return result;
}

 

3. 이전 "자바 JUnit를 이용한 단위 테스트"에서 추가한 "TestLoginDaoImplTest.java"에서 테스트 케이스인 testGetMemberListCount()메소드에서 getMemberListCount()메소드를 실행할 수 있게 수정합니다.

/**
 * Test method for {@link com.home.project.test2.dao.TestLoginDaoImpl#getMemberListCount()}.
 */
@Test
public void testGetMemberListCount() {
	System.out.println("testGetMemberListCount() 테스트 시작");

	int memberCount = testLoginDao.getMemberListCount();
	System.out.println("Member Count : " + memberCount);
	assertTrue("데이터베이스로 부터 전체 사용자를 가져오지 못함", memberCount > 0);

	System.out.println("testGetMemberListCount() 테스트 성공 종료");
}

 

​4. test2 프로젝트의 [Java Resources > src/test/java]에 있는 "com.home.project.test2.util > TestLoginDaoImplTest.java"에서 마우스 오른쪽 버튼을 클릭하여 콘텍스트 메뉴에서 [Run As > JUnit Test]를 클릭합니다.

JUnit Test가 실행되면서 왼쪽에 JUnit탭이 나타나고 Error가 "0", Failures이 "0"으로 테스트가 성공합니다.

[Console]

testGetMemberListCount() 테스트 시작
[main] 데이터베이스 커넥션 객체[1374677625, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 가져옴
PreparedStatement를 생성함
Query[sql : 'SELECT COUNT(MBR_ID) AS CNT FROM MBR_ACCOUNT_TB', parameters : []]를 실행함
PreparedStatement를 종료함
[main] 데이터베이스 커넥션 객체[1374677625, URL=jdbc:mariadb://localhost:3306/test, MariaDB Connector/J]를 반환함
1ms 소요됨
Member Count : 1
testGetMemberListCount() 테스트 성공 종료
728x90
반응형