ResultSet 사용하기
ResultSet 사용하기#
이 절은 Altibase JDBC 드라이버가 지원하는 ResultSet의 유형과 그 사용법을 설명한다.
ResultSet 생성#
결과셋은 데이터베이스에 대해 쿼리문을 실행할 때 생성되며, JDBC의 ResultSet 객체에 대응한다.
다음은 JDBC에서 ResultSet 객체를 생성하는 메소드들이다.
public Statement createStatement(int aResultSetType, int aResultSetConcurrency) throws SQLException;
public Statement createStatement(int aResultSetType, int aResultSetConcurrency, int aResultSetHoldability) throws SQLException;
public PreparedStatement prepareStatement(String aSql, int aResultSetType, int aResultSetConcurrency) throws SQLException;
public PreparedStatement prepareStatement(String aSql, int aResultSetType, int aResultSetConcurrency, int aResultSetHoldability) throws SQLException;
public CallableStatement prepareCall(String aSql, int aResultSetType, int aResultSetConcurrency) throws SQLException
public CallableStatement prepareCall(String aSql, int aResultSetType, int aResultSetConcurrency, int aResultSetHoldability) throws SQLExc
ResultSet의 유형#
JDBC의 ResultSet 객체는 결과셋 내에서 현재 형을 가리키는 커서를 관리하고 유지한다. 기본적인 ResultSet 객체의 커서는 업데이트가 불가능하고 순방향으로만 이동하는 커서이지만, 옵션을 사용해서 스크롤 가능하고 업데이트 가능한 ResultSet 객체를 생성할 수 있다.
다음은 사용자가 지정 가능한 ResultSet 객체의 유형이다.
-
TYPE_FORWARD_ONLY
스크롤이 불가능하며, 커서를 순방향으로만 이동할 수 있다. 데이터베이스 서버에서 커서가 열리는 시점에 결과셋의 데이터가 결정된다. -
TYPE_SCROLL_INSENSITIVE
스크롤이 가능하므로, 커서를 순방향, 역방향, 또는 위치를 지정해서 이동할 수 있다. 데이터베이스 서버에서 커서가 열리는 시점에 결과셋의 데이터가 결정된다. 서버에서 가져온 결과셋을 클라이언트에 누적해서 캐시하므로 메모리 사용량이 증가할 수 있다. -
TYPE_SCROLL_SENSITIVE
스크롤이 가능하므로, 커서를 순방향, 역방향, 또는 위치를 지정해서 이동할 수 있다. 데이터베이스 서버에서 커서가 열리는 시점에 결과셋이 결정되지만, 결과셋 내의 데이터는 클라이언트가 가져오거나 갱신하는 시점에 결정된다. 서버에서 가져온 결과셋을 클라이언트에 누적해서 캐시하므로 메모리 사용량이 증가할 수 있다.
Concurrency#
ResultSet 객체를 통한 업데이트 허용 여부를 결정하는 옵션이다. 아래의 두 가지 상수 중 하나를 사용할 수 있다.
-
CONCUR_READ_ONLY
업데이트를 허용하지 않는다. 기본값이다. -
CONCUR_UPDATABLE
ResultSet 객체를 이용한 업데이트를 허용한다.
Holdability#
트랜잭션 커밋 후에도 ResultSet 객체를 유지할 것인지를 결정하는 옵션이다. 아래 두 가지 상수 중 하나를 사용하면 된다.
-
CLOSE_CURSORS_AT_COMMIT
트랜잭션이 커밋 될 때, 커서가 닫힌다. -
HOLD_CURSORS_OVER_COMMIT
트랜잭션을 커밋하더라도 커서는 유지된다. 커서가 열린 후 한 번이라도 트랜잭션이 커밋되었다면, 그 커서는 이 후의 commit. rollback 수행에도 계속 유지된다. 하지만 커서가 열린 후 한 번도 커밋을 하지 않았다면, 트랜잭션이 rollback될 때 그 커서는 닫힌다.
주의사항#
-
ResultSet 객체를 위해 JDBC 드라이버가 클라이언트에 FetchSize에 설정된 개수만큼 행을 캐시하고 있으므로, 커서가 닫히더라도 캐시에 남아있는 데이터는 애플리케이션에서 가져갈 수 있다. 만약 커서가 닫힌 걸 애플리케이션에서 바로 감지하고 싶다면, FetchSize를 1로 설정하면 된다.
-
Altibase JDBC 드라이버의 Holdability 기본값은 CLOSE_CURSORS_AT_COMMIT으로, JDBC 스펙의 기본값인 HOLD_CURSORS_OVER_COMMIT와 다르다.
Holdability가 HOLD_CURSORS_OVER_COMMIT인 세션에서는 setAutoCommit() 메소드로 자동커밋 모드를 변경하기 전에 열려 있는 ResultSet 객체를 반드시 닫아야 한다. 아래는 오류가 발생하는 예제 코드이다.sCon = getConnection(); sStmt = sCon.createStatement(); byte[] br; byte[] bb = new byte[48]; for(byte i = 0; i < bb.length;i++) bb[i] = i; sCon.setAutoCommit(false); sStmt.executeUpdate("insert into Varbinary_Tab values(null)"); sCon.commit(); sPreStmt = sCon.prepareStatement("update Varbinary_Tab set VARBINARY_VAL=?"); sPreStmt.setObject(1, bb, java.sql.Types.VARBINARY); sPreStmt.executeUpdate(); sRS = sStmt.executeQuery("Select VARBINARY_VAL from Varbinary_Tab"); sRS.next(); br = sRS.getBytes(1); sCon.commit(); sCon.setAutoCommit(true); -> (1)
(1)에서 다음과 같은 exception이 발생한다.
java.sql.SQLException: Several statements still open at Altibase.jdbc.driver.ex.Error.processServerError(Error.java:320) at Altibase.jdbc.driver.AltibaseConnection.setAutoCommit(AltibaseConnection.java:988) at HodabilityTest.testHoldability(HodabilityTest.java:46) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:616)
exception이 발생하지 않게 하려면 (1)의 sCon.setAutoCommit(true)에 앞서 sRs.close()를 호출해야 한다.
- Holdability 유형이 HOLD_CURSORS_OVER_COMMIT인 ResultSet 객체를 사용하기 위해서는 클라이언트 세션이 Non-Autocommit 모드이거나 clientside_auto_commit 연결 속성이 on으로 설정되어야 한다. clientside_auto_commit 연결 속성을 on으로 설정하면, Holdability 유형이 자동으로 HOLD_CURSORS_OVER_COMMIT으로 변경된다.
예제#
Statement sUpdStmt = sConn.prepareStatement("UPDATE t1 SET val = ? WHERE id = ?");
Statement sSelStmt = sConn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
ResultSet sRS = sSelStmt.executeQuery("SELECT * FROM t1");
while (sRS.next())
{
// TODO : set parameters
sUpdStmt.execute();
sConn.commit();
}
sRS.close();
제약 사항#
Updatable ResultSet 또는 Scrollable ResultSet을 사용하기 위해서는 결과셋을 가져오는 SELECT 쿼리문에 다음의 제약 사항이 있다.
업데이트가 가능한 결과셋을 사용하기 위해서는,
-
FROM 절에 한 개의 테이블만 지정할 수 있다.
-
SELECT 리스트에 순수 칼럼만 지정할 수 있다. 수식 또는 함수가 포함될 수 없다. 그리고, NOT NULL 제약조건이 있으면서 DEFAULT값이 없는 칼럼은 SELECT 리스트에 반드시 포함되어야 한다.
Scrollable-Sensitive 결과셋을 사용하기 위해서는,
- FROM 절에 한 개의 테이블만 지정할 수 있다.
PSM을 수행하는 경우에는 기본 유형의 ResultSet 객체만 사용할 수 있다. 만약 사용자가 기본 유형이 아닌 옵션을 지정하면, 그 옵션은 무시된다.
CONCUR_UPDATABLE하고 TYPE_SCROLL_SENSITIVE한 ResultSet 객체는 JDBC 드라이버 내부적으로 한 개의 Statement를 더 사용하기 때문에, Statement 개수 제약에 더 일찍 도달할 수 있다. 따라서 이러한 유형의 결과셋을 많이 사용하는 경우에는 Statement의 최대 개수를 설정해야 한다.
업데이트가 가능하고 스크롤이 가능한 결과셋은 많은 데이터를 포함하고 있으므로, 일반적으로 순방향 전용의 결과셋에 비해 메모리 사용량이 높다. 따라서 결과셋이 큰 경우에는 메모리가 부족할 수 있으므로, 이러한 유형을 사용하지 않기를 권장한다.
ResultSet 객체의 특성은 위에서 설명한 ResultSet 유형, concurrency 유형, 및 holdability 유형으로 결정된다. 사용자는 이들 세 값을 임의의 조합으로 지정할 수 있지만, 결과셋을 만드는 쿼리문에 따라 사용자가 지정한 조합이 허용되지 않을 수도 있다. 이 경우 드라이버는 예외를 발생하지 않고 가능한 조합으로 변환한다. 즉, 아래와 같이 왼쪽 유형이 불가능한 경우 오른쪽 유형으로 자동 변환한다.
-
TYPE_SCROLL_SENSITIVE → TYPE_SCROLL_INSENSITIVE
-
CONCUR_UPDATABLE → CONCUR_READ_ONLY
-
HOLD_CURSORS_OVER_COMMIT → CLOSE_CURSORS_AT_COMMIT
이렇게 내부적으로 변환이 발생하면, 경고를 통해 변환 발생 여부를 확인할 수 있다.
ResultSet 객체의 유형이 TYPE_SCROLL_INSENSITIVE, TYPE_SCROLL_SENSITIVE인 경우 메모리 사용량 증가로 인해 ResultSet의 결과가 349,502건으로 제한되어 있다. 이 값을 초과할 경우 Dynamic array cursor overflow 에러가 발생할 수 있다.
Hole 감지#
TYPE_SCROLL_SENSITIVE 유형의 ResultSet 객체는 fetch할 때 서버로부터 최신 데이터를 가져온다. 따라서 커서가 열리는 순간에는 보였던 처음의 행이 스크롤 되면서 안 보일 수 있다. 예를 들어, ResultSet 객체에 있던 행이 다른 Statement를 통해 지워진다면, 그 행은 ResultSet 객체에서 더 이상 볼 수 없게 된다. 이렇게 볼 수 없게 된 행을 Hole이라고 한다.
아래는 JDBC에서 Hole을 검출하는 코드 예제이다.
while (sRS.next())
{
if (sRS.rowDeleted())
{
// HOLE DETECTED!!!
}
else
{
// do something ...
}
}
Hole에서는 유효한 데이터를 얻을 수 없으며, Hole에 해당하는 ResultSet의 반환값은 다음 중 하나이다:
- SQL 데이터형의 NULL
- 참조형으로는 null
- 값으로는 0
Fetch Size#
Altibase JDBC 드라이버는 성능 향상을 위해, ResultSet 객체를 위한 데이터를 서버로부터 가져올 때 한 행씩 가져오는 대신에 여러 행을 한번에 가져와서 클라이언트에 캐시한다. 이것을 prefetch라고 하며, Statement객체의 setFetchSize() 메소드를 이용해서 한번에 가져오는 행의 개수를 설정할 수 있다.
public void setFetchSize(int aRows) throws SQLException;
Altibase JDBC 드라이버에서는 0에서 2147483647까지의 값으로 설정할 수 있다. JDBC 스펙에는 이 범위를 벗어난 값을 지정할 때 Exception을 발생하도록 되어 있지만, Altibase JDBC 드라이버는 편의상 예외를 발생하지 않고 무시한다.
0을 설정하면, Altibase 서버가 클라이언트로 한번에 반환할 크기를 스스로 결정한다. 이 경우, 한 행의 크기에 따라 반환되는 행의 개수가 달라질 것이다.
FetchSize 값은 Scroll-Sensitive 결과셋에서 특히 중요하다. 사용자가 Scroll-Sensitive 결과셋으로부터 데이터를 가져갈 때, 드라이버는 prefetch한 것을 우선으로 반환한다. 그러므로, 데이터베이스의 데이터가 갱신되었더라도, 그 행이 prefetch한 캐시에 존재하는 한 캐시의 데이터가 사용자에게 반환된다. 사용자가 데이터베이스의 최신 데이터를 보기 원한다면, FetchSize를 1로 하면 된다. 그러나 이런 설정은 서버로부터 데이터를 가져오는 빈도수를 높여 성능을 떨어뜨릴 수 있다.
Refreshing Rows#
ResultSet 객체의 refreshRow() 메소드를 사용하면, SELECT문을 실행하지 않고서도 서버로부터 이미 가져온 데이터를 다시 가져올 수 있다. refreshRow() 메소드는 현재 행을 기준으로 FetchSize에 설정된 행 개수만큼 가져온다. 그러므로 이 메소드를 사용하기 위해서는, 커서가 결과셋에서 어떤 행이라도 가리키고 있는 상태이어야 한다.
이 메소드는 ResultSet 객체의 유형이 다음과 같을 때 동작한다.
-
TYPE_SCROLL_SENSITIVE & CONCUR_UPDATABLE
-
TYPE_SCROLL_SENSITIVE & CONCUR_READ_ONLY
TYPE_FORWARD_ONLY일 경우에는 이 메소드를 호출하면 예외가 발생하고, TYPE_SCROLL_INSENSITIVE일 경우에는 아무런 동작도 일어나지 않는다.