h2.JDBC

11g부터 오라클 데이터 베이스는 java 개발 시 활용 가능한 여러 새로운 기능들을 도입

이 장에서 소개할 내용들을 간단히 정리해 보면 다음과 같다.

  • JDBC를 통한 Database Change Notification 사용
  • JDBC의 다양한 데이터타입 지원
    • ROWID
    • LOB
    • ANYTYPE
    • ANYDATA
  • 세분화된 Exception 지원
  • JDBC를 통한 데이터베이스 Startup/Shutdown

Database Change Notification(DCN)
특별한 애플리케이션 모델 - 데이터베이스 서버의 데이터를 미들-티어의 데이터 캐쉬에 저장해두고, 같은 쿼리가
재실행되면 저장된 데이터를 바로 이용하여, 데이터베이스 서버로의 불필요한 round trip을 예방하는 방식을 이용한다.
DCN : 데이터베이스 단의 데이터가 변경된 경우, 데이터 캐쉬에 저장된 데이터가 invalidateion 되었다는 사실을 미들-티어에 전달하는 기능
(특정 쿼리를 등록해두고, 그 쿼리와 관련된 데이터베이스 객체가 변경되면 서버가 invalidation notirication을 통보해주도록 설정하는 기능) 10gR2부터 지원

11g부터는 오라클 데이터베이스의 DCN기능을 오라클 JDBC드라이버를 통해서 클라이언트 측에서 설정하고 이용
JDBC드라이버를 통해 invalidation 이벤트를 받으면 미들-티어의 데이터 캐쉬를 업데이트해서, 항상 데이터 캐쉬를 최신의 상태로 유지하는 멀티-티어 시스템을 구축할 수 있다.

DCN을 이용해서,SQL쿼리를 등록하면 다음의 경우에 invalidation notification을 받도록 설정할 수 있다.

  • 쿼리와 관련된 객체를 변경시키는 DML/DDL이 commit되었을 때
  • 쿼리의 resultset을 변경시키는 DML/DDL이 commit되었을 때

10g : DCN은 쿼리를 등록하면 SQL문장 자체가 등록되는 것이 아니라, 문장에 포함되는 테이블이 등록되어, 쿼리 결과에
포함되지 않는 다른 row들이 변경되는 경우에도 Notification을 발생시킴

11g : DCN_QUERY_CHANGE_NOTIFICATION 옵션을 설정하면 등록한 쿼리의 Resultset이변경되는 경우에만 Notification을 발생시키도록 좀 더 섬세한 설정이 가능해짐

+기본 용어 및 기초 개념+

1.Registration 생성

JDBC 애플리케이션에서 DCN을 사용하기 위해서는 먼저 Registration을 생성해야 함
JDBC Connection이 맺어져 있어야만 Registration을 생성할 수 있지만, Connection에 Registration이 종속되는 것은 아니므로,
Registration 생성 후에 Connection이 Close되어도 Tegistration은 그대로 남아있다.

Oracle.jdbc.OracleConnection 인터페이스의 registerDatabaseChangeNotification()메소드를 사용하면 JDBC
타입의 Resgistranton을 생성할 수 있다.

서버에서 JDBC드라이버를 사용해서 Registration을 생성하면, JDBC드라이버는 다음 과정을 수행한다.
1) 서버로부터 오는 Notification을 listen하는 쓰레드 생성
2) Notification이 오면 이를 java이벤트로 전환
3) 생성된 Registration에 등록된 모든 리스너에 이벤트 전송

registerDatabaseCahngeNotification 메소드의 options 파라미터로 java.util.Properties 객체를 넘기면
Register에 다음과 같은 옵션들은 설정할 수 있다. Property는 oracle.jdbc.OracleConnection 클래스의 public
static String의 상수 타입만 가능함을 주의한다.

Registration을 생성하는 방법은 다음과 같다.

...
OracleConnection oraConn = (OracleConnection) conn;
java.util.Properties dcnOptions = new java.util.Properties();
dcnOptions.setProperty(oracle.jdbc.OracleConnection.NTF_LOCAL_TCP_PORT, "7000");
// Create a new registration
DatabaseChangeRegistration registration = oraConn.registerDatabaseChangeNotification (dcnOptions);
...

2. Database Change Event 통보

서버로부터 데이터베이스의 데이터 변경 notification을 받으면, 이를 이용해서 의도한 작업을 수행할
리스너를 registration에 등록한다.

서버는 데이터베이스의 데이터가 변경되면 이를 JDBC 드라이버에게 알림 -> JDBC 드라이버는 JAVA 이벤트를 생성 -> 이벤트를 통보할 registration을 찾아서 이 registration에 등록된 리스너에게 이벤트를 보냄

전송되는 이벤트에는 변경된 객체의 ID 및 변경을 일으킨 작업의 타입 등의 정보가 들어있다.
registration의 옵션을 통해서 이벤트에 좀 더 상세 정보가 포함되도록 설정할 수 있다. 리스너에는 이 이벤트
정보를 이용해서 미들-티어의 데이터 캐쉬와 관련된 작업을 하도록 코딩하면 된다.

Note: 리스너 코드에는 JDBC notification 메커니즘을 지연시킬 가능성이 있는 긴 시간을 소모하는 코드가
포함되지 않도록 주의한다. 예를 들어, 데이터베이스를 다시 쿼리해서 데이터 캐쉬를 업데이트하게 하고
싶다면 새로운 쓰레드를 생성해서 그 안에서 처리되도록 코딩한다.

addListener() 메소드를 통해 리스너를 Registration에 등록한다. 등록하는 방법은 다음과 같다.

...
//Attach the listener to the registration.
DCNListener list = new DCNListener();
registration.addListener(list);

3. Registration에 쿼리 등록

생성된 Registration에 notification을 받고자 하는 SQL문장을 등록한다.
쿼리를 등록하는 방법은 다음과 같다.

...
Statement stmt = oraConn.createStatement();
// associating the query with the registration
((OracleStatement)stmt).setDatabaseChangeRegistration(registration);
// any query that will be executed with the 'stmt' object will be associated with
// the registration 'registration' until 'stmt' is closed or
// '((OracleStatement)stmt).setDatabaseChangeRegistration(null);' is executed.

위의 예는 'stmt' Statement 객체를 'registration'이라는 Registration에 등록하는 예제이다. 이렇게 하면 'stmt'
객체 를 통해 실행되는 모든 쿼리가 'registration'에 등록되어 Notification을 받게 된다. 'stmt'가 close되거나
'((OracleStatement)stmt).setDatabaseChangeRegistration(null);'을 통해서 stmt를 명시적으로 un-register할
때까지 실행될 때까지 이 상태가 유지된다.

4. Registration 제거

Registration을 un-register하면 서버에서 Registration을 삭제하고 드라이버에서 할당되었던 리소스들을
해제한다. Registration 을 un-register할 때는 Registration 생성 시 사용했던 것과 다른 Connection을 사용해도
상관없다.
제거하는 방법은 다음과 같다.

...
//Close registration
OracleConnection conn3 = connect();
conn3.unregisterDatabaseChangeNotification(registration);
conn3.close();

다양한 데이터타입 지원
오라클 데이터베이스 10g는 JDBC 3.0을 지원했지만, 11g Release1부터 JDBC 4.0에 추가된 모든 기능들을
지원하면서 JDBC를 통해 사용 가능한 데이터 타입들이 다양해졌다. 또한 오라클 JDBC는 표준 JDBC가
지원하지 않는 오라클 데이터 타입들에 접근할 수 있도록 다양한 인터페이스들을 제공한다..
다음은 11g에서부터 새롭게 지원되는 데이터 타입들이다.

  • ROWID
  • LOB
  • ANYTYPE
  • ANYDATA

데이터 타입 별 특징과 사용방법

+ROWID+
SQL ROWID는 데이터베이스 테이블의 특정 행의 주소를 저장하는 built-in 데이터 타입이다. JDBC
4.0에서부터 java.sql.RowId 인터페이스를 도입하면서부터 Java에서 SQL ROWID 타입이 지원되기 시작했다.
예를 들어, 다음과 같이하면 java에서 ResultSet rs의 첫 행의 RowId를 읽어올 수 있다.

java.sql.RowId rowed_1 = rs.getRowId(1);

읽어 들인 RowId는 다음에 그 행을 다시 참고하고자 할 때 식별자로 사용할 수 있다. 예를 들면 다음과 같이
PreparedStatement의 파라미터로 읽어온 RowId를 사용할 수 있다.

Connection conn = ds.getConnection(user, passwd);
PreparedStatement pstmt = conn.prepareStatement ("UPDATE emp SET ename = ? WHERE rowid = ?");
ps.setRowId(1,"john");
ps.setRowId(2,rowed_1);

+LOB+
JDBC 4.0부터는 Connection 인터페이스에서 Blob, Clob, NClob 객체를 생성할 수 있는 createBlob, createClob,
createNClob 메소드를 지원한다.

다음과 같이 Connection 인터페이스에서 Blob 객체를 생성할 수 있다.

Connection conn = DriverManager.getConnection(url,props);
Blob aBlob = conn.createBlob();

위와 같이 생성된 aBlob 객체는 아무 데이터도 포함하지 않은 빈 상태이다. LOB객체에 데이터를 추가하려면
다음과 같이 setXXX 메소드를 사용한다.

int numWritten = aBlob.setBytes(1, val);

반대로 ResultSet에서 LOB 값을 읽어 들이고자 한다면 getXXX 메소드를 사용하면 된다. 다음은 ResultSet rs의
첫 행에서 BLOB 데이터를 읽어오는 예이다.

Blob aBlob = rs.getBlob(1);

LOB 객체에서 데이터의 일부만 읽어올 수도 있다. 다음은 BLOB 객체를 200 byte 위치에서부터 100Byte만큼
읽어 들이는 예이다.

InputeStream is = aBlob.getBinaryStream(200,100);

+ANYTYPE+
오라클 11g부터는 SYS.ANYTYPE 오라클 데이터 타입에 접근할 수 있는 Java 인터페이스를 제공한다.
ANYTYPE은 오라클 데이터베이스 9i Release2에서부터 도입된 SQL 데이터 타입이다.

SYS.ANYTYPE 타입은 인스턴스에 모든 SQL타입에 대한 타입 description이 저장하는 데이터 타입이다.
SYS.ANYTYPE에는 persistent/transient, named/unnamed, object타입/collection타입을 막론하고 모든 SQL
데이터 타입에 대한 타입 description이 저장될 수 있다.

oracle.sql.TypeDescriptor java 클래스를 통해서 Java 애플리케이션에서 SYS.ANYTYPE 데이터 타입에 접근할
수 있으며, getObject() 메소드를 사용해서 데이터베이스에서 ANYTYPE 인스턴스를 읽어 들일 수 있다.
getObject 메소드는 TypeDescriptor 인스턴스를 리턴한다.

Note: persistent 데이터 타입이란 CREATE TYPE 문장을 통해 생성되어 데이터베이스에 영구적으로
저장되는 데이터 타입을 의미한다. unnamed transient 타입은 프로그램 상에서 런타임에 일시적으로
생성되어 사용되며 데이터베이스에 저장되지 않는 데이터 타입을 의미한다.

다음은 데이터베이스에서 ANYTYPE의 인스턴스를 읽어 들이는 방법이다.

ResultSet rs = stmt.executeQuery("select anytype_column from my_tab");
TypeDescriptor td = (TypeDescriptor)rs.getObject(1);
short typeCode = td.getInternalTypeCode();
if(typeCode == TypeDescriptor.TYPECODE_OBJECT)
{
if(td.isTransientType()) // check if it's a transient type
{AttributeDescriptor[] attributes =
((StructDescriptor)td).getAttributesDescriptor();
for(int i=0; i<attributes.length; i++)
System.out.println(attributes[i].getAttributeName());
}
else
{System.out.println(td.getTypeName());}
}

+ANYDATA+
ANYDATA 역시 ANYTYPE과 함께 오라클 데이터베이스 9i RELEASE 2에서 도입된 SQL 데이터 타입이다.
11g JDBC부터 ANYDATA에 접근할 수 있는 인터페이스가 제공된다.

ANYDATA는 하나의 테이블 칼럼에 다양한 데이터 타입의 데이터를 저장하고자 할 때 칼럼 타입으로 사용할
수 있는 데이터 타입이다. SQL built-in 타입 및 사용자 정의 데이터 타입, transient 타입까지 모든 타입의
인스턴스를 값으로 가질 수 있다.

SYS.ANYDATA SQL타입은 java 클래스 oracle.sql.ANYDATA와 매핑된다. oracle.sql.Datum 인스턴스에서
convertDatum() 메소드를 호출하면 oracle.sql.ANYDATA 클래스의 인스턴스가 리턴된다.

다음과 같이 SYS.ANYDATA 타입 칼럼을 포함하는 'anydata_tab' 테이블을 가정해보자.
CREATE TABLE anydata_tab ( id NUMBER, data SYS.ANYDATA )
그리고 다음과 같이 'employee'라는 SQL 객체 타입이 정의되어 있다고 가정하자.
CREATE OR REPLACE TYPE employee AS OBJECT ( empno NUMBER, ename VARCHAR2(10) )
위와 같은 경우, 'employee'데이터 타입의 인스턴스를 생성해서 'anydata_tab' 테이블에 insert하는 과정은
다음과 같다.

PreparedStatement pstmt = conn.prepareStatement("insert into anydata_tab values (?,?)");
StructDescriptor sd = StructDescriptor.createDescriptor("EMPLOYEE",(OracleConnection)conn);
Object[] objattr = new Object[2];
objattr[0] = new BigDecimal(1120);
objattr[1] = new String("Papageno");
STRUCT myEmployeeStr = new STRUCT(sd,conn,objattr);
ANYDATA anyda = ANYDATA.convertDatum(myEmployeeStr);
pstmt.setInt(1,123);
pstmt.setObject(2,anyda);
pstmt.executeUpdate();

세분화된 Exception 지원

이전 릴리즈까지는 SQLException만을 지원했던데 비해, 11g는 SQLException의 다양한 하위클래스들을
지원해준다. 다양해진 exception 구조를 통해 애플리케이션에서의 좀 더 나은 예외상황 핸들링이 가능해졌다.

Exception은 크게 TransientException과 PermanentException 두 종류로 나뉘어진다. Permanent는 시스템의
성능과 무관하게 발생하는 에러들로 시스템이 정상 동작하고 있을 때에도 발생한다. Transient는 시스템
failure의 결과 발생하는 에러들이므로 항상 발생하는 것은 아니다.

11g에서 지원하는 Exception의 구조는 다음과 같다.


그림 2 11g Exception Hierarchy

JDBC를 통한 데이터베이스 STARTUP/SHUTDOWN

11g에서부터 oracle.jdbc.OracleConnection 클래스에서 startup() 및 shutdown()이라는 새로운 JDBC 메소드를
제공한다. 이 두 메소드는 SQL*Plus에서처럼 데이터베이스를 애플리케이션에서 직접 start up하고 shut
down할 수 있는 인터페이스이다.
startup()과 shutdown() 메소드를 사용하기 위해서는 커넥션을 맺을 때 반드시 preliminary 모드로 SYSDBA
혹은 SYSOPER로 커넥션을 맺어야 한다. Preliminary 모드는 데이터베이스 인스턴스가 구동 중이 아닐 때 한
명의 유저만을 허용하여 데이터베이스 start up 시키는 작업에만 허용하는 모드이다. 커넥션 생성 시 다음과
같이 prelim_auth 프로퍼티를 true로 설정하면 된다.

prop.setProperty("user","sys");
prop.setProperty("password","manager");
prop.setProperty("internal_logon","sysdba");
prop.setProperty("prelim_auth","true");
ds.setConnectionProperties(prop);

이렇게 커넥션을 맺은 후에 startup() 메소드를 호출해서 데이터베이스를 start up 시킨다. startup() 메소드
호출 시 다음과 같은 데이터베이스 start up 옵션 설정 파라미터를 명시할 수 있다.

  • FORCE : 인스턴스 start up 전에 데이터베이스를 abort 모드로 shut down
  • RESTRICT : 데이터베이스를 start up 한 후에, CREATE SESSION와 RESTRICTED 권한을 가지고 있는
    유저(일반적으로는 DBA)에게만 데이터베이스 접근 허용
  • NO_RESTRICTION : RESTRICTION없이 데이터베이스 start up
    Startup() 메소드 호출 방법은 다음과 같다.
OracleConnection conn = (OracleConnection)ds.getConnection();
conn.startup(OracleConnection.DatabaseStartupMode.NO_RESTRICTION);

Startrup() 메소드는 데이터베이스 인스턴스를 start up시키기만 할 뿐 mount하거나 open하는 것은
아니다. 데이터베이스를 mount/open하려면 다음과 같이 한다.

Statements stmt = conn.createStatement();
stmt.executeUpdate("ALTER DATABASE MOUNT");
stmt.executeUpdate("ALTER DATABASE OPEN");

Shutdown() 메소드 호출로 데이터베이스를 shutdown 할 때 설정할 수 있는 옵션은 다음과 같다.

  • ABORT
  • CONNECT
  • FINAL
  • IMMEDIATE
  • TRANSACTIONAL
conn.shutdown(OracleConnection.DatabaseShutdownMode.IMMEDIATE);
Statement stmt1 = conn.createStatement();
stmt1.excuteUpdate("ALTER DATABASE CLOSE NORMAL");
stmt1.excuteUpdate("ALTER DATABASE CLOSE DISMOUNT");

실습예제

Quick Implementation of Database Change Notification

DCN 기능을 이용해서 데이터베이스에 registration을 생성하고 리스너를 등록한 후, Scott 유저로
insert문장을 실행하면 리스너가 실행한 작업에 대한 DCN 이벤트를 받아서 화면에 출력해주는
애플리케이션을 구현해본다.

1.) 본 예제에서는 Scott 유저로 데이터베이스에 접근해서 작업하므로, 먼저 Scott 유저에게 다음과 같이 change
notification 권한을 부여한다.

grant change notification to scott;

2.) 다음 java 소스를 DBChangeNotification.java라는 파일에 저장한다.


import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleStatement;
import oracle.jdbc.dcn.DatabaseChangeEvent;
import oracle.jdbc.dcn.DatabaseChangeListener;
import oracle.jdbc.dcn.DatabaseChangeRegistration;
public class DBChangeNotification
{
static final String USERNAME= "scott";
static final String PASSWORD= "tiger";
static String URL;
public static void main(String[] argv)
{
if(argv.length < 1)
{
System.out.println("Error: You need to provide the URL in the first argument.");
System.out.println(" For example: > java -classpath .:ojdbc5.jar DBChangeNotification
\"jdbc:oracle:thin:
@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=yourhost.yourdomain.com)(PORT=1521))(CONNECT_DATA=
(SERVICE_NAME=yourservicename)))\"");
System.exit(1);
}
URL = argv[0];
DBChangeNotification demo = new DBChangeNotification();
try
{
demo.run();
}
catch(SQLException mainSQLException )
{
mainSQLException.printStackTrace();
}
}
void run() throws SQLException
{
OracleConnection conn = connect();
0
// (1)Registration을 생성

Properties prop = new Properties();
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS,"true");
DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop);
try
{
// (2)addListener() 메소드를 통해 리스너를 Registration에 등록한다
DCNDemoListener list = new DCNDemoListener(this);
dcr.addListener(list);
// (3)생성된 Registration에 notification을 받고자 하는 SQL문장을 등록
Statement stmt = conn.createStatement();
// associate the statement with the registration:
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs = stmt.executeQuery("select * from dept where deptno='45'");
while (rs.next())
{}
String[] tableNames = dcr.getTables();
for(int i=0;i<tableNames.length;i++)
System.out.println(tableNames[i]+" is part of the registration.");
rs.close();
stmt.close();
}
catch(SQLException ex)
{
// if an exception occurs, we need to close the registration in order
// to interrupt the thread otherwise it will be hanging around.
if(conn != null)
conn.unregisterDatabaseChangeNotification(dcr);
throw ex;
}
finally
{
try
{
// Note that we close the connection!
conn.close();
}
catch(Exception innerex){ innerex.printStackTrace(); }
}
synchronized( this )
{
// The following code modifies the dept table and commits:
try
{
OracleConnection conn2 = connect();
conn2.setAutoCommit(false);
Statement stmt2 = conn2.createStatement();
stmt2.executeUpdate("insert into dept (deptno,dname) values ('45','cool
dept')",Statement.RETURN_GENERATED_KEYS);
ResultSet autoGeneratedKey = stmt2.getGeneratedKeys();
if(autoGeneratedKey.next())
System.out.println("inserted one row with ROWID="+autoGeneratedKey.getString(1));
stmt2.executeUpdate("insert into dept (deptno,dname) values ('50','fun
dept')",Statement.RETURN_GENERATED_KEYS);
autoGeneratedKey = stmt2.getGeneratedKeys();
if(autoGeneratedKey.next())
System.out.println("inserted one row with ROWID="+autoGeneratedKey.getString(1));
stmt2.close();
conn2.commit();
conn2.close();
}
catch(SQLException ex) { ex.printStackTrace(); }
// wait until we get the event
try{ this.wait();} catch( InterruptedException ie ) {}
}
// (4)Registration을 un-register하면 서버에서 Registration을 삭제하고 드라이버에서 할당되었던 리소스들을 해제한다
OracleConnection conn3 = connect();
conn3.unregisterDatabaseChangeNotification(dcr);
conn3.close();
}
/**
* Creates a connection the database.
*/
OracleConnection connect() throws SQLException
{
OracleDriver dr = new OracleDriver();
Properties prop = new Properties();
prop.setProperty("user",DBChangeNotification.USERNAME);
prop.setProperty("password",DBChangeNotification.PASSWORD);
return (OracleConnection)dr.connect(DBChangeNotification.URL,prop);
}
}
/**
* DCN listener: it prints out the event details in stdout.
*/
class DCNDemoListener implements DatabaseChangeListener
{
DBChangeNotification demo;
DCNDemoListener(DBChangeNotification dem)
{
demo = dem;
}
public void onDatabaseChangeNotification(DatabaseChangeEvent e)
{
Thread t = Thread.currentThread();
System.out.println("DCNDemoListener: got an event ("+this+" running on thread "+t+")");
System.out.println(e.toString());
synchronized( demo ){ demo.notify();}
}
}

3.) 다음과 같이 위 java 소스를 컴파일한다.


UNIX
----
javac -cp .:<directory>/ojdbc5.jar DBChangeNotification.java


Windows
-------
javac -cp .:<directory>/ojdbc5.jar DBChangeNotification.java
<directory>는 ojdbc5.jar 파일이 들어있는 디렉토리 위치로 바꾼다. 일반적으로는 $ORACLE_HOME/jdbc/lib 위치에
들어있다.

4.) 위에서 컴파일한 클래스를 아래와 같이 실행해보자. 실행시 인수로 커넥션 URL을 넣어준다.

UNIX
----
java -cp .:<directory>/ojdbc5.jar DBChangeNotification "jdbc:oracle:thin:@obe11g:1521:ORCL"
Windows
-------
java -cp .;<directory>/ojdbc5.jar DBChangeNotification "jdbc:oracle:thin:@obe11g:1521:ORCL"
<directory>는 ojdbc5.jar 파일이 들어있는 디렉토리 위치로 바꾼다. 일반적으로는 $ORACLE_HOME/jdbc/lib 위치에
들어있다.

5.) 아래와 같이 리스너가 이벤트를 받아서 아래와 같이 이벤트 내용을 화면에 출력하는 것을 확인할 수 있다.

COTT.DEPT is part of the registration.
inserted one row with ROWID=AAAQ+yAAEAAAAANAAA
inserted one row with ROWID=AAAQ+yAAEAAAAANAAB
DCNDemoListener: got an event (DCNDemoListener@170888e running on thread Thread[Thread-2,5,main])
Connection information : local=obe11g.us.oracle.com/192.168.203.11:47632,
remote=obe11g.us.oracle.com/192.168.203.11:36251
Registration ID : 1
Notification version : 1
Event type : OBJCHANGE
Database name : orcl
Table Change Description (length=1)
operation=[INSERT], tableName=SCOTT.DEPT, objectNumber=69554
Row Change Description (length=2):
ROW: operation=INSERT, ROWID=AAAQ+yAAEAAAAANAAA
ROW: operation=INSERT, ROWID=AAAQ+yAAEAAAAANAAB

Using JDBC method for Database Startup/Shutdown
데이터베이스에 접근해서 startup(), shutdown() 메소드를 이용해서 데이터베이스를 셧다운/스타트업하는
애플리케이션을 작성해본다.

1.) Starup/shutdown 메소드를 사용하기 위해서는 SYSDBA 혹은 SYSOPER 권한으로 데이터베이스에
접속해야 한다. 오라클 JDBC Thin 드라이버를 통해서 SYSDBA나 SYSOPER로 접속하려면 서버가 패스워드
파일을 사용하도록 설정해야 한다.
커맨드라인에서 다음 명령어를 실행한다.

orapwd file=$ORACLE_HOME/dbs/orapw password=yourpass entries=5

2.) Starup/shutdown 메소드를 사용할 유저는 SYSDBA권한을 가지고 있어야 하므로, SQL*Plus에서 다음을
실행한다.

GRANT SYSDBA TO system;
ALTER USER system IDENTIFIED BY manager;

3.) Init<SID>.ora 파일을 열어서 다음 행을 추가한다.

REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE

4.) 이제 startup/shutdown 메소드를 사용하기위한 설정을 마쳤다. 데이테이스 인스턴스를 재 시작해서
설정내용을 적용한다.

5.) $ORACLE_HOME/network/admin/listener.ora파일에 다음과 같이 JDBC를 붙으려는 서비스가 static하게
등록되어 있는지 확인하고, 등록되어 있지 않다면 아래 내용을 listener.ora 파일에 추가한다.
데이터베이스를 startup 시키려면 인스턴스가 떠 있지 않은 상태에서 그 서비스에 접속해야 하기 때문에
반드시 JDBC를 통해서 접속할 서비스가 리스너에 static하게 등록되어 있어야 한다.

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = orcl.us.oracle.com)
(SID_NAME = orcl)
(ORACLE_HOME = /u01/app/oracle/product/11.1.0/db_1)
)
)

6.) 다음은 데이터베이스 startup시키는 java 프로그램이다. DBStartup.java라는 이름으로 다음을 저장한다.

import java.sql.Statement;
import java.util.Properties;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.pool.OracleDataSource;
/**
* In order to be able to logon as sysdba, you need to create a password file for user "sys":
* orapwd file=$T_DBS/orapw password=manager entries=300
* and add the following setting in init.ora:
* REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE
* then restart the database.
*/
public class DBStartup
{
static final String DB_URL =
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=obe11g)(PORT=1521))) "
+"(CONNECT_DATA=(SERVICE_NAME=orcl.us.oracle.com)))";
public static void main(String[] argv)
{
try{
OracleDataSource ds = new OracleDataSource();
Properties prop = new Properties();
prop.setProperty("user","sys");
prop.setProperty("password","oracle");
prop.setProperty("internal_logon","sysdba");
prop.setProperty("prelim_auth","true");
ds.setConnectionProperties(prop);
ds.setURL(DB_URL);
OracleConnection conn = (OracleConnection)ds.getConnection();
Database 11g New Features Cookbook
CONFIDENTIAL 29 Oracle Korea
System.out.println(" *** OracleConnection conn = (OracleConnection)ds.getConnection() ****");
conn.startup(OracleConnection.DatabaseStartupMode.NO_RESTRICTION);
System.out.println(" *** conn.startup(OracleConnection.DBSTARTUP_NO_RESTRICTION) ****");
conn.close();
OracleDataSource ds1 = new OracleDataSource();
Properties prop1 = new Properties();
prop1.setProperty("user","sys");
prop1.setProperty("password","oracle");
prop1.setProperty("internal_logon","sysdba");
ds1.setConnectionProperties(prop1);
ds1.setURL(DB_URL);
OracleConnection conn1 = (OracleConnection)ds1.getConnection();
System.out.println(" *** OracleConnection conn1 = (OracleConnection)ds1.getConnection() ****");
Statement stmt = conn1.createStatement();
stmt.executeUpdate("ALTER DATABASE MOUNT");
System.out.println(" *** stmt.executeUpdate(\"ALTER DATABASE MOUNT\"); ****");
stmt.executeUpdate("ALTER DATABASE OPEN");
System.out.println(" *** stmt.executeUpdate(\"ALTER DATABASE OPEN\"); ****");
stmt.close();
conn1.close();
System.out.println(" ******** The End Startup **********");
}catch (Exception e) {
e.printStackTrace();
}
}
}

7.) 다음은 데이터베이스 shutdown시키는 java 프로그램이다. DBShutdown.java라는 이름으로 다음을
저장한다.

import java.sql.Statement;
import java.util.Properties;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.pool.OracleDataSource;
public class DBShutdown
{
static final String DB_URL =
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=obe11g)(PORT=1521))) "
+"(CONNECT_DATA=(SERVICE_NAME=orcl.us.oracle.com)))";
public static void main(String[] argv)
{
try{
OracleDataSource ds = new OracleDataSource();
Properties prop = new Properties();
prop.setProperty("user","sys");
prop.setProperty("password","oracle");
prop.setProperty("internal_logon","sysdba");
ds.setConnectionProperties(prop);
ds.setURL(DB_URL);
OracleConnection conn = (OracleConnection)ds.getConnection();
System.out.println(" *** OracleConnection conn = (OracleConnection)ds.getConnection() ****");
conn.shutdown(OracleConnection.DatabaseShutdownMode.IMMEDIATE);
System.out.println(" *** conn.shutdown(OracleConnection.DBSHUTDOWN_IMMEDIATE) *****");
Statement stmt = conn.createStatement();
stmt.executeUpdate("ALTER DATABASE CLOSE NORMAL");
System.out.println(" *** stmt.executeUpdate(\"ALTER DATABASE CLOSE NORMAL\") ****");
stmt.executeUpdate("ALTER DATABASE DISMOUNT");
System.out.println(" *** stmt.executeUpdate(\"ALTER DATABASE DISMOUNT\") ****");
stmt.close();
conn.shutdown(OracleConnection.DatabaseShutdownMode.FINAL);
System.out.println(" *** conn.shutdown(OracleConnection.DBSHUTDOWN_FINAL) ****");
conn.close();
System.out.println(" ******** The End ShutDown **********");
}catch(Exception e) {
e.printStackTrace();
}
}
}



8.) 다음의 명령어로 위에서 작성한 애플리케이션을 컴파일한다.


UNIX
----
javac -cp .:<directory>/ojdbc5.jar DBStartup.java
javac -cp .:<directory>/ojdbc5.jar DBShutdown.java
Windows
-------
javac -cp .;<directory>/ojdbc5.jar DBStartup.java
javac -cp .;<directory>/ojdbc5.jar DBShutdown.java
<directory>는 ojdbc5.jar 파일이 들어있는 디렉토리 위치로 바꾼다. 일반적으로는 $ORACLE_HOME/jdbc/lib 위치에
들어있다.


9.) 컴파일한 클래스를 실행한다.

UNIX
----
java -cp .:<directory>/ojdbc5.jar DBStartup
java -cp .:<directory>/ojdbc5.jar DBShutdown
Windows
-------
java -cp .;<directory>/ojdbc5.jar DBStartup
java -cp .;<directory>/ojdbc5.jar DBShutdown
<directory>는 ojdbc5.jar 파일이 들어있는 디렉토리 위치로 바꾼다. 일반적으로는 $ORACLE_HOME/jdbc/lib 위치에
들어있다.


10.) 다음과 같이 데이터베이스가 Starup/Shutdown되는 것을 확인할 수 있다.

$ java DBStartup
*** OracleConnection conn = (OracleConnection)ds.getConnection() ****
*** conn.startup(OracleConnection.DBSTARTUP_NO_RESTRICTION) ****
*** OracleConnection conn1 = (OracleConnection)ds1.getConnection() ****
*** stmt.executeUpdate("ALTER DATABASE MOUNT"); ****
*** stmt.executeUpdate("ALTER DATABASE OPEN"); ****
******** The End Startup **********
$ java DBShutdown
*** OracleConnection conn = (OracleConnection)ds.getConnection() ****
*** conn.shutdown(OracleConnection.DBSHUTDOWN_IMMEDIATE) *****
*** stmt.executeUpdate("ALTER DATABASE CLOSE NORMAL") ****
*** stmt.executeUpdate("ALTER DATABASE DISMOUNT") ****
*** conn.shutdown(OracleConnection.DBSHUTDOWN_FINAL) ****
******** The End ShutDown **********

결론

11g부터 오라클 JDBC는 JDBC 4.0의 모든 기능을 지원하고, 좀 더 다양한 데이터베이스 특징들을
JDBC를 통해 사용할 수 있도록 기능이 추가되어 Java 애플리케이션에서 좀 더 향상된 데이터베이스
컨트롤이 가능해졌다.