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_SENSITIVEResultSet 유형 으로 사용하여 스크롤 가능한 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 에서 사용할 수 있습니다 .

Persistence footer banner