1. 개요
JDBC ( Java Database Connectivity) API 는 Java 애플리케이션에서 데이터베이스에 대한 액세스를 제공합니다. 지원되는 JDBC 드라이버를 사용할 수 있는 한 JDBC를 사용하여 모든 데이터베이스에 연결할 수 있습니다.
ResultSet 은 데이터베이스 쿼리를 실행하여 생성된 데이터 테이블입니다. 이 사용방법(예제)에서는 ResultSet API 에 대해 자세히 살펴보겠습니다 .
2. 결과 집합 생성
먼저 Statement 인터페이스를 구현하는 모든 객체에서 executeQuery() 를 호출 하여 ResultSet 을 검색합니다. PreparedStatement 와 CallableStatement 는 모두 Statement 의 서브인터페이스입니다 .
PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();
ResultSet 객체 는 결과 집합의 현재 행을 가리키는 커서를 유지합니다. ResultSet 에서 next() 를 사용 하여 레코드를 반복합니다.
다음으로, 데이터베이스 열에서 값을 가져오기 위해 결과를 반복하는 동안 getX() 메서드를 사용합니다 . 여기서 X 는 열의 데이터 유형입니다. 사실, 우리는 getX() 메소드에 데이터베이스 컬럼 이름을 제공할 것입니다:
while(rs.next()) {
String name = rs.getString("name");
Integer empId = rs.getInt("emp_id");
Double salary = rs.getDouble("salary");
String position = rs.getString("position");
}
마찬가지로 열 이름 대신 getX() 메서드 와 함께 열의 인덱스 번호를 사용할 수 있습니다 . 인덱스 번호는 SQL select 문의 열 시퀀스입니다.
select 문이 열 이름을 나열하지 않는 경우 인덱스 번호는 테이블의 열 시퀀스입니다. 열 인덱스 번호는 1부터 시작합니다.
Integer empId = rs.getInt(1);
String name = rs.getString(2);
String position = rs.getString(3);
Double salary = rs.getDouble(4);
3. ResultSet 에서 메타데이터 검색
이 섹션에서는 ResultSet 의 열 속성 및 유형에 대한 정보를 검색하는 방법을 살펴보겠습니다 .
먼저 ResultSet 에서 getMetaData() 메서드를 사용하여 ResultSetMetaData 를 얻습니다 .
ResultSetMetaData metaData = rs.getMetaData();
다음으로 ResultSet 에 있는 열의 수를 알아보겠습니다 .
Integer columnCount = metaData.getColumnCount();
또한 메타데이터 개체에서 아래 방법 중 하나를 사용하여 각 열의 속성을 검색할 수 있습니다.
- getColumnName(int columnNumber) – 열의 이름을 가져옵니다.
- getColumnLabel(int columnNumber) – SQL 쿼리에서 AS 뒤에 지정된 열의 레이블에 액세스합니다.
- getTableName(int columnNumber) – 이 열이 속한 테이블 이름을 가져옵니다.
- getColumnClassName(int columnNumber) – 열의 Java 데이터 유형을 가져옵니다.
- getColumnTypeName(int columnNumber) – 데이터베이스에 있는 열의 데이터 유형을 가져옵니다.
- getColumnType(int columnNumber) – 열의 SQL 데이터 유형을 가져옵니다.
- isAutoIncrement(int columnNumber) – 열이 자동 증분인지 여부를 나타냅니다.
- isCaseSensitive(int columnNumber) – 열 대소문자가 중요한지 여부를 지정합니다.
- isSearchable(int columnNumber) – SQL 쿼리 의 where 절 에서 열을 사용할 수 있는지 제안합니다 .
- isCurrency(int columnNumber) – 열에 현금 값이 포함되어 있는지 여부를 나타냅니다.
- isNullable(int columnNumber) – 열이 null일 수 없는 경우 0 을 반환 하고, 열에 null 값을 포함할 수 있는 경우 1 을 반환하고, 열의 null 허용 여부를 알 수 없는 경우 2 를 반환합니다.
- isSigned(int columnNumber) – 열의 값이 서명되면 true 를 반환 하고 그렇지 않으면 false 를 반환합니다.
속성을 가져오기 위해 열을 반복해 보겠습니다.
for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
String catalogName = metaData.getCatalogName(columnNumber);
String className = metaData.getColumnClassName(columnNumber);
String label = metaData.getColumnLabel(columnNumber);
String name = metaData.getColumnName(columnNumber);
String typeName = metaData.getColumnTypeName(columnNumber);
int type = metaData.getColumnType(columnNumber);
String tableName = metaData.getTableName(columnNumber);
String schemaName = metaData.getSchemaName(columnNumber);
boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber);
boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber);
boolean isCurrency = metaData.isCurrency(columnNumber);
boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber);
boolean isReadOnly = metaData.isReadOnly(columnNumber);
boolean isSearchable = metaData.isSearchable(columnNumber);
boolean isReadable = metaData.isReadOnly(columnNumber);
boolean isSigned = metaData.isSigned(columnNumber);
boolean isWritable = metaData.isWritable(columnNumber);
int nullable = metaData.isNullable(columnNumber);
}
4. ResultSet 탐색
ResultSet 을 얻을 때 커서의 위치는 첫 번째 행 앞에 있습니다. 또한 기본적으로 ResultSet 은 정방향으로만 이동합니다. 그러나 다른 탐색 옵션에 대해 스크롤 가능한 ResultSet 을 사용할 수 있습니다.
이 섹션에서는 다양한 탐색 옵션에 대해 설명합니다.
4.1. 결과 집합 유형
ResultSet 유형은 데이터세트를 통해 조정하는 방법을 나타냅니다.
- TYPE_FORWARD_ONLY – 커서가 처음부터 끝까지 이동하는 기본 옵션
- TYPE_SCROLL_INSENSITIVE – 커서는 데이터 세트를 통해 앞뒤 방향으로 이동할 수 있습니다. 데이터 세트를 이동하는 동안 기본 데이터에 변경 사항이 있으면 무시됩니다. 데이터 세트에는 데이터베이스 쿼리가 결과를 반환한 시점의 데이터가 포함됩니다.
- TYPE_SCROLL_SENSITIVE – 스크롤에 둔감한 유형과 유사하지만 이 유형의 경우 데이터세트는 기본 데이터에 대한 변경 사항을 즉시 반영합니다.
모든 데이터베이스가 모든 ResultSet 유형을 지원하는 것은 아닙니다. 따라서 DatabaseMetaData 개체 에서 supportResultSetType을 사용하여 해당 유형이 지원 되는지 확인해 보겠습니다 .
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
4.2. 스크롤 가능한 결과 집합
스크롤 가능한 ResultSet 을 얻으려면 Statement 를 준비하는 동안 몇 가지 추가 매개변수 를 전달해야 합니다 .
예를 들어 TYPE_SCROLL_INSENSITIVE 또는 TYPE_SCROLL_SENSITIVE 를 ResultSet 유형 으로 사용하여 스크롤 가능한 ResultSet 을 얻을 수 있습니다 .
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
4.3. 탐색 옵션
스크롤 가능한 ResultSet 에서 아래 옵션 중 하나를 사용할 수 있습니다 .
- next() – 현재 위치에서 다음 행으로 진행
- 이전() – 이전 행으로 이동
- first() – ResultSet 의 첫 번째 행으로 이동합니다.
- last() – 마지막 행으로 이동
- beforeFirst() – 시작 부분으로 이동합니다. 이 메서드를 호출 한 후 ResultSet 에서 next() 를 호출하면 ResultSet 에서 첫 번째 행이 반환됩니다.
- afterLast() – 끝으로 도약합니다. 이 메서드를 실행한 후 ResultSet에서 이전()을 호출 하면 ResultSet 에서 마지막 행이 반환됩니다.
- relative(int numOfRows) – numOfRows 만큼 현재 위치에서 앞으로 또는 뒤로 이동합니다.
- absolute(int rowNumber) – 지정된 rowNumber 로 점프
몇 가지 예를 살펴보겠습니다.
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// iterate through the results from first to last
}
rs.beforeFirst(); // jumps back to the starting point, before the first row
rs.afterLast(); // jumps to the end of resultset
rs.first(); // navigates to the first row
rs.last(); // goes to the last row
rs.absolute(2); //jumps to 2nd row
rs.relative(-1); // jumps to the previous row
rs.relative(2); // jumps forward two rows
while (rs.previous()) {
// iterates from current row to the first row in backward direction
}
4.4. 결과 집합 행 수
getRow() 를 사용 하여 ResultSet 의 현재 행 번호를 가져옵니다 .
먼저 ResultSet 의 마지막 행으로 이동 한 다음 getRow() 를 사용하여 레코드 수를 가져옵니다.
rs.last();
int rowCount = rs.getRow();
5. ResultSet 의 데이터 업데이트
기본적으로 ResultSet 은 읽기 전용입니다. 그러나 업데이트 가능한 ResultSet 을 사용하여 행을 삽입, 업데이트 및 삭제할 수 있습니다.
5.1. 결과 집합 동시성
동시성 모드는 ResultSet 이 데이터를 업데이트할 수 있는지 여부를 나타냅니다.
CONCUR_READ_ONLY 옵션은 기본값이며 ResultSet 을 사용하여 데이터를 업데이트할 필요가 없는 경우 사용해야 합니다 .
그러나 ResultSet 의 데이터를 업데이트해야 하는 경우 CONCUR_UPDATABLE 옵션을 사용해야 합니다.
모든 데이터베이스가 모든 ResultSet 유형 에 대해 모든 동시성 모드를 지원하는 것은 아닙니다 . 따라서 supportResultSetConcurrency() 메서드 를 사용하여 원하는 유형과 동시성 모드가 지원되는지 확인해야 합니다 .
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetConcurrency(
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
5.2. 업데이트 가능한 ResultSet 얻기
업데이트 가능한 ResultSet 을 얻으려면 Statement 를 준비할 때 추가 매개변수를 전달해야 합니다 . 이를 위해 명령문을 생성할 때 세 번째 매개변수로 CONCUR_UPDATABLE 을 사용하겠습니다 .
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
5.3. 행 업데이트
이 섹션 에서는 이전 섹션에서 만든 업데이트 가능한 ResultSet 을 사용하여 행을 업데이트합니다 .
updateX() 메서드 를 호출하고 업데이트할 열 이름과 값을 전달하여 행의 데이터를 업데이트할 수 있습니다. updateX() 메서드 에서 X 대신 지원되는 모든 데이터 유형을 사용할 수 있습니다 .
double 유형 의 "salary" 열을 업데이트해 보겠습니다 .
rs.updateDouble("salary", 1100.0);
이것은 단지 ResultSet 의 데이터를 업데이트 하지만 수정 사항은 아직 데이터베이스에 다시 저장되지 않습니다.
마지막으로 updateRow() 를 호출 하여 업데이트를 데이터베이스에 저장합니다 .
rs.updateRow();
열 이름 대신 열 인덱스를 updateX() 메서드에 전달할 수 있습니다. 이는 getX() 메서드를 사용하여 값을 가져오기 위해 열 인덱스를 사용하는 것과 유사합니다. 열 이름이나 인덱스를 updateX() 메서드에 전달하면 동일한 결과가 생성됩니다.
rs.updateDouble(4, 1100.0);
rs.updateRow();
5.4. 행 삽입
이제 업데이트 가능한 ResultSet 을 사용하여 새 행을 삽입해 보겠습니다 .
먼저 moveToInsertRow() 를 사용하여 커서를 이동하여 새 행을 삽입합니다.
rs.moveToInsertRow();
다음으로 updateX() 메서드를 호출하여 행에 정보를 추가해야 합니다. 데이터베이스 테이블의 모든 열에 데이터를 제공해야 합니다. 모든 열에 데이터를 제공하지 않으면 기본 열 값이 사용됩니다.
rs.updateString("name", "Venkat");
rs.updateString("position", "DBA");
rs.updateDouble("salary", 925.0);
그런 다음 insertRow() 를 호출 하여 데이터베이스에 새 행을 삽입해 보겠습니다.
rs.insertRow();
마지막으로 moveToCurrentRow()를 사용합시다. 이렇게 하면 moveToInsertRow() 메서드 를 사용하여 새 행을 삽입하기 시작하기 전의 행으로 커서 위치가 다시 이동합니다 .
rs.moveToCurrentRow();
5.5. 행 삭제
이 섹션에서는 업데이트 가능한 ResultSet 을 사용하여 행을 삭제합니다 .
먼저 삭제할 행으로 이동합니다. 그런 다음 deleteRow() 메서드를 호출 하여 현재 행을 삭제합니다.
rs.absolute(2);
rs.deleteRow();
6. 유지성
유지 가능성 은 데이터베이스 트랜잭션이 끝날 때 ResultSet 이 열리거나 닫힐지 여부를 결정 합니다.
6.1. 유지성 유형
트랜잭션이 커밋된 후 ResultSet 이 필요하지 않은 경우 CLOSE_CURSORS_AT_COMMIT 를 사용 합니다.
HOLD_CURSORS_OVER_COMMIT 를 사용하여 유지 가능한 ResultSet 을 만듭니다 . 유지 가능한 ResultSet 은 데이터베이스 트랜잭션이 커밋된 후에도 닫히지 않습니다.
모든 데이터베이스가 모든 유지 가능성 유형을 지원하는 것은 아닙니다.
따라서 DatabaseMetaData 객체 에서 supportResultSetHoldability () 를 사용하여 유지 가능성 유형이 지원되는지 확인 합시다. 그런 다음 getResultSetHoldability() 를 사용하여 데이터베이스의 기본 유지 가능성을 얻습니다 .
boolean isCloseCursorSupported
= dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
boolean isOpenCursorSupported
= dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
boolean defaultHoldability
= dbmd.getResultSetHoldability();
6.2. 유지 가능한 결과 집합
유지 가능한 ResultSet 을 생성하려면 Statement를 생성하는 동안 홀드 가능성 유형 을 마지막 매개변수로 지정해야 합니다 . 이 매개변수는 동시성 모드 다음에 지정됩니다.
Microsoft SQL Server(MSSQL)를 사용하는 경우 ResultSet 이 아닌 데이터베이스 연결에 대한 유지 가능성을 설정해야 합니다 .
dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
실제로 이것을 봅시다. 먼저, 유지 가능성을 HOLD_CURSORS_OVER_COMMIT 로 설정 하는 Statement 를 생성해 보겠습니다 .
Statement pstmt = dbConnection.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResultSet.HOLD_CURSORS_OVER_COMMIT)
이제 데이터를 검색하는 동안 행을 업데이트해 보겠습니다. 이것은 데이터베이스에 업데이트 트랜잭션을 커밋한 후 ResultSet 을 통해 계속 반복한다는 점을 제외하고는 앞에서 논의한 업데이트 예제와 유사합니다. 이것은 MySQL과 MSSQL 데이터베이스 모두에서 잘 작동합니다.
dbConnection.setAutoCommit(false);
ResultSet rs = pstmt.executeQuery("select * from employees");
while (rs.next()) {
if(rs.getString("name").equalsIgnoreCase("john")) {
rs.updateString("name", "John Doe");
rs.updateRow();
dbConnection.commit();
}
}
rs.last();
MySQL은 HOLD_CURSORS_OVER_COMMIT 만 지원합니다 . 따라서 CLOSE_CURSORS_AT_COMMIT 를 사용하더라도 무시됩니다.
MSSQL 데이터베이스는 CLOSE_CURSORS_AT_COMMIT 를 지원합니다 . 이것은 트랜잭션을 커밋할 때 ResultSet 이 닫힐 것임을 의미합니다. 결과적으로 트랜잭션을 커밋한 후 ResultSet 에 액세스하려고 하면 'Cursor is not open error'가 발생합니다. 따라서 ResultSet 에서 추가 레코드를 검색할 수 없습니다 .
7. 크기 가져오기
일반적으로 ResultSet 에 데이터를 로드할 때 데이터베이스 드라이버는 데이터베이스에서 가져올 행 수를 결정합니다. 예를 들어 MySQL 데이터베이스에서 ResultSet 은 일반적으로 모든 레코드를 한 번에 메모리에 로드합니다.
그러나 때로는 JVM 메모리에 맞지 않는 많은 수의 레코드를 처리해야 할 수도 있습니다. 이 경우 Statement 또는 ResultSet 객체에서 fetch size 속성을 사용하여 처음에 반환되는 레코드 수를 제한할 수 있습니다.
추가 결과가 필요할 때마다 ResultSet 은 데이터베이스에서 다른 레코드 배치를 가져옵니다. 가져오기 크기 속성을 사용하여 데이터베이스 여행당 가져올 행 수에 대한 제안을 데이터베이스 드라이버에 제공할 수 있습니다 . 지정한 가져오기 크기는 후속 데이터베이스 여행에 적용됩니다.
ResultSet 에 대한 가져오기 크기를 지정하지 않으면 Statement 의 가져오기 크기 가 사용됩니다. Statement 또는 ResultSet 에 대해 가져오기 크기를 지정하지 않으면 데이터베이스 기본값이 사용됩니다.
7.1. 문 에서 크기 가져오기 사용
이제 Statement 의 fetch 크기 가 실제로 작동하는지 살펴보겠습니다. Statement 의 가져오기 크기 를 10개의 레코드로 설정합니다. 쿼리가 100개의 레코드를 반환하는 경우 매번 10개의 레코드를 로드하는 10개의 데이터베이스 왕복이 발생합니다.
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// iterate through the resultset
}
7.2. ResultSet 에서 크기 가져오기 사용
이제 ResultSet 을 사용하여 이전 예제의 가져오기 크기를 변경해 보겠습니다 .
먼저 Statement 에서 가져오기 크기를 사용합니다 . 이렇게 하면 쿼리를 실행한 후 ResultSet 이 처음에 10개의 레코드를 로드할 수 있습니다.
그런 다음 ResultSet 에서 가져오기 크기를 수정합니다 . 이것은 이전에 Statement 에서 지정한 가져오기 크기를 재정의합니다 . 따라서 모든 후속 여행은 모든 레코드가 로드될 때까지 20개의 레코드를 로드합니다.
결과적으로 모든 레코드를 로드하는 데 6번의 데이터베이스 이동만 있습니다.
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
rs.setFetchSize(20);
while (rs.next()) {
// iterate through the resultset
}
마지막으로 결과를 반복하면서 ResultSet 의 가져오기 크기를 수정하는 방법을 살펴보겠습니다 .
이전 예제와 유사하게 먼저 Statement 에서 가져오기 크기를 10으로 설정합니다 . 따라서 처음 3개의 데이터베이스 여행은 각 여행마다 10개의 레코드를 로드합니다.
그런 다음 30번째 레코드를 읽는 동안 ResultSet 의 가져오기 크기를 20으로 수정합니다 . 따라서 다음 4번의 이동은 각 이동마다 20개의 레코드를 로드합니다.
따라서 100개의 레코드를 모두 로드하려면 7개의 데이터베이스 이동이 필요합니다.
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
int rowCount = 0;
while (rs.next()) {
// iterate through the resultset
if (rowCount == 30) {
rs.setFetchSize(20);
}
rowCount++;
}
8. 결론
이 기사에서는 데이터베이스에서 데이터를 검색하고 업데이트하기 위해 ResultSet API 를 사용하는 방법을 살펴보았습니다 . 논의한 고급 기능 중 일부는 사용 중인 데이터베이스에 따라 다릅니다. 따라서 사용하기 전에 해당 기능에 대한 지원을 확인해야 합니다.
항상 그렇듯이 코드는 GitHub 에서 사용할 수 있습니다 .