두번째로 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() 테스트 성공 종료