High Level Programming Language/JDBC Database Access

Retrieving and Modifying Values from Result Sets

헬로우월드 2025. 4. 6. 17:32

ResultSet에서 값 검색 및 수정하기

다음 메서드인 CoffeesTable.viewTable은 COFFEES 테이블의 내용을 출력하며, ResultSet 객체와 커서 사용법을 보여줍니다:

public static void viewTable(Connection con) throws SQLException {
  String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
  try (Statement stmt = con.createStatement()) {
    ResultSet rs = stmt.executeQuery(query);
    while (rs.next()) {
      String coffeeName = rs.getString("COF_NAME");
      int supplierID = rs.getInt("SUP_ID");
      float price = rs.getFloat("PRICE");
      int sales = rs.getInt("SALES");
      int total = rs.getInt("TOTAL");
      System.out.println(coffeeName + ", " + supplierID + ", " + price +
                         ", " + sales + ", " + total);
    }
  } catch (SQLException e) {
    JDBCTutorialUtilities.printSQLException(e);
  }
}

ResultSet 객체는 데이터베이스 쿼리를 실행하여 생성되는 결과를 나타내는 데이터 테이블입니다. 예를 들어, CoffeesTable.viewTable 메서드는 stmt 객체를 통해 쿼리를 실행하고 ResultSet rs를 생성합니다. ResultSet 객체는 PreparedStatement, CallableStatement, RowSetStatement 인터페이스를 구현하는 모든 객체를 통해 생성할 수 있습니다.

ResultSet 객체의 데이터는 커서를 통해 접근합니다. 이 커서는 데이터베이스 커서가 아닌 ResultSet 내의 한 행을 가리키는 포인터입니다. 처음에는 첫 번째 행 앞에 위치하며, ResultSet.next 메서드를 호출하여 다음 행으로 이동할 수 있습니다. 이 메서드는 마지막 행 이후일 경우 false를 반환합니다. while 루프 안에서 ResultSet.next를 반복 호출함으로써 전체 데이터에 순회할 수 있습니다.

이 페이지에서 다루는 주제:

  • ResultSet 인터페이스
  • 행에서 열 값 검색하기
  • 커서(Cursor)
  • ResultSet 객체에서 행 업데이트
  • Statement 객체를 사용한 배치 업데이트
  • ResultSet 객체에 행 삽입

 

ResultSet 인터페이스

ResultSet 인터페이스는 실행된 쿼리의 결과를 검색하고 조작하기 위한 메서드를 제공합니다. ResultSet 객체는 다음과 같은 특성을 가질 수 있습니다: 타입(type), 동시성(concurrency), 커서 보존성(cursor holdability).

ResultSet 타입

ResultSet 객체의 타입은 두 가지 기능 영역에서의 기능 레벨을 결정합니다: 커서를 조작하는 방법과 Result set이 데이터 소스의 변경 사항을 어떻게 반영하는지 여부입니다.

세 가지 타입은 다음과 같습니다:

  • TYPE_FORWARD_ONLY: ResultSet은 스크롤할 수 없고 커서는 오직 앞 방향으로만 이동할 수 있습니다. ResultSet 은 쿼리가 실행될 때 또는 행이 검색될 때 조건을 만족하는 행으로 구성됩니다.
  • TYPE_SCROLL_INSENSITIVE: ResultSet은 스크롤할 수 있으며 커서는 앞뒤로 이동하거나 절대 위치로 이동할 수 있습니다. ResultSet은 오픈 상태에서 데이터 소스에 가해진 변경을 반영하지 않습니다.
  • TYPE_SCROLL_SENSITIVE: ResultSet은 스크롤할 수 있고 커서는 앞뒤로 이동하거나 절대 위치로 이동할 수 있습니다. ResultSet은 오픈 상태에서 데이터 소스에 가해진 변경을 반영합니다.

디폴트 타입은 TYPE_FORWARD_ONLY입니다.

참고: 모든 데이터베이스 및 JDBC 드라이버가 위의 모든 타입을 지원하지는 않습니다. DatabaseMetaData.supportsResultSetType 메서드를 통해 해당 타입이 지원되는지 확인할 수 있습니다.

 

ResultSet 동시성

ResultSet 객체의 동시성은 ResultSet이 업데이트 가능한지 여부를 결정합니다.

두 가지 레벨이 있습니다:

  • CONCUR_READ_ONLY: ResultSet 객체는 업데이트할 수 없습니다.
  • CONCUR_UPDATABLE: ResultSet 객체는 업데이트할 수 있습니다.

기본값은 CONCUR_READ_ONLY입니다.

참고: 모든 JDBC 드라이버가 업데이트 가능한 ResultSet을 지원하는 것은 아닙니다. DatabaseMetaData.supportsResultSetConcurrency를 사용해 지원 여부를 확인할 수 있습니다.

 

커서 보존성 (Holdability)

Connection.commit() 메서드를 호출하면 현재 트랜잭션 중에 생성된 ResultSet 객체가 닫힐 수 있습니다. 이 동작은 상황에 따라 바람직하지 않을 수 있습니다. ResultSetholdability 속성은 커서가 커밋 시 닫힐지 여부를 제어합니다.

createStatement, prepareStatement, prepareCall 메서드에 아래 상수를 지정할 수 있습니다:

  • HOLD_CURSORS_OVER_COMMIT: 커서가 커밋 이후에도 오픈되어 있음
  • CLOSE_CURSORS_AT_COMMIT: 커서가 커밋 시 닫힘

디폴트값은 DBMS마다 다를 수 있습니다.

참고: 모든 JDBC 드라이버가 holdable 또는 non-holdable 커서를 지원하지는 않습니다.

 

예시:

public static void cursorHoldabilitySupport(Connection conn)
    throws SQLException {

    DatabaseMetaData dbMetaData = conn.getMetaData();
    System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " +
        ResultSet.HOLD_CURSORS_OVER_COMMIT);

    System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " +
        ResultSet.CLOSE_CURSORS_AT_COMMIT);

    System.out.println("Default cursor holdability: " +
        dbMetaData.getResultSetHoldability());

    System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " +
        dbMetaData.supportsResultSetHoldability(
            ResultSet.HOLD_CURSORS_OVER_COMMIT));

    System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " +
        dbMetaData.supportsResultSetHoldability(
            ResultSet.CLOSE_CURSORS_AT_COMMIT));
}

 

Row에서 Column 값 검색하기

ResultSet 인터페이스는 getBoolean, getLong 등 다양한 getter 메서드를 선언하며, 컬럼 값을 가져올 수 있습니다. 컬럼 인덱스(1부터 시작) 또는 컬럼 이름/별칭을 사용할 수 있습니다.

예:

public static void alternateViewTable(Connection con) throws SQLException {
  String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
  try (Statement stmt = con.createStatement()) {
    ResultSet rs = stmt.executeQuery(query);
    while (rs.next()) {
      String coffeeName = rs.getString(1);
      int supplierID = rs.getInt(2);
      float price = rs.getFloat(3);
      int sales = rs.getInt(4);
      int total = rs.getInt(5);
      System.out.println(coffeeName + ", " + supplierID + ", " + price +
                         ", " + sales + ", " + total);
    }
  } catch (SQLException e) {
    JDBCTutorialUtilities.printSQLException(e);
  }
}

컬럼 이름은 대소문자를 구분하지 않습니다. 컬럼이 여러 개고 이름이 중복될 경우, 가장 먼저 일치하는 컬럼이 반환됩니다.

컬럼 이름 대신 숫자를 사용하는 것이 성능 면에서 더 좋으며, SELECT * 문처럼 명시적 이름이 없는 경우에는 숫자를 사용하는 것이 바람직합니다. 컬럼 이름을 사용할 경우, AS 절을 사용하여 별칭을 지정하는 것이 안전합니다.

또한 getString 메서드는 CHAR, VARCHAR 이외에도 대부분의 SQL 타입에 사용 가능합니다. 단, 숫자형 데이터를 가져오면 문자열로 리턴되므로, 이후 연산을 위해 숫자로 다시 변환해야 합니다.

커서(Cursor)

커서는 ResultSet 객체의 특정 행을 가리킵니다. 생성 직후에는 첫 번째 행 이전에 위치합니다. 커서를 이동시키는 주요 메서드는 다음과 같습니다:

  • next(): 다음 행으로 이동
  • previous(): 이전 행으로 이동
  • first(): 첫 번째 행으로 이동
  • last(): 마지막 행으로 이동
  • beforeFirst(): 첫 번째 행 앞 위치
  • afterLast(): 마지막 행 뒤 위치
  • relative(int n): 현재 위치 기준으로 상대 이동
  • absolute(int row): 지정한 행 번호로 이동

디폴트 타입이 TYPE_FORWARD_ONLY인 경우 next()만 사용할 수 있습니다.

 

ResultSet 객체에서 행 업데이트

디폴트 ResultSet 객체는 수정할 수 없으며 커서도 앞으로만 이동합니다. 그러나 TYPE_SCROLL_SENSITIVECONCUR_UPDATABLE을 설정하면 스크롤 및 수정이 가능합니다.

예시:

public void modifyPrices(float percentage) throws SQLException {
  try (Statement stmt =
    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
    ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
    while (uprs.next()) {
      float f = uprs.getFloat("PRICE");
      uprs.updateFloat("PRICE", f * percentage);
      uprs.updateRow();
    }
  } catch (SQLException e) {
    JDBCTutorialUtilities.printSQLException(e);
  }
}

업데이트는 updateXxx() 메서드로 값을 설정한 후, 반드시 updateRow()를 호출해야 데이터베이스에 반영됩니다.

Statement 객체를 사용한 배치 업데이트

Statement, PreparedStatement, CallableStatement 객체는 명령 리스트를 가질 수 있습니다.

  • addBatch()로 명령 추가
  • clearBatch()로 초기화
  • executeBatch()로 실행

예시:

public void batchUpdate() throws SQLException {
  con.setAutoCommit(false);
  try (Statement stmt = con.createStatement()) {
    stmt.addBatch("INSERT INTO COFFEES VALUES('Amaretto', 49, 9.99, 0, 0)");
    stmt.addBatch("INSERT INTO COFFEES VALUES('Hazelnut', 49, 9.99, 0, 0)");
    stmt.addBatch("INSERT INTO COFFEES VALUES('Amaretto_decaf', 49, 10.99, 0, 0)");
    stmt.addBatch("INSERT INTO COFFEES VALUES('Hazelnut_decaf', 49, 10.99, 0, 0)");

    int[] updateCounts = stmt.executeBatch();
    con.commit();
  } catch (BatchUpdateException b) {
    JDBCTutorialUtilities.printBatchUpdateException(b);
  } catch (SQLException ex) {
    JDBCTutorialUtilities.printSQLException(ex);
  } finally {
    con.setAutoCommit(true);
  }
}

setAutoCommit(false)는 반드시 호출되어야 오류 처리를 정확히 할 수 있습니다.

 

파라미터화된 배치 업데이트

con.setAutoCommit(false);
PreparedStatement pstmt = con.prepareStatement(
  "INSERT INTO COFFEES VALUES(?, ?, ?, ?, ?)");
pstmt.setString(1, "Amaretto");
// 나머지 setXXX 및 addBatch 반복...
int[] updateCounts = pstmt.executeBatch();
con.commit();
con.setAutoCommit(true);

 

배치 업데이트 예외 처리

executeBatch()를 호출했을 때 다음의 경우 BatchUpdateException이 발생합니다:

  1. SELECT 문과 같이 ResultSet을 생성하는 SQL이 있을 때
  2. 어떤 명령이 실행되지 못했을 때

BatchUpdateException.getUpdateCounts()를 사용하면 실행된 수를 알 수 있습니다.

예시:

public static void printBatchUpdateException(BatchUpdateException b) {
  System.err.println("----BatchUpdateException----");
  System.err.println("SQLState:  " + b.getSQLState());
  System.err.println("Message:  " + b.getMessage());
  System.err.println("Vendor:  " + b.getErrorCode());
  System.err.print("Update counts:  ");
  int[] updateCounts = b.getUpdateCounts();
  for (int i = 0; i < updateCounts.length; i++) {
    System.err.print(updateCounts[i] + "   ");
  }
}

 

ResultSet 객체에 행 삽입

참고: 모든 JDBC 드라이버가 ResultSet을 통한 행 삽입을 지원하지는 않습니다. 지원하지 않을 경우 SQLFeatureNotSupportedException이 발생합니다.

public void insertRow(String coffeeName, int supplierID, float price,
                      int sales, int total) throws SQLException {
  try (Statement stmt =
        con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
    ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
    uprs.moveToInsertRow();
    uprs.updateString("COF_NAME", coffeeName);
    uprs.updateInt("SUP_ID", supplierID);
    uprs.updateFloat("PRICE", price);
    uprs.updateInt("SALES", sales);
    uprs.updateInt("TOTAL", total);
    uprs.insertRow();
    uprs.beforeFirst();
  } catch (SQLException e) {
    JDBCTutorialUtilities.printSQLException(e);
  }
}

moveToInsertRow()는 insert 전용 버퍼로 이동하며, insertRow()로 실제 데이터베이스에 반영됩니다.
beforeFirst()를 통해 커서를 다시 이동해야 하며, 그렇지 않으면 다른 코드가 오작동할 수 있습니다.

 

출처 : https://docs.oracle.com/javase/tutorial/jdbc/basics/retrieving.html

 

Retrieving and Modifying Values from Result Sets (The Java™ Tutorials > JDBC Database Access > JDBC Basics

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag

docs.oracle.com