3.고급 기능#
이 장에서는 Altibase JDBC 드라이버가 제공하는 보다 향상된 기능들을 소개하고, 사용법을 설명한다.
자동 생성 키#
자동 생성 키(Auto-generated Keys)란 테이블의 각 행을 유일하게 가리킬 수 있는 값으로, 데이터베이스에서 자동으로 생성된다.
Altibase에서는 시퀀스(sequence)로 자동 생성 키 역할을 할 수 있다. 이 절에서는 JDBC에서 자동 생성 키 값을 얻는 방법을 설명한다.
사용법#
자동 생성 키를 얻기 위해서 먼저 어떤 칼럼에 대해 자동 생성된 키를 얻을 것인지를 명시하는 메소드를 사용해서 Statement 객체를 실행한다. 그리고 getGeneratedKeys() 메소드로 자동 생성된 키의 결과셋을 가져올 수 있다.
또는 어떤 칼럼이 자동 생성된 키 값을 얻을 것인지를 명시하는 메소드를 사용해서 PreparedStatement 객체를 생성하고 실행한 다음, getGeneratedKeys() 메소드로 자동 생성된 키의 결과셋을 가져올 수 있다.
다음은 자동으로 생성된 키를 가져올 수 있는 SQL문을 실행하는 Statement의 메소드들이다.
public boolean execute(String aSql, int aAutoGeneratedKeys) throws SQLException;
public boolean execute(String aSql, int[] aColumnIndexes) throws SQLException;
public boolean execute(String aSql, String[] aColumnNames) throws SQLException;
다음은 자동으로 생성된 키를 가져올 수 있는 PreparedStatement 객체를 생성하는 Connection의 메소드들이다.
public PreparedStatement prepareStatement(String aSql, int aAutoGeneratedKeys) throws SQLException;
public PreparedStatement prepareStatement(String aSql, int[] aColumnIndexes) throws SQLException;
public PreparedStatement prepareStatement(String aSql, String[] aColumnNames) throws SQLException;
위의 두 가지 방법 중 하나를 사용해서 SQL문을 실행한 후, Statement의 아래 메소드를 이용해서 자동 생성된 키를 ResultSet 객체로 얻을 수 있다.
public ResultSet getGeneratedKeys() throws SQLException;
제약 사항#
Altibase에서 자동으로 생성된 키를 얻을 때 아래의 제약 사항이 있다.
-
단순 INSERT 문에 대해서만 지원한다.
-
Altibase는 AUTO INCREMENT 속성의 칼럼을 지원하지 않기 때문에, 오직 시퀀스로부터만 자동 생성 키를 얻을 수 있다.
다음은 자동 생성 키를 얻을 수 있는 SQL문의 예제이다.
INSERT INTO t1 (id, val) VALUES (t1_id_seq.nextval, ?);
다음은 자동 생성 키를 얻을 수 없는 SQL문의 예제이다.
SELECT * FROM t1;
EXEC p1;
자동 생성 키를 만들지 않는 SQL문을 자동 생성 키 생성 플래그(Statement.RETURN_GENERATED_KEYS)와 함께 실행하면 해당 플래그는 무시되고, getGeneratedKeys() 메소드는 빈 결과셋을 반환한다.
예제#
sStmt.executeUpdate(sQstr, Statement.RETURN_GENERATED_KEYS);
ResultSet sKeys = sStmt.getGeneratedKeys();
while (sKeys.next())
{
int sKey = sKeys.getInt(1);
// do somethings...
}
sKeys.close();
sStmt.close();
타임아웃#
이 절에서는 Altibase 서버와 연결된 클라이언트 세션에서 발생할 수 있는 타임아웃을 설명하고, 타임아웃과 관련된 프로퍼티를 설정하는 방법을 코드 예제로 보여준다.
로그인 타임아웃#
Connection 객체의 connect 메소드를 호출한 후, 서버로부터 응답을 받을 때까지 대기하는 최대 시간을 초과할 때 발생하는 타임아웃이다. 대기하는 최대 시간을 설정하는 속성은 login_timeout이며, 설정 값의 단위는 초(second)이다.
코드 예제#
아래는 속성을 설정하는 두 가지 방법을 코드 예제로 보여준다.
- 타임아웃 속성을 추가한 Properties 객체를 사용해서 Connection 객체를 생성한다.
Properties sProps = new Properties();
...
sProps("login_timeout", "100");
...
Connection sCon = DriverManager.getConnection( sUrl, sProps );
- 타임아웃 속성을 명시한 연결 URL을 사용해서 Connection 객체를 생성한다.
String sUrl = "jdbc:Altibase://localhost:20300/mydb?login_timeout=100";
Connection sCon = DriverManager.getConnection( sUrl );
응답 타임아웃#
Altibase 서버로부터 응답을 기다리는 최대 시간을 초과할 때 발생하는 타임아웃이다. 대기하는 최대 시간을 설정하는 속성은 response_timeout이며, 설정 값의 단위는 초(second)이다.
이 값은 서버와 통신하는 모든 메소드 호출에 적용된다.
코드 예제#
아래는 응답 타임아웃의 속성을 설정하는 방법을 코드 예제로 보여준다.
- 타임아웃 속성을 추가한 Properties 객체를 사용해서 Connection 객체를 생성한다.
Properties sProps = new Properties();
...
sProps("response_timeout", "100");
...
Connection sCon = DriverManager.getConnection( sUrl, sProps );
- 타임아웃 속성을 명시한 연결 URL을 사용해서 Connection 객체를 생성한다.
String sUrl = "jdbc:Altibase://localhost:20300/mydb?response_timeout=100";
Connection sCon = DriverManager.getConnection( sUrl );
- 애플리케이션 실행 시 인자로 전달한다.
java ... -DALTIBASE_RESPONSE_TIMEOUT=100 ...
- 환경 변수를 설정한다.
// Linux
export ALTIBASE_RESPONSE_TIMEOUT=100
DataSource#
Altibase JDBC 드라이버는 연결 설정을 담고 있는 파일을 사용해서 데이터베이스에 접속하는 방법을 제공한다. 이 때 설정 파일에 포함되어 있는 데이터베이스 서버로의 연결 정보들의 집합을 DataSource라고 한다.
DataSource 설정 방법#
DataSource는 altibase_cli.ini 파일에 아래의 형식으로 설정한다.
# comment
[ datasource_name ]
Server=localhost # comment
Port=20300
User=sys
Password=manager
만약 더 추가할 연결 속성이 있다면, “key=value” 형태의 문자열을 줄 단위로 적으면 된다.
JDBC 드라이버는 아래 순서의 경로대로 altibase_cli.ini 파일을 찾는다.
-
/altibase_cli.ini
-
\$HOME/altibase_cli.ini
-
\$ALTIBASE_HOME/conf/altibase_cli.ini
DataSource를 이용한 접속#
DataSource를 사용해서 서버에 접속하려면, IP 주소와 포트 번호 대신에 altibase_cli.ini 파일에 명시한 DSN(DataSource Name)을 연결 URL에 명시하면 된다.
다음은 DSN을 이용한 연결 URL의 예제이다.
jdbc:Altibase://datasource_name
jdbc:Altibase://datasource_name:20301
jdbc:Altibase://datasource_name:20301?sys=user&password=pwd
연결 URL에 DSN을 명시할 때, port 또는 다른 속성을 추가로 지정할 수 있다. 단, altibase_cli.ini 파일에 지정한 속성을 연결 URL에 중복해서 지정하면, 파일의 값은 무시되고 연결 URL에 지정한 속성값이 사용된다.
Connection Pool#
다음의 방법으로 연결 풀 (Connection Pool)을 설정하고 관리할 수 있다.
- AltibaseConnectionPoolDataSource 사용: WAS에서 연결 풀을 사용할 때, 이 클래스를 WAS의 JDBC연결 풀 설정에서 지정한다. 6.3.1 버전 이하의 Altibase JDBC 드라이버에서 이 클래스의 이름은 ABConnectionPoolDataSource였다.
AltibaseConnectionPoolDataSource에서 설정하는 속성 정보는 아래와 같다.
| 프로퍼티 이름 | 설명 |
|---|---|
| databaseName | 데이터베이스 이름 |
| dataSourceName | dataSource 이름 |
| loginTimeout | 데이터베이스 로그인을 위한 최대 대기 시간 |
| logWriter | dataSource를 위한 Log writer |
| password | 데이터베이스 패스워드 |
| portNumber | 데이터베이스 포트 넘버 |
| serverName | 데이터베이스 서버명 |
| URL | Altibase 연결을 위한 Connection string 정보 (대문자 주의) |
| user | 데이터베이스 사용자 ID |
WAS (Web Application Server) 설정#
Altibase는 아래의 웹 애플리케이션 서버와 함께 사용할 수 있다.
-
Tomcat 8.x
-
Code example
Context initContext = new InitialContext(); Context envContext = (Context)initContext.lookup("java:/comp/env"); DataSource ds = (DataSource)envContext.lookup("jdbc/altihdb"); Connection conn = ds.getConnection(); // ... -
WebLogic 12.x
-
Jeus 6.x
웹 애플리케이션 서버에서 JDBC 드라이버와 연결 풀을 설정하고 사용하는 방법에 대해서는 각각의 제품별 매뉴얼을 참조하기 바란다.
Tomcat 8.x#
Apache Tomcat의 설치 및 설정 방법에 대한 자세한 내용은 http://tomcat.apache.org/tomcat-8.0-doc/index.html를 참조하기 바란다.
Context configuration#
아래와 같이 Context에 JNDI DataSource를 추가한다.
<Context>
<Resource name="jdbc/altihdb" auth="Container" type="javax.sql.DataSource"
driverClassName="Altibase.jdbc.driver.AltibaseDriver"
url="jdbc:Altibase://localhost:20300/mydb"
username="SYS" password="MANAGER"
maxTotal="100" maxIdle="30" maxWaitMillis="10000" />
</Context>
web.xml configuration#
<!----- web.xml ----->
<resource-ref>
<description>Altibase Datasource example</description>
<res-ref-name>jdbc/altihdb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Code example#
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/altihdb");
Connection conn = ds.getConnection();
// ...
WebLogic 12.x#
WebLogic Server의 설치 및 설정 방법에 대한 자세한 내용은 http://docs.oracle.com/middleware/1213/wls/index.html를 참조하기 바란다.
아래의 링크를 참조하여 JDBC datasource와 Connection Pool을 설정할 수 있다.
Altibase를 위한 설정 옵션은 아래와 같다.
-
데이터베이스 유형(Database Type): 기타(Other)
-
드라이버 클래스 이름(Driver Class Name): Altibase.jdbc.driver.AltibaseDriver
-
URL: jdbc:Altibase://localhost:20300/mydb
Jeus 6.x#
\$JEUS_HOME/config/JeusMain.xml 파일의 \<data-source> 요소를 편집하여 연결 풀을 설정한다.
<!------ JeusMain.xml ----->
<resource>
<data-source>
<database>
<vendor>others</vendor>
<export-name>jdbc/altihdb</export-name>
<data-source-class-name>
Altibase.jdbc.driver.AltibaseConnectionPoolDataSource
</data-source-class-name>
<data-source-type>ConnectionPoolDataSource</data-source-type>
<auto-commit>true</auto-commit>
<property>
<name>PortNumber</name>
<type>java.lang.Integer</type>
<value>20300</value>
</property>
<property>
<name>Password</name>
<type>java.lang.String</type>
<value>MANAGER</value>
</property>
<property>
<name>ServerName</name>
<type>java.lang.String</type>
<value>localhost</value>
</property>
<property>
<name>ConnectionAttributes</name>
<type>java.lang.String</type>
<value>;create=true</value>
</property>
<property>
<name>DatabaseName</name>
<type>java.lang.String</type>
<value>mydb</value>
</property>
<property>
<name>User</name>
<type>java.lang.String</type>
<value>SYS</value>
</property>
</database>
</data-source>
</resource>
연결 확인#
Altibase JDBC 드라이버는 연결 유효성 검사를 위해, 일반 쿼리보다 가볍게 실행되는 핑(PING) 방식의 쿼리를 지원한다. WAS에서 poolPingQuery 또는 validationQuery 등으로 이용할 수 있다.
쿼리 패턴은 아래와 같으며, PreparedStatment에서도 사용할 수 있다.
- 허용되는 패턴
/* PING */ SELECT 1
/* ping */ select 1
/* PING */ SELECT 1
/* PING */ select 1
/* PING */ select 1
/* PING */ select 1
/* PING */ seLECt 1
/* ping */ SELECT 1
- 허용되지 않는 패턴
/* PING */SELECT 1
/*+ ping */ select 1
/*ping */ select 1
/* ping */ select 1 from dual
/** PING */ SELECT 1
//* PING */ SELECT 1
Multiple ResultSet#
Altibase용 PSM(저장 프로시저 및 저장 함수)은 여러 개의 결과셋을 클라이언트에 반환할 수 있다. 이 절에서는 여러 개의 결과셋을 반환하는 PSM의 예제를 가지고, JDBC 애플리케이션에서 이러한 결과셋들을 다루는 방법을 코드 예제로 살펴본다.
다음은 여러 개의 결과셋을 반환하는 PSM 예제이다.
CREATE TYPESET my_type
AS
TYPE my_cur IS REF CURSOR;
END;
CREATE PROCEDURE p1 (p1 OUT MY_TYPE.MY_CUR, p2 out MY_TYPE.MY_CUR)
AS
BEGIN
OPEN p1 FOR 'SELECT * FROM t1';
OPEN p1 FOR 'SELECT * FROM t2';
END;
다음은 JDBC 애플리케이션에서 PSM 호출로 반환된 여러 개의 결과셋들을 다루는 코드 예제이다.
CallableStatement sCallStmt = connection().prepareCall("{call p1()}");
sCallStmt.execute();
ResultSet sRs = null;
ResultSetMetaData sRsMd = null;
do{
sRs = sCallStmt.getResultSet();
sRsMd = sRs.getMetaData();
if(sRsMd != null)
{
while(sRs.next())
{
// do something
for(int i=1; i <= sRsMd.getColumnCount(); i++)
{
System.out.println(sRs.getString(i));
}
}
}
}while(sCallStmt.getMoreResults());
sCallStmt.close();
JDBC와 Failover#
이 절은 Altibase JDBC 애플리케이션에서 Failover 기능을 사용하는 방법을 설명한다.
Failover란#
Failover란 데이터베이스 서버에 장애가 발생하여 연결이 끊어졌을 때, 애플리케이션이 즉시 다른 서버로 연결을 생성하여 기존에 수행하던 작업을 계속하는 기능을 말한다.
Failover는 아래 두 가지 방식으로 동작할 수 있다.
-
CTF(Connection Time Failover)
데이터베이스로의 접속 시도를 실패한 경우, 다른 서버로 접속을 재시도하는 동작 방식이다. CTF는 Connection 객체의 connect 메소드를 호출할 때 발생할 수 있다. -
STF(Session Time Failover)
SQL문을 수행하여 서버로부터 결과를 받기 전에 연결 오류가 발생한 경우, 다른 서버로 접속하여 사용자가 지정한 작업을 계속하는 동작 방식이다. STF는 connect를 제외한 서버와 통신을 하는 모든 메소드 수행 시에 발생할 수 있다.
Failover에 대한 자세한 내용은 Replication Manual의 “Failover”장을 참고하도록 한다.
사용 방법#
여기에서는 JDBC 애플리케이션에서 CTF 및 STF 기능을 사용하는 방법을 설명한다.
CTF#
Properties 객체에 아래의 속성을 추가해서 CTF 기능을 사용할 수 있다.
Properties sProps = new Properties();
sProps.put("alternateservers", "database1:20300, database2:20300");
sProps.put("connectionretrycount", "5");
sProps.put("connectionretrydelay", "2");
sProps.put("sessionfailover", "off");
각각의 속성에 대한 자세한 설명은 1장의 "연결 속성" 절을 참고하라.
STF#
CTF 기능을 설정하는 속성에 추가로 "SessionFailover=on"을 설정해서 STF 기능을 사용할 수 있다.
데이터베이스 서버로 접속을 시도하는 것을 제외한 통신 상황에서, 클라이언트가 서버의 장애를 감지하면 먼저 CTF 과정을 수행하여 접속을 복원한다. 그 다음, 클라이언트는 사용자가 등록한 콜백 함수를 수행한 후에, 사용자가 Failover 발생을 인지할 수 있도록 Failover Success Exception을 발생시킨다. 이 때 모든 서버로 Failover가 실패하면 드라이버는 원래 발생했던 Exception을 던진다.
아래는 사용자가 작성해야 할 Failover 콜백 함수를 위한 인터페이스이다.
public interface AltibaseFailoverCallback
{
public final static class Event
{
public static final int BEGIN = 0;
public static final int COMPLETED = 1;
public static final int ABORT = 2;
}
public final static class Result
{
public static final int GO = 3;
public static final int QUIT = 4;
}
int failoverCallback(Connection aConnection,
Object aAppContext,
int aFailoverEvent);
};
다음은 사용자가 Failover 콜백 함수를 등록하고 해제하는 과정을 보여주는 코드 예제이다.
public class UserDefinedFailoverCallback implements AltibaseFailoverCallback
{
...
public int failoverCallback(Connection aConnection,
Object aAppContext,
int aFailoverEvent)
{
// User Defined Code
// Result.GO나 Result.QUIT 중 한 가지 값을 반환해야 함.
}
...
}
위의 AltibaseFailoverCallback 인터페이스에 포함된 Event 상수는 사용자가 작성한 Failover 콜백 함수가 JDBC 드라이버에 의해 호출될 때, 콜백 함수의 세 번째 인자인 aFailoverEvent로 전달된다. 각 Event 상수의 의미는 다음과 같다.
-
Event.BEGIN: Session Failover가 시작됨
-
Event.COMPLETED: Session Failover가 성공하였음
-
Event.ABORT: Session Failover가 실패하였음
AltibaseFailoverCallback 인터페이스에 포함된 Result 상수는 사용자가 작성하는 콜백 함수에서 반환할 수 있는 값들이다. 콜백 함수에서 Result 상수 이외의 값을 반환하면 Failover가 정상적으로 동작하지 않는다.
-
Result.GO: 콜백 함수에서 이 상수값이 반환되면, JDBC 드라이버는 STF의 다음 과정을 계속해서 진행한다.
-
Result.QUIT: 콜백 함수에서 이 상수값이 반환되면, JDBC 드라이버는 STF 과정을 종료한다.
다음은 사용자가 작성하는 Failover 콜백 함수의 두 번째 인자로 사용할 수 있는 객체의 코드 예제이다.
public class UserDefinedAppContext
{
// User Defined Code
}
사용자가 구현한 애플리케이션의 정보를 STF 과정에서 사용할 필요가 있는 경우, Failover 콜백 함수를 등록하면서 콜백 함수에 전달될 객체를 지정할 수 있다. 콜백 함수를 등록하는 registerFailoverCallback 메소드의 두 번째 인자로 이 객체를 지정하면, 실제로 콜백 함수가 호출될 때 이 객체가 전달된다. 다음은 이런 과정을 코드로 나타낸 예제이다.
// 사용자 정의 콜백 함수 객체 생성
UserDefinedFailoverCallback sCallback = new UserDefinedFailoverCallback();
// 사용자 정의 애플리케이션 정보 객체 생성
UserDefinedAppContext sAppContext = new UserDefinedAppContext();
...
Connection sCon = DriverManager.getConnection(sURL, sProp);
// 사용자 정의 애플리케이션 객체와 함께 콜백 함수 등록
((AltibaseConnection)sCon).registerFailoverCallback(sCallback, sAppContext);
...
// 콜백 함수 해제
((AltibaseConnection)sCon).deregisterFailoverCallback();
코드 예제#
STF를 위한 콜백 함수를 구현하는 코드 예제이다.
아래의 예제는 여러 가지 경우를 무시한 단순 코드이므로, 사용자 애플리케이션에서 그대로 사용할 수 없음을 주의해야 한다.
public class MyFailoverCallback implements AltibaseFailoverCallback
{
public int failoverCallback(Connection aConnection, Object aAppContext,int aFailoverEvent)
{
Statement sStmt = null;
ResultSet sRes = null;
switch (aFailoverEvent)
{
// 사용자 어플리케이션의 로직상 Failover 시작 전에 필요한 작업을 진행할 수 있다.
case Event.BEGIN:
System.out.println(“Failover Started .... “);
break;
// 사용자 어플리케이션의 로직상 Failover 완료 후에 필요한 작업을 진행할 수 있다.
case Event.COMPLETED:
try
{
sStmt = aConnection.createStatement();
}
catch( SQLException ex1 )
{
try
{
sStmt.close();
}
catch( SQLException ex3 )
{
}
return Result.QUIT;
}
try
{
sRes = sStmt.executeQuery("select 1 from dual");
while(sRes.next())
{
if(sRes.getInt(1) == 1 )
{
break;
}
}
}
catch ( SQLException ex2 )
{
try
{
sStmt.close();
}
catch( SQLException ex3 )
{
}
// Failover 과정을 종료한다.
return Result.QUIT;
}
break;
}
// Failover 과정을 계속한다.
return Result.GO;
}
}
다음은 STF 성공 여부를 확인하는 코드 예제이다. STF가 성공했는지 실패했는지를 판단하는 방법은 SQLException의 ErrorCode가 Validation.FAILOVER_SUCCESS와 일치하는지 확인하는 것이다. while loop 내에 Failover 검증 코드를 삽입한 이유는 Failover가 성공하더라도 이전에 수행중이던 작업은 다시 수행해야 하기 때문이다.
// 반드시 수행하려던 작업을 재수행할 수 있도록 구현해야 한다.
// 이 경우에는 while loop를 사용하였다.
while (true)
{
try
{
sStmt = sConn.createStatement();
sRes = sStmt.executeQuery("SELECT C1 FROM T1");
while (sRes.next())
{
System.out.println("VALUE : " + sRes.getString(1));
}
}
catch (SQLException e)
{
// Failover 성공 여부 확인
if (e.getErrorCode() == AltibaseFailoverCallback.FailoverValidation.FAILOVER_SUCCESS)
{
// Failover가 성공했으므로 Exception을 무시하고 계속 진행한다.
continue;
}
System.out.println("EXCEPTION : " + e.getMessage());
}
break;
}
JDBC Escapes#
JDBC 스펙은 데이터베이스 제품들에 대해 벤더 특유의 SQL 문법을 JDBC 애플리케이션이 인식할 수 있도록 escape 문법을 제공한다. 즉, escape 문법이 포함된 SQL문에 대해서는 JDBC 드라이버가 자신의 데이터베이스에 맞는 SQL문으로 변환한다.
아래는 JDBC 스펙에서 지원하는 escape가 포함된 SQL문과 Altibase JDBC 드라이버가 이것을 Altibase용으로 변환한 SQL문을 정리한 표이다.
|
종류 |
JDBC 스펙에서 지원하는 SQL문 |
Altibase용으로 변환된 SQL문 |
|---|---|---|
|
ESCAPE |
SELECT cVARCHAR FROM t1 WHERE cVARCHAR LIKE '%a|%b%' {escape '|'} |
SELECT cVARCHAR FROM t1 WHERE cVARCHAR LIKE '%a|%b%' escape '|' |
|
FN |
SELECT {fn concat('concat', 'test')} FROM dual |
SELECT concat('concat', 'test') FROM dual |
|
DTS |
UPDATE t1 SET cDATE = {d '1234-12-30'} |
UPDATE t1 SET cDATE = to_date('1234-12-30', 'yyyy-MM-dd') |
|
UPDATE t1 SET cDATE = {t '12:34:56'} |
UPDATE t1 SET cDATE = to_date('12:34:56', 'hh24:mi:ss') |
|
|
UPDATE t1 SET cDATE = {ts '2010-01-23 12:23:45'} |
UPDATE t1 SET cDATE = to_date('2010-01-23 12:23:45', 'yyyy-MM-dd hh24:mi:ss') |
|
|
UPDATE t1 SET cDATE = {ts '2010-11-29 23:01:23.971589'} |
UPDATE t1 SET cDATE = to_date('2010-11-29 23:01:23.971589', 'yyyy-MM-dd hh24:mi:ss.ff6') |
|
|
CALL |
{call p1()} |
execute p1() |
|
{? = call p2(?)} |
execute ? := p2(?) |
|
|
OJ |
SELECT * FROM {oj t1 LEFT OUTER JOIN t2 ON t1.cINT = t2.cINT} |
SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.cINT = t2.cINT |
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이 발생하지 않게 하려면 의 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일 경우에는 아무런 동작도 일어나지 않는다.
Atomic Batch#
알티베이스 JDBC 드라이버는 Atomic Batch 기능을 제공하여, 일괄처리(Batch)의 원자성을 보장할 뿐 아니라 대용량의 데이터 삽입을 빠르게 처리할 수 있도록 지원한다.
이 절에서는 알티베이스 JDBC 드라이버가 지원하는 Atomic Batch 사용법에 대하여 설명한다.
사용법#
Atomic Batch 기능을 사용하기 위해서 우선 자바 프로그래밍에서 PreparedStatement 객체를 생성하여 AltibasePreparedStatement 클래스 타입으로 캐스팅한다.
다음은 Atomic Batch 기능을 사용하기 위해 호출하는 setAtomicBatch() 메소드이다.
public void setAtomicBatch(boolean aValue) throws SQLException
Atomic Batch가 PreparedStatement 객체에 설정되었는지 여부를 확인하려면 getAtomicBatch() 메소드를 호출한다.
public boolean getAtomicBatch()
제약 사항#
Altibase에서 Atomic Batch 기능을 사용할 때 아래의 제약 사항이 있다.
-
단순 INSERT 문에 대해서만 지원한다. 복합 INSERT 문이나 UPDATE, DELETE 등의 DML 문에 대한 정합성을 보장하지 못한다.
-
트리거가 동작할 때 수행 단위가 Each Statement일 경우 트리거는 한 번만 동작한다.
-
SYSDATE는 1번만 동작한다.
예제#
......
Connection con = sConn = DriverManager.getConnection(aConnectionStr, mProps);
Statement stmt = con.createStatement();
try
{
stmt.execute("Drop table " + TABLE_NAME); } catch (SQLException e) { }
stmt.execute("create table " + TABLE_NAME + "(c1 VARCHAR (1000))");
PreparedStatement sPrepareStmt = con.prepareStatement("insert into " + TABLE_NAME + " values(?)");
((AltibasePreparedStatement)sPrepareStmt).setAtomicBatch(true);
for(int i = 1; i <= MAX_RECORD_CNT; i++)
{
sPrepareStmt.setString(1, String.valueOf(i % 50));
sPrepareStmt.addBatch();
if(i%BATCH_SIZE == 0)
{
sPrepareStmt.executeBatch();
con.commit();
}
}
con.commit();
}
catch (SQLException e)
{
System.out.println(e.getMessage());
}
......
Date, Time, Timestamp#
이 절은 날짜형 데이터 타입인 Date, Time, 및 Timestamp 각각의 의미와, Altibase JDBC 드라이버에서 지원하는 데이터 변환 범위를 설명한다.
의미#
-
Date: 날짜만 표현
-
Time: 시각을 표현 (날짜가 포함될 수도 있음)
-
Timestamp: 날짜, 시각, 초 및 그 이하의 시각까지 표현
변환 표#
아래의 표는 setObject 메소드에 전달되는 객체의 타입에 따라 Altibase JDBC 드라이버가 처리하는 형식을 보여준다.
| 전달객체 | String | Date | Time | Timestamp |
|---|---|---|---|---|
| setObject (DATE) |
2134-12-23 00:00:00.0 사용자가 시분초 부분을 입력하면 오류 발생. 드라이버가 0으로 설정함. |
2134-12-23 00:00:00.0 시분초는 입력해도 드라이버가 무시함. |
SQLException: UNSUPPORTED_TYPE_CONVERSION | 2134-12-23 12:34:56.123456 |
| setObject (TIME) |
1970-01-01 12:34:56.0 사용자가 년월일 또는 nano초 부분을 입력하면 오류 발생. 드라이버가 기준 년월일로 설정함. |
2134-12-23 12:34:56.0 | 2134-12-23 12:34:56.0 | 2134-12-23 12:34:56.0 |
| setObject (TIMESTAMP) |
2134-12-23 12:34:56.123456 | 2134-12-23 00:00:00.0 시분초는 입력해도 드라이버가 무시함. |
SQLException: UNSUPPORTED_TYPE_CONVERSION | 2134-12-23 12:34:56.123456 |
| setString() | DATE_FORMAT 속성에 설정한 형식으로 입력해야 함. | - | - | - |
| setDate() | - | 2134-12-23 00:00:00.0 시분초는 입력해도 드라이버가 무시함. |
- | - |
| setTime() | - | - | 2134-12-23 12:34:56.0 | - |
| setTimestamp() | - | - | - | 2134-12-23 12:34:56.123456 |
아래는 데이터베이스에 저장되어 있는 DATE 타입의 값(1234-01-23 12:23:34.567123)을 getDate(), getTime(), 및 getTimestamp() 메소드를 사용해서 가져오는 값을 보여준다.
| 함수 | 반환값 |
|---|---|
| getDate() | 1234-01-23 00:00:00.0 |
| getTime() | 1234-01-23 12:23:34.0 |
| getTimestamp() | 1234-01-23 12:23:34.567123 |
GEOMETRY#
이 절은 Altibase가 제공하는 GEOMETY 타입의 데이터를 JDBC 애플리케이션에서 조작하는 방법을 설명한다.
사용 방법#
Altibase JDBC 애플리케이션에서는 GEOMETRY 타입의 데이터에 대해 byte 배열을 사용해서 조작할 수 있다.
PreparedStatement의 IN 파라미터를 사용해서 데이터베이스의 GEOMETRY 타입 칼럼에 데이터(NULL 포함)를 삽입하는 경우, AltibaseTypes.GEOMETRY 상수를 사용해서 데이터 타입을 반드시 명시해야 한다.
쿼리문에 직접 GEOMETRY 타입의 데이터를 표기하는 방법은 Spatial SQL Reference를 참고하도록 한다.
예제#
다음은 JDBC 애플리케이션에서 GEOMETRY 타입의 칼럼에 데이터를 삽입하는 코드 예제이다.
int sSize = ... ;
byte[] sGeometryData = new byte[sSize];
Connection sConn = ... ;
PreparedStatement sPstmt = sConn.prepareStatement("INSERT INTO TEST_TABLE VALUES (?)");
sPstmt.setObject(1, sGeometryData, AltibaseTypes.GEOMETRY);
sPstmt.executeQuery();
...
LOB#
이 절은 Altibase가 제공하는 LOB 타입의 데이터를 JDBC 애플리케이션에서 조작하는 방법을 설명한다.
전제 조건#
- Altibase가 지원하는 LOB 데이터 타입은 BLOB 및 CLOB이 있으며, 각각 4GB-1byte의 최대 크기를 가질 수 있다. 단, JDK 1.6 이상에서만 가능하다.
LOB 데이터를 정상적으로 다루기 위해서는 세션의 autocommit 모드가 아래의 조건 중 하나를 만족해야 한다.
-
Connection 객체의 setAutoCommit(false)을 사용해서 세션의 자동커밋을 해제(disable)하고 사용자가 수동으로 트랜잭션을 제어해야 한다.
-
Clientside_auto_commit을 on으로 지정하여 JDBC 드라이버가 트랜잭션의 자동커밋을 제어하도록 한다.
BLOB 사용하기#
아래는 JDBC 애플리케이션에서 BLOB 데이터를 조작하는 방법을 코드 예제로 보여준다.
PreparedStatement 객체를 통해서 BLOB 데이터 쓰기#
다음은 예제에서 사용되는 테이블을 생성하는 구문이다.
CREATE TABLE TEST_TABLE ( C1 BLOB );
1. InputStream 객체와 setBinaryStream 메소드를 사용한 방법#
InputStream sInputStream = ...
long sLength = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE VALUES (?)");
...
sPstmt.setBinaryStream(1, sInputStream, sLength);
...
sPstmt.execute();
...
JDK 1.5에서는 sPstmt를 AltibasePreparedStatement 타입으로 캐스팅 하면 long 타입의 길이 변수로 정의된 setBinaryStream() 메소드를 호출할 수 있다.
import Altibase.jdbc.driver.AltibasePreparedStatement;
...
((AltibasePreparedStatement)sPstmt).setBinaryStream(1, sInputStream, sLength);
...
2. OutputStream 객체와 setBinaryStream 메소드를 사용한 방법#
byte[] sBuf = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT * FROM TEST_TABLE FOR UPDATE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
Blob sBlob = sPstmt.getBlob(1);
OutputStream sOutputStream = sBlob.setBinaryStream(1);
sOutputStream.write(sBuf);
sOutputStream.close();
...
}
...
sPstmt.execute();
...
3. Blob 객체와 setBlob 메소드를 사용한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setBlob(1, sBlob);
...
sPstmt.execute();
...
4. Blob 객체와 setObject 메소드를 사용한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setObject(1, sBlob);
...
sPstmt.execute();
...
5. setObject 메소드에 SQL 타입을 지정한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setObject(1, sBlob, AltibaseTypes.BLOB);
...
sPstmt.execute();
...
ResultSet 객체를 통해서 BLOB 데이터 쓰기#
다음은 예제에서 사용되는 테이블을 생성하는 구문이다.
CREATE TABLE BLOB_TABLE ( BLOB_COLUMN BLOB );
1. InputStream 객체와 updateBinaryStream 메소드를 사용한 방법#
InputStream sInputStream = ...
long sLength = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateBinaryStream(1, sInputStream, sLength);
sRs.updateRow();
...
}
...
2. Blob 객체와 updateBlob 메소드를 사용한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateBlob(1, sBlob), ;
sRs.updateRow();
...
}
...
3. Blob 객체와 updateObject 메소드를 사용한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateObject(1, sBlob);
sRs.updateRow();
...
}
...
4. updateObject 메소드에 SQL 타입을 지정한 방법#
java.sql.Blob sBlob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateObject(1, sBlob, AltibaseTypes.BLOB);
sRs.updateRow();
...
}
...
SELECT … FOR UPDATE 구문으로 BLOB 데이터 갱신#
byte[] sBytes = new byte[sLength];
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE FOR UPDATE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Blob sBlob = sRs.getBlob(1);
sBlob.setBytes(0, sBytes);
...
}
...
BLOB 데이터 읽기#
LOB 데이터를 읽을 때, 내부적으로는 Lob Locator를 이용하기 때문에 반드시 commit 또는 rollback과 같은 명시적인 트랜잭션 종료 작업을 추가해야 한다.
1. InputStream 객체와 getBinaryStream 메소드를 사용한 방법#
...
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT BLOB_COLUMN
FROM BLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
InputStream sInputStream = sRs.getBinaryStream(1);
...
}
...
sCon.commit();
2. getBlob 메소드와 InputStream 객체를 사용한 방법#
...
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT BLOB_COLUMN
FROM BLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Blob sBlob = sRs.getBlob(1);
InputStream sInputStream = sBlob.getBinaryStream();
...
}
...
sCon.commit();
3. getBlob 메소드와 byte 배열을 사용한 방법#
...
final int sReadLength = 100;
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT BLOB_COLUMN FROM BLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Blob sBlob = sRs.getBlob(1);
long sRemains = sBlob.length();
long sOffset = 0;
while(sRemains > 0)
{
byte[] sReadBytes = sBlob.getBytes(sOffset, sReadLength);
sRemains -= sReadBytes.length;
sOffset += sReadBytes.length;
...
}
...
}
...
sCon.commit();
BLOB 데이터 변경하기#
Truncation#
Statement sStmt = ...
ResultSet sRs = sStmt.executeQuery("SELECT * FROM t1 FOR UPDATE");
while(sRs.next())
{
...
int sLength = ... ;
Blob sBlob = sRs.getBlob(2);
// After executing this method
// sBlob.length() == sLength
sBlob.truncate(sLength);
}
...
CLOB 사용하기#
아래는 JDBC 애플리케이션에서 CLOB 데이터를 조작하는 방법을 코드 예제로 보여준다.
PreparedStatement를 통해서 CLOB 데이터 쓰기#
다음은 예제에서 사용되는 테이블을 생성하는 구문이다.
CREATE TABLE TEST_TABLE ( C1 BLOB );
1. Reader 객체와 setCharacterStream 메소드를 사용한 방법#
Reader sReader = ...
long sLength = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setCharacterStream(1, sReader, sLength);
...
sPstmt.execute();
...
JDK 1.5에서는 sPstmt를 AltibasePreparedStatement 타입으로 캐스팅 하면 long 타입의 길이 변수로 정의된 setCharacterStream() 메소드를 호출할 수 있다.
import Altibase.jdbc.driver.AltibasePreparedStatement;
...
((AltibasePreparedStatement)sPstmt).setCharacterStream(1, sReader, sLength);
...
2. Writer 객체와 setCharacterStream 메소드를 사용한 방법#
char[] sBuf = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT * FROM TEST_TABLE FOR UPDATE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
Clob sClob = sPstmt.getClob(1);
Writer sWriter = sClob.setCharacterStream(1);
sWriter.write(sBuf);
sWriter.close();
...
}
...
sPstmt.execute();
...
3. Clob 객체와 setClob 메소드를 사용한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setClob(1, sClob);
...
sPstmt.execute();
...
4. Clob 객체와 setObject 메소드를 사용한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setObject(1, sClob);
...
sPstmt.execute();
...
5. setObject 메소드에 SQL 타입을 지정한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("INSERT INTO TEST_TABLE
VALUES (?)");
...
sPstmt.setObject(1, sClob, AltibaseTypes.Clob);
...
sPstmt.execute();
...
ResultSet 객체를 사용해서 CLOB 데이터 쓰기#
다음은 예제에서 사용되는 테이블을 생성하는 구문이다.
CREATE TABLE CLOB_TABLE ( CLOB_COLUMN CLOB );
1. Reader 객체와 updateCharacterStream 메소드를 사용한 방법#
Reader sReader = ...
long sLength = ... // The length of source from which Reader is linked
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateCharacterStream(1, sReader, sLength);
sRs.updateRow();
...
}
...
2. Clob 객체와 updateClob 메소드를 사용한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateClob(1, sClob);
sRs.updateRow();
...
}
...
3. Clob 객체와 updateObject 메소드를 사용한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateObject(1, sClob);
sRs.updateRow();
...
}
...
4. updateObject 메소드에 SQL 타입을 지정한 방법#
java.sql.Clob sClob = ...
...
PreparedStatement sPstmt = connection().prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
sRs.updateObject(1, sClob, AltibaseTypes.CLOB);
sRs.updateRow();
...
}
...
SELECT … FOR UPDATE 구문으로 CLOB 데이터 삽입#
...
String sStr = ... ;
PreparedStatement sPstmt = connection().prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE FOR UPDATE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Clob sClob = sRs.getClob(1);
sClob.setString(0, sStr);
...
}
...
CLOB 데이터 읽기#
LOB 데이터를 읽을 때, 내부적으로는 Lob Locator를 이용하기 때문에 반드시 commit 또는 rollback과 같은 명시적인 트랜잭션 종료 작업을 추가해야 한다.
1. Reader 객체와 getCharacterStream 메소드를 사용한 방법#
...
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Reader sReader = sRs.getCharacterStream(1);
...
}
...
sCon.commit();
2. Reader 객체 와 getClob 메소드를 사용한 방법#
...
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Clob sClob = sRs.getClob(1);
Reader sReader = sClob.getCharacterStream();
...
}
...
sCon.commit();
3. getClob 메소드와 String 객체를 사용한 방법#
...
final int sReadLength = 100;
sCon = getConnection();
PreparedStatement sPstmt = sCon.prepareStatement("SELECT CLOB_COLUMN FROM CLOB_TABLE");
ResultSet sRs = sPstmt.executeQuery();
while(sRs.next())
{
...
Clob sClob = sRs.getClob(1);
long sRemains = sClob.length();
long sOffset = 0;
while(sRemains > 0)
{
String sStr = sClob.getSubString(sOffset, sReadLength);
sRemains -= sStr.length;
sOffset += sStr.length;
...
}
...
}
...
sCon.commit();
CLOB 데이터 변경하기#
Truncation#
Statement sStmt = ...
ResultSet sRs = sStmt.executeQuery("SELECT * FROM t1 FOR UPDATE");
while(sRs.next())
{
...
int sLength = ... ;
Clob sClob = sRs.getClob(2);
// After executing this method
// sClob.length() == sLength
sClob.truncate(sLength);
}
...
createBlob(), createClob()을 이용한 LOB 사용#
createBlob(), createClob() 메소드를 이용하여 LOB 데이터를 사용하는 방법을 설명한다.
장점#
JDBC 3.0을 준수하는 이전 버전의 JDBC 드라이버는 LOB 객체를 사용하려면 Statement 객체에서 getBlob() 또는 getClob() 메소드를 사용해 LOB 객체를 획득해야 하지만 Altibase 7.3 이상의 JDBC 드라이버는 connection 객체에서 빈 LOB 객체를 생성하여 LOB 데이터를 저장할 수 있어서 사용성이 크게 향상되었다.
JDBC 3.0 을 준수하는 JDBC 드라이버에서 LOB 객체 사용 예
Connection sConn = getConnection();
PreparedStatement sStmt = sConn.prepareStatement("INSERT INTO T1 VALUES (?)");
File sFile = new File(aFileUrl2);
FileReader sFileReader = new FileReader(sFile);
// clob 객체를 바로 사용할 수 없고 setCharacterStream을 사용해야 한다.
sStmt.setCharacterStream(1, sFileReader, (int)sFile.length());
...
sFile = new File(aFileUrl2);
FileInputStream sFInStream = new FileInputStream(sFile);
// blob 객체를 바로 사용할 수 없고 setBinaryStream을 사용해야 한다.
sStmt.setBinaryStream(1, sFInStream , (int)sFile.length());
...
sStmt.executeUpdate();
...
// LOB 객체를 사용하려면 먼저 execute를 수행하여 ResultSet으로부터 LOB 객체를 획득해야 한다.
Statement sStmt = sConn.createStatement();
ResultSet sRs = sStmt.executeQuery("SELECT * FROM t1");
Clob sClob = sRs.getClob(1);
...
Altibase 7.3 이상의 JDBC 드라이버에서 LOB 객체 사용 예
Connection sConn = getConnection();
PreparedStatement sStmt = sConn.prepareStatement("INSERT INTO T1 VALUES (?)");
Clob sClob = sConn.createClob(); // Connection 객체에서 빈 CLOB 객체 생성
String sStr = readFile(blahblah);
sClob.setString(1, sStr); // 빈 CLOB 객체에 데이터 저장
sStmt.setClob(1, sClob); // CLOB 객체를 바로 사용할 수 있음.
...
Blob sBlob = sConn.createBlob();
byte[] sBytes = getBytesFromFile();
sBlob.setBytes(1, sBytes); // Connection 객체에서 생성한 빈 BLOB 객체에 데이터 저장
sStmt.setBlob(1, sBlob); // BLOB 객체를 바로 사용할 수 있음.
...
sStmt.executeUpdate();
고려 사항#
createBlob(), createClob()은 데이터를 포함하지 않은 LOB 객체를 생성한다. 이렇게 생성된 LOB 객체에 LOB 데이터를 저장하고 조작하는 것은 JDBC 드라이버에서 관리한다. 따라서 클라이언트의 처리 성능과 메모리 사용에 영향을 줄 수 있다. LOB 데이터 크기에 따라 메모리 부족(OutOfMemory) 에러가 발생할 수도 있으므로 이때는 일반적인 stream 방식으로 LOB 데이터를 처리해야 한다.
또한 createBlob(), createClob()으로 생성한 LOB 객체는 long 데이터형을 지원하지 않는다.
BLOB 데이터 입력 예제#
createBlob() 메소드로 생성한 BLOB 객체와 PreparedStatement.setBlob() 메소드를 사용하여 BLOB 데이터를 입력하는 예제이다.
Connection sConn = getConnection();
java.sql.Blob sBlob = sConn.createBlob(); // createBlob()으로 빈 BLOB 객체를 생성
sBlob.setBytes(...); // 빈 BLOB 객체에 BLOB 데이터를 할당
...
PreparedStatement sPstmt = sConn.prepareStatement("INSERT INTO BLOB_TABLE VALUES (?)");
sPstmt.setBlob(1, sBlob);
...
sPstmt.executeUpdate();
sBlob.free();
sPstmt.close();
...
CLOB 데이터 입력 예제#
createClob() 메소드로 생성한 CLOB 객체와 PreparedStatement.setClob() 메소드를 사용하여 CLOB 데이터를 입력하는 예제이다.
Connection sConn = getConnection();
java.sql.Clob sClob = sConn.createClob(); // createClob()으로 빈 CLOB 객체를 생성
sClob.setString(...); // 빈 CLOB 객체에 CLOB 데이터를 할당
...
PreparedStatement sPstmt = sConn.prepareStatement("INSERT INTO CLOB_TABLE VALUES (?)");
sPstmt.setClob(1, sClob);
...
sPstmt.executeUpdate();
sClob.free();
sPstmt.close();
...
자원 해제하기#
많은 수의 LOB 객체를 사용해서 데이터를 획득하는 JDBC 애플리케이션의 경우, 반드시 획득한 LOB 객체를 해제하여야 한다. 또한 트랜잭션을 커밋하는 것과 상관없이 LOB 객체는 명시적으로 해제되어야 한다.
아래는 Blob 객체를 해제하는 코드 예제이다.
...
Blob sBlob = sRs.getBlob(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.AltibaseLob)sBlob).free();
...
Blob 객체를 free 메소드로 해제하면, 대응하는 Lob Locator가 서버에서 해제되므로 그 객체에 대해서는 더이상 연산 수행이 불가능하다.
아래는 Clob 객체를 해제하는 코드 예제이다.
...
Clob sClob = sRs.getClob(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.AltibaseLob)sClob).free();
...
Clob 객체도 Blob과 마찬가지로 free 메소드로 해제하면, 대응하는 Lob Locator가 서버에서 해제되므로 그 객체에 대해서는 더 이상 연산 수행이 불가능하다.
아래는 BlobInputStream 객체와 BlobOutputStream 객체를 해제하는 코드 예제이다.
InputStream sInputStream = sRs.getBinaryStream(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.BlobInputStream)sInputStream).freeLocator();
CallableStatement sCallStmt = aConn.prepareCall("INSERT INTO TEST_TABLE VALUES (?)");
sCallStmt.registerOutParameter(1, Types.BLOB);
sCallStmt.execute();
Blob sBlob = sCallStmt.getBlob(1);
OutputStream sOutputStream = sBlob.setBinaryStream(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.BlobOutputStream)sOutputStream).freeLocator();
BlobInputStream 또는 BlobOutputStream 객체를 freeLocator 메소드로 해제하면, 대응하는 Lob Locator가 서버에서 해제되므로 그 객체에 대해서는 더이상 연산 수행이 불가능하다.
아래는 ClobReader 객체와 ClobWriter 객체를 해제하는 코드 예제이다.
Reader sClobReader = sRs.getCharacterStream(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.ClobReader)sClobReader).freeLocator();
CallableStatement sCallStmt = aConn.prepareCall("INSERT INTO TEST_TABLE VALUES (?)");
sCallStmt.registerOutParameter(1, Types.CLOB);
sCallStmt.execute();
Clob sClob = sCallStmt.getClob(1);
Writer sClobWriter = sClob.setCharacterStream(1);
// Freeing Lob Locator
((Altibase.jdbc.driver.ClobWriter)sClobWriter).freeLocator();
ClobReader 또는 ClobWriter 객체를 freeLocator 메소드로 해제하면, 대응하는 Lob Locator가 서버에서 해제되므로 그 객체에 대해서는 더이상 연산 수행이 불가능하다.
제약 사항#
clientside_auto_commit을 on으로 지정하여 JDBC 드라이버가 트랜잭션의 자동커밋을 제어하도록 하여도 LOB 데이터를 다루는 것에는 아래와 같은 제약이 여전히 존재한다.
ResultSet 객체(커서)를 통해 가져온 LOB 데이터를 커서가 닫히기 전에 다른 Statement의 executeUpdate() 메소드에 사용하면 Lob locator가 사라지기 때문에 그 커서로부터 더 이상의 fetch가 불가능해진다. 아래는 이러한 오류 상황이 발생할 수 있는 예제 코드이다.
PreparedStatement sPreStmt =
sCon.prepareStatement( "INSERT INTO TEST_TEXT " +
"VALUES ( ?, ?, ?, ? )" );
Statement sStmt = sCon.createStatement();
ResultSet sRS = sStmt.executeQuery( "SELECT ID, TEXT " +
" FROM TEST_SAMPLE_TEXT " );
while ( sRS.next() ) -> (2)
{
sID = sRS.getInt( 1 );
sClob = sRS.getClob( 2 );
switch ( sID )
{
case 1 :
sPreStmt.setInt( 1, 1 );
sPreStmt.setString( 2, "Altibase Greetings" );
sPreStmt.setClob( 3, sClob );
sPreStmt.setInt( 4, (int)sClob.length() );
break;
case 2 :
sPreStmt.setInt( 1, 2 );
sPreStmt.setString( 2, "Main Memory DBMS" );
sPreStmt.setClob( 3, sClob );
sPreStmt.setInt( 4, (int)sClob.length() );
break;
default :
break;
}
sPreStmt.executeUpdate(); -> (1)
}
(1): ResultSet sRS가 열려 있는 상태에서 sPreStmt.executeUpdate()를 호출하면, JDBC 드라이버가 트랜잭션을 자동으로 commit하면서 sClob의 Lob locator가 사라진다.
(2): Lob locator가 사라졌으므로 sRs.next()에서 exception이 발생할 수 있다.
따라서, 이러한 로직에서 LOB 데이터를 다룰 때는 먼저 setAutoCommit(false)를 호출하여 세션의 자동커밋을 해제해야 한다.
NOT NULL 제약이 있는 LOB 타입 칼럼에 setNull을 수행하면 [Unable to insert (or update) NULL into NOT NULL column.] 에러가 발생한다. 이 경우, Empty로 초기화된 데이터가 남아 있어 반드시 트랜잭션을 롤백해야 한다.
Autocommit 제어#
Altibase JDBC 애플리케이션에서는 auto_commit 연결 속성 또는 JDBC Connection 객체의 setAutoCommit 메소드를 사용해서 세션의 자동커밋(autocommit) 모드를 정할 수 있다. 사용자가 auto_commit=true 또는 setAutoCommit(true) 메소드를 사용해서 자동커밋을 설정(enable)하면, Altibase 서버가 트랜잭션을 자동으로 커밋한다.
연결 속성 clientside_auto_commit을 이용해서 자동커밋 모드를 설정할 수도 있다. 연결 속성 clientside_auto_commit을 on으로 지정하면, Altibase 서버 대신에 JDBC 드라이버가 트랜잭션을 자동으로 커밋한다.
clientside_auto_commit을 off로 설정한 경우에는 세션의 자동커밋 모드가 setAutoCommit 메소드에 의해 결정된다.
세션의 자동커밋을 해제(disable)하려면 setAutoCommit(false)을 호출하면 된다.
clientside_auto_commit=on인 세션에서도 setAutoCommit(false)을 호출하면 자동커밋은 해제되며, JDBC 드라이버의 자동 커밋 모드로 전환하려면 setAutoCommit(true)를 호출하면 된다.
자동커밋이 해제되면, 사용자가 commit() 또는 rollback() 메소드를 사용해서 트랜잭션을 수동으로 커밋하거나 롤백해야 한다.
자동커밋 모드를 설정하고 해제하는 방법을 정리하면 아래의 표와 같다.
| Autocommit 모드 | 설정 방법 |
|---|---|
| 서버에서 트랜잭션 자동커밋 | auto_commit=true(또는 미지정) or setAutoCommit(true) |
| JDBC 드라이버에서 트랜잭션 자동커밋 | auto_commit=true(또는 미지정) and clientside_auto_commit=on |
| 자동커밋 해제 | auto_commit=false or setAutoCommit(false) |
BIT, VARBIT#
이 절은 BIT, VARBIT 타입의 데이터를 JDBC 애플리케이션에서 조작하는 방법과 주의사항을 설명한다.
사용 방법#
Altibase JDBC 애플리케이션에서는 Java BitSet 클래스를 사용하여 BIT, VARBIT 타입의 데이터를 조작할 수 있다.
PreparedStatement의 IN 파라미터를 사용할 때는 Types.BIT로 타입을 지정하거나, 지정하지 않을 때에는 BitSet 또는 문자열로 값을 지정할 수 있다.
주의사항#
비트가 '0'으로 끝나는 BIT, VARBIT 값을 만들려면, 특정 길이의 비트 값으로 구성할 수 있는 AltibaseBitSet 클래스 또는 문자열의 값을 사용해야 한다. Java BitSet은 어떤 비트를 set()했는지 기억하도록 구현된 것으로, 0으로 구성된 특정 길이의 BIT 값은 만들 수 없기 때문이다.
예제#
다음은 JDBC 애플리케이션에서 BIT, VARBIT 타입의 칼럼에 데이터를 삽입하는 코드 예제이다.
...
BitSet sBitSet1 = new BitSet();
sBitSet1.set(1);
BitSet sBitSet2 = new AltibaseBitSet( 5 );
sBitSet1.set(2);
PreparedStatement sPstmt = sConn.prepareStatement("INSERT INTO TEST_TABLE VALUES (?)");
sPstmt.setObject(1, sBitSet1, Types.BIT);
sPstmt.executeUpdate();
sPstmt.setObject(1, sBitSet2);
sPstmt.executeUpdate();
sPstmt.setObject(1, "0110100", Types.BIT);
sPstmt.executeUpdate();
...
JDBC 로깅#
JDBC 로깅(JDBC Logging)은 Altibase JDBC 드라이버에서 발생하는 각종 로그를 기록하는 것을 의미하며, java.util.logging 패키지를 이용하여 관련된 로그를 기록할 수 있다. 이 절에서는 JDBC 로깅을 하기 위한 설정 방법 및 사용법을 설명한다.
JDBC 로깅 설정 방법#
JDBC 드라이버로부터 로그를 남기려면 로깅 기능이 추가된 JDBC jar 파일을 사용해야 한다. 그리고 ALTIBASE_JDBC_TRACE 환경 변수에서 로깅 기능을 활성화한 후 사용하면 된다.
JRE버전#
JRE 1.5 이상이 설치되어야 JDBC 로깅을 사용할 수 있으며, 이외의 라이브러리는 필요하지 않다.
CLASSPATH 설정#
로깅을 사용하려면 CLASSPATH 환경변수에 Altibase_t.jar 파일을 추가해야 한다.
ex) 유닉스 환경에서 bash 쉘을 사용하는 경우
export CLASSPATH=$ALTIBASE_HOME/lib/Altibase_t.jar:.:$CLASSPATH
로깅 활성화#
JVM 파라미터를 이용하여 ALTIBASE_JDBC_TRACE 환경 변수를 아래와 같이 TRUE로 설정하면, 클라이언트의 프로그램 수정 없이 글로벌하게 로깅 기능이 활성화된다. 그러나 ALTIBASE_JDBC_TRACE의 변경 값을 적용하려면, 클라이언트 프로그램을 재시작해야 한다.
java -DALTIBASE_JDBC_TRACE=true ...
JDBC 로깅 사용법#
java.util.logging 파일 설정#
java.util.logging 설정은 \$JRE_HOME/lib/logging.properties 파일에서 설정하거나, 아래와 같이 java.util.logging.config.file를 이용하여 별도로 설정할 수 있다.
java -Djava.util.logging.config.file=$ALTIBASE_HOME/sample/JDBC/Logging/logging.properties -DALTIBASE_JDBC_TRACE=true ...
알티베이스는 \$ALTIBASE_HOME/sample/JDBC/Logging 디렉토리에 logging.properties 샘플 파일을 제공하고 있으며, 이를 참조하여 사용하거나, 직접 설정 파일을 생성하여 Djava.util.logging.config.file 프로퍼티를 통해 사용하면 된다.
Logger의 종류#
로거는 트리 구조로 구성되어 있으며 부분별로 로그의 양이나 셋팅을 조절할 때 사용한다. 알티베이스 JDBC 드라이버에서 지원하는 Logger의 종류는 아래와 같다.
| Logger | 설명 |
|---|---|
| altibase.jdbc | 알티베이스 JDBC 메시지 (Connection, Statement, PreparedStatement 등의 JDBC API call) |
| altibase.jdbc.pool | 커넥션 풀 관련 메시지 |
| altibase.jdbc.rowset | ResultSet 관련 메시지 |
| altibase.jdbc.xa | xa 관련 메시지 |
| altibase.jdbc.failover | failover 관련 메시지 |
| altibase.jdbc.cm | CmChannel 네트워크 패킷 메시지 |
Logger 레벨#
logger에 레벨을 설정하면 보다 상세하게 로그의 양을 조절할 수 있다. 알티베이스 JDBC 드라이버에서 제공하는 레벨은 아래와 같으며, SEVERE에서 FINEST으로 갈수록 상세한 로그를 남긴다. CONFIG레벨을 설정하면 SEVERE, WARNING, INFO, CONFIG 레벨의 로그들이 남는다.
| Logger 레벨 | 설명 |
|---|---|
| OFF | 로그를 남기지 않는다. |
| SEVERE | SQLException이나 내부 에러가 발생했을 때 해당 에러만 로그에 남긴다. |
| WARNING | SQLWarning을 로그에 남긴다. |
| INFO | JDBC 드라이버 내부적으로 특정 객체의 상태를 모니터링하여 로그에 남긴다. |
| CONFIG | JDBC 드라이버 내부적으로 어떤 SQL문이 실행되는지 확인할 때 주로 사용한다. PreparedStatement같은 경우 prepare할 때 sql이 표시되며, Statement는 execute 할 때 sql이 표시된다. 이때 sql이 실행되는데 걸린 시간이 milli sec 단위로 같이 표시된다. |
| FINE | 표준 JDBC API에 진입하고 리턴될 때 해당 인자 값과 반환 값을 로그에 남긴다. API에 진입할 때마다 로그가 남기 때문에 로그의 양이 많아질 수 있으며, Connection이나 Statement가 close되는데 걸린 시간이 추가로 표시된다. |
| FINEST | JDBC 드라이버와 알티베이스 서버가 주고 받는 패킷 정보를 로그로 남긴다. 로그의 양이 가장 많다. |
logging.properties#
다음은 로그 레벨을 CONFIG로 하고, 네트워크 패킷 로그를 남기는 샘플 logging.properties이며, \$ALTIBASE_HOME/sample/JDBC/Logging/logging.properties 에서도 내용을 참조할 수 있다.
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler --> 기본적인 handler로 FileHandler와 ConsoleHandler를 추가한다.
.level = CONFIG --> 루트 logger 레벨을 CONFIG로 셋팅한다.
# default file output is in same directory.
java.util.logging.FileHandler.level = CONFIG
java.util.logging.FileHandler.pattern = ./jdbc_trace.log
java.util.logging.FileHandler.limit = 10000000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.append = false
java.util.logging.FileHandler.formatter = Altibase.jdbc.driver.logging.SingleLineFormatter
--> java.util.logging.FileHandler의 기본셋팅을 설정하는 부분이다. sql정보만 보면 되기때문에 FileHandler의 레벨을 CONFIG으로 설정했다.
java.util.logging.ConsoleHandler.level = CONFIG
java.util.logging.ConsoleHandler.formatter = Altibase.jdbc.driver.logging.SingleLineFormatter
--> java.util.logging.ConsoleHandler를 설정하는 부분이다. 로그를 한 줄로 출력하기 위해 SingleLineFormatter를 사용했다.
altibase.jdbc.level = CONFIG
altibase.jdbc.rowset.level = SEVERE
altibase.jdbc.cm.level = FINEST
altibase.jdbc.cm.handlers = Altibase.jdbc.driver.logging.MultipleFileHandler
#altibase.jdbc.cm.handlers = java.util.logging.FileHandler
--> altibase jdbc logger를 설정하는 부분으로 레코드 셋의 정보는 제외해야 하기 때문에 rowset의 레벨을 SEVERE로 설정했다. 네트워크 패킷 정보는 로그로 남겨야 하기 때문에 cm의 레벨을 FINEST로 설정했다. 또한 네트워크 패킷같은 경우 세션별로 파일을 별도 저장하기 위해 handler로 MultipleFileHandler를 사용했다.
Altibase.jdbc.driver.logging.MultipleFileHandler.level = FINEST
Altibase.jdbc.driver.logging.MultipleFileHandler.pattern = ./jdbc_net_%s.log
Altibase.jdbc.driver.logging.MultipleFileHandler.limit = 10000000
Altibase.jdbc.driver.logging.MultipleFileHandler.count = 1
Altibase.jdbc.driver.logging.MultipleFileHandler.formatter = java.util.logging.XMLFormatter
--> MultipleFileHandler를 설정하는 부분으로 pattern에 jdbc_net_%s.log를 사용해 세션의 아이디별로 파일이 생성되도록 설정했다. 또한 formatter로 XMLFormatter를 사용해 XML형태의 파일에 로그가 생성되도록 설정했다.
Hibernate#
Altibase는 비표준 SQL을 제공하며, Hibernate 는 이러한 기능을 수행할 수 있도록 Dialect 클래스를 지원한다. Hibernate 에서 Altibase 를 연동하려면 Altibase 의 JDBC Driver 를 설정하고, Hibernate 의 configuration 에 AltibaseDialect.class 를 지정해야 한다.
AltibaseDialect#
Hibernate 6.4 부터 공식 지원#
Hibernate 6.4 부터는 AltibaseDialect가 Hibernate ORM 패키지에 포함되었다. 이제 AltibaseDialect를 사용하려면 Maven 의존성 설정만 추가하면 된다.
Hibernate 6.4 이전#
Hibernate 6.4 이전 버전에서는 AltibaseDialect가 없으므로, AltibaseDialect.class 를 직접 지정해야 한다. 이를 위해서는 Altibase 에서 제공하는 AltibaseDialect.java 파일 (필요에 따라 AltibaseLimitHandler.java 포함)을 컴파일하고 Hibernate 가 제공하는 파일에 포팅해야 사용할 수 있다. AltibaseDialect.java 파일과 AltibaseLimitHandler.java 파일은 Altibase Github 사이트에서 제공한다. 상세한 사용 방법은 AltibaseDialect 포팅 방법 을 참고한다.
Maven 의존성(Dependency) 설정#
AltibaseDialect 의존성 추가#
Hibernate 6.4 부터 hibernate-community-dialects에 AltibaseDialect가 포함되었기 때문에, 아래와 같이 의존성을 추가하면 된다.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
<version>6.4.1.Final</version>
</dependency>
Altibase JDBC 드라이버 의존성 추가#
Altibase 7.3.0.0.2부터 Maven Central Repository에서 Altibase JDBC 드라이버를 다운로드할 수 있어서, 아래와 같이 의존성을 추가하면 된다.
<dependency>
<groupId>com.altibase</groupId>
<artifactId>altibase-jdbc</artifactId>
<version>7.3.0.0.2</version>
</dependency>
Lob 관련 연결 속성#
Altibase 7.1에서는 Lob 칼럼 값이 null인 경우, ResultSet.getBlob(), ResultSet.getClob() 수행시 Lob객체를 반환하기 때문에 lob_null_select 연결 속성의 값을 "off"로 변경해야 했다. 그러나 Altibase 7.3 부터는 lob_null_select 연결 속성의 기본값이 off로 변경됨에 따라, 더이상 연결 속성을 변경하지 않아도 된다.
SQL Plan#
SQL 실행 계획을 문자열로 가져오는 기능을 비표준 API로 제공한다. 실행 계획은 Altibase가 명령문을 실행하기 위해 수행하는 작업의 순서를 나타낸다. Option에는 ON, OFF, 또는 ONLY가 올 수 있으며 기본 설정값은 OFF이다.
사용법#
실행 계획을 가져오기 위해서는 SQL 문을 수행하기 전에 AltibaseConnection 객체의 setExplainPlan(byte aExplainPlanMode) 메소드를 호출해, 어떤 내용의 실행 계획을 가져올지 지정해야 한다. 지정 가능한 aExplainPlanMode 옵션은 아래 표에 기술되어 있다. AltibaseStatement 객체에 SQL 문을 입력 후, getExplainPlan() 메서드를 호출하여 문자열 행태의 실행 계획을 반환 받을 수 있다.
인자#
| 속성 | 속성값 | 내용 |
|---|---|---|
| AltibaseConnection.EXPLAIN_PLAN_OFF | 0 | SELECT 문 실행 후 Plan Tree 정보는 보여주지 않고 결과 레코드만 보여준다. |
| AltibaseConnection.EXPLAIN_PLAN_ON | 1 | SELECT 문 실행 후 결과 레코드와 함께 Plan Tree의 정보를 보여준다. Plan tree에는 레코드 접근 횟수 및 튜플이 점유한 메모리 양, 비용 등이 출력된다. |
| AltibaseConnection.EXPLAIN_PLAN_ONLY | 2 | SELECT 문 실행 후 결과 레코드와 함께 Plan Tree의 정보를 보여준다. EXPLAN PLAN = ONLY인 경우 질의 실행 없이 실행 계획만 생성하므로, ACCESS 항목과 같이 실제 실행 후 그 값이 결정되는 항목들은 물음표(“??”)로 표시된다. |
코드 예제#
AltibaseConnection sConn = (AltibaseConnection)DriverManager.getConnection(sURL, sProps);
sConn.setExplainPlan(AltibaseConnection.EXPLAIN_PLAN_ONLY);
AltibaseStatement sStmt = (AltibaseStatement)sConn.prepareStatement("SELECT sysdate FROM dual");
System.out.println(sStmt.getExplainPlan());
코드 결과#
------------------------------------------------------------
PROJECT ( COLUMN_COUNT: 1, TUPLE_SIZE: 8, COST: 0.01 )
SCAN ( TABLE: DUAL, FULL SCAN, ACCESS: ??, COST: 0.01 )
------------------------------------------------------------