내용 목차
본 장에서는 tbJDBC를 사용하여 애플리케이션 프로그램을 개발하는 과정을 순서대로 설명하고, 추가로 데이터 타입, Stream 등을 설명한다.
다음은 tbJDBC를 사용하여 애플리케이션 프로그램을 개발하는 과정이다.
기본이 되는 Java 패키지와 tbJDBC 패키지를 import한다.
/* 기본 Java 패키지 */ import java.sql.*; /* tbJDBC 패키지 */ import com.tmax.tibero.jdbc.*; import com.tmax.tibero.jdbc.ext.*;
데이터베이스에 연결하기 위해서 Connection 객체를 생성한다. Connection 객체를 생성하기 위해서 tbJDBC를 로딩한 후 DriverManager를 이용한다.
Class.forName("com.tmax.tibero.jdbc.TbDriver"); Connection conn = DriverManager.getConnection ("jdbc:tibero:thin:@localhost:8629:dbsvr", "tibero", "tmax");
또는 다음과 같이 java.util.Properties를 이용하여 Conneciton할 때 설정 정보를 설정할 수 있다.
Class.forName("com.tmax.tibero.jdbc.TbDriver"); Properties prop = new Properties(); prop.setProperty("user", "tibero"); prop.setProperty("password", "tmax"); Connection conn = DriverManager.getConnection("jdbc:tibero:thin:@localhost:8629:dbsvr", prop);
또는 다음과 같이 DataSource 객체를 이용하여 Connection 객체를 생성한다.
TbDataSource ds = new TbDataSource(); ds.setURL("jdbc:tibero:thin:@localhost:8629:dbsvr"); ds.setUser("tibero"); ds.setPassword("tmax"); Connection conn = ds.getConnection();
데이터베이스에 연결되면, Connection 객체를 이용하여 Statement 객체를 생성한다.
Statement stmt = conn.createStatement(); PreparedStatement pstmt = conn.prepareStatement("SELECT empno, name FROM emp"); CallableStatement cstmt = conn.prepareCall("BEGIN ... END;");
질의문을 수행하고 ResultSet 객체를 받는다.
ResultSet rs = stmt.executeQuery("SELECT * FROM ALL_OBJECTS;");
ResultSet 객체는 next() 메소드를 이용하여 모든 로우에 접근할 수 있다. 이때 getXXX() 메소드를 호출하면 원하는 타입별로 로우 데이터를 가져올 수 있다. 만약 next() 메소드가 false라면, 더는 결과 집합이 존재하지 않음을 의미한다.
while (rs.next()) { System.out.println(rs.getString(1)); System.out.println(rs.getInt(2)); }
기본적으로 DML 문(INSERT, UPDATE, DELETE)을 수행한 다음에는 자동으로 커밋된다. 이러한 기능을 auto-commit이라고 한다. 사용자는 Connection 클래스에서 제공하는 다음의 메소드를 이용하여 이 기능을 임의로 해제할 수 있다.
void setAutoCommit(boolean flag)
이 메소드를 이용하여 auto-commit 기능을 비활성화하면, 사용자는 반드시 DML 문을 사용한 다음 커밋이나 롤백을 수행해야 데이터베이스에 적용된다.
conn.commit(); conn.rollback();
ResultSet 객체와 Statement 객체는 반드시 소멸시켜야 한다. 왜냐하면, tbJDBC 내부에는 별도의 finalizer 메소드가 없기 때문이다. 이 때문에 예상치 못한 시점에 심각한 메모리 누수 현상이 발생할 수 있고, 데이터베이스 내부 커서의 최대 허용 범위를 초과하여 에러가 발생할 수도 있다.
rs.close(); stmt.close();
마지막으로 모든 작업이 끝나면 반드시 데이터베이스 연결을 해제해야 한다. 그렇지 않으면 현재 세션이 연결된 상태로 남아 있기 때문에 다음 연결할 때에는 최대 허용 세션을 초과할 수 있다. 따라서 데이터베이스 연결에 실패할 수도 있다.
conn.close();
다음은 DB를 연결할 때에 Properties로 설정할 수 있는 속성들이다.
속성명 | 타입 | 설명 |
---|---|---|
databaseName | String | 서버에 존재하는 특정 데이터베이스의 이름이다. |
dataSourceName | String | 데이터소스의 이름이다. |
description | String | 데이터소스에 대한 설명이다. |
networkProtocol | String | 서버와 통신하는 네트워크 프로토콜의 이름이다. (기본값: TCP) |
password | String | 서버 접속을 위한 패스워드이다. |
user | String | 서버 접속을 위한 사용자 이름이다. |
portNumber | int | 서버 리스너의 포트 번호이다. |
serverName | String | 데이터베이스의 이름이다. |
login_timeout | int | 소켓의 read timeout을 지정한다. 최소 소켓을 생성할 때부터 DB 연결 생성이 완료될 때까지 적용된다. DB 연결 생성이 완료된 뒤에는 read_timeout 속성이 적용된다. 만약 응답이 없이 지정된 시간이 지나면 timeout을 발생시키지만 설정값이 0일 경우에는 timeout을 발생시키지 않는다. (단위: millisecond, 기본값: 0) |
read_timeout | int | DB 연결 생성이 완료된 이후의 소켓의 read timeout을 지정한다. 만약 응답이 없이 지정된 시간이 지나면 timeout을 발생시키지만 설정값이 0일 경우에는 timeout을 발생시키지 않는다. (단위: millisecond, 기본값: 0) |
characterset | String | JDBC에서 인코딩, 디코딩 Character Set을 지정한다. (기본값: 서버의 문자 셋을 사용) |
program_name | String | 프로그램 이름이다. (기본값: JDBC Thin Client) |
includeSynonyms | String | DatabaseMetaData.getColumn()에서 synonyms 객체를 포함할지 여부를 지정한다. (기본값: false) |
mapDateToTimestamp | String | DATE 컬럼에 대해서 결과 타입으로 Timestamp(true)로 돌려줄지 Date(false)로 여부를 지정한다. (기본값: true) |
defaultNChar | String | PreparedStatement.setString() API로 설정한 문자열을 national charset 설정을 이용하여 서버로 전송하도록 강제한다. (기본값: false) |
self_keepalive | String | SELF KEEP ALIVE 기능을 활성화할지 여부를 지정한다. 활성화한 경우에는 self_keepidle, self_keepintvl, self_keepcnt 속성 설정에 따라 연결 대상에 대한 네트워크 접근에 문제가 없는지 확인한다. 전체 확인 과정에 실패하면, 해당 네트워크 연결을 강제로 종료시킨다. (기본값: false) |
self_keepidle | int | 접속 완료, DB 요청/응답 처리 등의 정상적인 네트워크 사용 이후, 다음 번의 정상 처리된 시간이 갱신되지 않을 때, 정상 상황으로 간주할 최대 시간을 지정한다. 지정된 시간이 지나면 네트워크 접근에 대한 확인 절차가 시작된다. self_keepalive 설정이 true인 경우에만 유효하다. (단위: 초, 기본값: 60) |
self_keepintvl | int | 매 번 확인할 때의 간격 및 한 번 확인할 때 최대 대기 시간을 지정한다. self_keepalive 설정이 true인 경우에만 유효하다. (단위: 초, 기본값: 10) |
self_keepcnt | int | 확인 절차를 총 몇 회까지 수행할 것인지를 지정한다. 지정된 횟수만큼 연속으로 실패하여야만 전체 확인과정이 실패한 것으로 처리된다. self_keepalive 설정이 true인 경우에만 유효하다. (기본값: 3) |
failover_retry_count | int | Failover 기능이 활성화되었을 때 연결 복원을 시도하는 최대 횟수를 지정한다. (기본값: 3) Failover 기능에 대한 자세한 설명은 “제9장 Failover와 Load balancing”의 내용을 참고한다. |
tbJDBC는 JDBC 타입을 지원할 뿐만 아니라 Interval 타입을 추가로 제공한다.
다음은 JDBC 타입과 tbJDBC 타입과의 대응 관계를 나타내는 표이다.
JDBC 타입(표준) | Java 타입(표준) | tbJDBC 타입 |
---|---|---|
java.sql.Types.CHAR | java.lang.String | java.lang.String |
java.sql.Types.VARCHAR | java.lang.String | java.lang.String |
java.sql.Types.LONGVARCHAR | java.lang.String | java.lang.String |
java.sql.Types.NUMERIC | java.math.BigDecimal | java.math.BigDecimal |
java.sql.Types.DECIMAL | java.math.BigDecimal | java.math.BigDecimal |
java.sql.Types.BIT | boolean | boolean |
java.sql.Types.TINYINT | byte | byte |
java.sql.Types.SMALLINT | short | short |
java.sql.Types.INTEGER | int | int |
java.sql.Types.BIGINT | long | long |
java.sql.Types.REAL | float | float |
java.sql.Types.FLOAT | double | double |
java.sql.Types.DOUBLE | double | double |
java.sql.Types.BINARY | byte[] | byte[] |
java.sql.Types.VARBINARY | byte[] | byte[] |
java.sql.Types.LONGVARBINARY | byte[] | byte[] |
java.sql.Types.DATE | java.sql.Date | java.sql.Date |
java.sql.Types.TIME | java.sql.Time | java.sql.Time |
java.sql.Types.TIMESTAMP | java.sql.Timestamp | java.sql.Timestamp |
java.sql.Types.BLOB | java.sql.Blob | com.tmax.tibero.jdbc.TbBlob |
java.sql.Types.CLOB | java.sql.Clob | com.tmax.tibero.jdbc.TbClob |
java.sql.Types.NCLOB | java.sql.NClob | com.tmax.tibero.jdbc.TbNClob |
java.sql.Types.NCHAR | java.sql.NCHAR | java.lang.String |
java.sql.Types.NVARCHAR | java.sql.NVARCHAR | java.lang.String |
java.sql.Types.SQLXML | java.sql.SQLXML | com.tmax.tibero.jdbc.TbSQLXML |
com.tmax.tibero.jdbc.data.DataType.VARRAY | java.sql.Array | com.tmax.tibero.jdbc.TbArray |
com.tmax.tibero.jdbc.data.DataType.Struct | java.sql.Struct | com.tmax.tibero.jdbc.TbUpStruct |
com.tmax.tibero.jdbc.data.DataType.CURSOR | - | com.tmax.tibero.jdbc.TbResultSet |
com.tmax.tibero.jdbc.data.DataType.ITV_DTS | - | com.tmax.tibero.jdbc.TbIntervalDts |
com.tmax.tibero.jdbc.data.DataType.ITV_YTM | - | com.tmax.tibero.jdbc.TbIntervalYtm |
com.tmax.tibero.jdbc.data.DataType.RowId | - | com.tmax.tibero.jdbc.TbRowId |
com.tmax.tibero.jdbc.data.DataType.TIMESTAMP_TZ | java.sql.Timestamp | com.tmax.tibero.jdbc.TbTimestampTZ |
com.tmax.tibero.jdbc.data.DataType.TIMESTAMP_LTZ | java.sql.Timestamp | java.sql.Timestamp |
Tibero에서 TIME 데이터 타입을 지원하지만 DATE 데이터 타입을 사용하기를 권장한다.
TIME 컬럼에 대해서 PreparedStatement.setTime() 메소드로 지정할 수 없다. PreapredStatement.setTime()은 DATE 데이터 타입의 컬럼에 대해서 지원하고 있다. TIME 컬럼에 대한 값을 지정하기 위해서는 문자열을 TIME 값으로 변환하는 TO_TIME 함수를 통해서 입력할 수 있다.
tbJDBC에서는 특정 데이터 타입에 대해 다음과 같은 Stream 기능을 제공한다.
컬럼에 대한 Stream 생성
CHAR, VARCHAR, RAW, NCHAR, NVARCHAR
LONG, LONG RAW
LOB
Stream 소멸
tbJDBC에서는 다음과 같이 4종류의 Stream 메소드를 제공한다.
각각의 메소드를 이용하면 해당 데이터 타입에서 허용하는 최대 크기까지 점진적으로 데이터를 읽을 수 있다. 메소드에서 반환되는 객체는 InputStream과 Reader이고, read() 메소드를 사용하면 데이터를 읽을 수 있다.
CHAR, VARCHAR, RAW 컬럼에 Stream 메소드를 사용하는 경우 전체 데이터를 한꺼번에 받아와서 Stream 객체로 생성한다. 반면, LONG이나 LONG RAW, LOB 컬럼에 대해서는 최초 일부의 데이터만 읽어온 후 Stream 객체를 생성한다. 단, 사용자가 추가로 요청하면 차례대로 데이터를 더 읽어온 후 Stream 객체를 생성하게 된다.
다음은 getBinaryStream()을 이용하여 데이터를 읽어오는 예이다.
ResultSet rs = stmt.executeQuery("SELECT name, image FROM backgrounds"); while (rs.next()) { InputStream imageIS = rs.getBinaryStream(2); try { FileOutputStream fos = new FileOutputStream(rs.getString(1)); int chunk = 0; while ((chunk = imageIS.read()) != -1) fos.write(chunk); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) fos.close(); } }
다음은 getBinaryStram() 대신 getBytes()를 이용하여 데이터를 읽어오는 예이다. getBytes()는 해당 컬럼에 저장된 모든 데이터를 한꺼번에 받아오기 때문에 그 크기만큼 메모리를 차지한다는 점을 주의해야 한다.
ResultSet rs = stmt.executeQuery("SELECT name, image FROM backgrounds"); while (rs.next()) { bytes[] images = rs.getBytes(2); try { FileOutputStream fos = new FileOutputStream(rs.getString(1)); fos.write(images); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) fos.close(); } }
tbJDBC에서는 PSM을 사용할 수 있다.
다음과 같이 SQL-92의 ESCAPE 문법과 PSM 문법을 제공한다.
// SQL92 ESCAPE 문법 CallableStatement cstmt = conn.prepareCall("{call proc(?)}"); CallableStatement cstmt = conn.prepareCall("{? = call func(?)}"); // PSM 문법 CallableStatement cstmt = conn.prepareCall("begin proc(?); end;"); CallableStatement cstmt = conn.prepareCall("begin ? := func(?); end;");
다음은 PSM 문법을 사용하여 내장 함수를 호출하는 예이다.
create or replace function concat(x varchar2, y varchar2)
return varchar2 is
begin
return x || y;
end;
CallableStatement cstmt = conn.prepareCall("begin ? := concat(?, ?); end;"); cstmt.regiterOutParameter(1, Types.VARCHAR); cstmt.setString(2, "Tmax"); cstmt.setString(3, "Tibero"); cstmt.executeUpdate(); String result = cstmt.getString(1);
tbJDBC에서는 예외 상황을 처리하기 위해 java.sql.SQLException 클래스 객체를 정의할 수 있다. 예외 상황은 tbJDBC 내부에서 발생할 수도 있고, 데이터베이스 내부에서 발생할 수도 있다. 예외 상황의 내용은 에러 코드와 에러가 발생한 위치 등의 정보가 포함되어 있다.
예외 상황에 대한 정보를 얻기 위해 다음과 같은 메소드가 제공된다.
메소드 | 설명 |
---|---|
getMessage() | 에러가 발생한 원인이다. |
getSQLState() | SQL 상태 정보이다. |
getErrorCode() | 에러 코드이다. |
printStackTrace() | 에러가 발생한 스택의 트레이스 정보(Stack trace)이다. |
다음은 위의 메소드를 사용하여 예외 상황을 처리하는 예이다.
try { stmt.execute("drop table not_exist_table"); } catch (SQLException e) { System.out.println("ERROR[" + e.getErrorCode() + "]" + e.getMessage()); e.printStackTrace(); }
실행하면 다음과 같은 정보가 화면에 출력된다.
ERROR[-7071] Schema object'NOT_EXIST_TABLE' was not found or is invalid. java.sql.SQLException: Schema object 'NOT_EXIST_TABLE' was not found or is invalid. at com.tmax.tibero.jdbc.msg.common.TbMsgError.readErrorStackInfo(TbMsgError.java :108) at com.tmax.tibero.jdbc.msg.TbMsgEreply.deserialize(TbMsgEreply.java:61) at com.tmax.tibero.jdbc.comm.TbStream.readMsgBody(TbStream.java:327) at com.tmax.tibero.jdbc.comm.TbCommType4.executeDirect(TbCommType4.java:460) at com.tmax.tibero.jdbc.TbStatement.executeInternal(TbStatement.java:1051) at com.tmax.tibero.jdbc.TbStatement.execute(TbStatement.java:560) at TestSimple.main(TestSimple.java:102)