Top > XT_PACKAGE > XT_UTL_FILE (バイナリファイル・アクセス)

PL/SQL からバイナリファイルを書き込む

UTL_FILE パッケージは Oracle でファイルの入出力を行なうためのパッケージである。 Oracle 10g になって拡張され、小規模なバイナリファイルを取り扱うことも可能となっている。

しかし、バッファサイズがの上限が 32KB - 1 であり、取り扱うことができるのは RAW 型である。 そのため 32KB 以上のバイナリコードを書き込むには複数のバイトコードを分割して複数回にわけて書き込む必要がある。

そこで、ここではバイナリはもちろん BLOB、CLOB まで扱える Java ストアド・プロシージャ( Java ストアド・パッケージ) を作成し、PL/SQL でラップすることで UTL_FILE の機能を補うユーザーパッケージを作成する。

機能

注意点 兼 メモ書き

  • Oracle 10g R2(Win) でのみ動作確認。(※)
  • ファイルアクセスのセキュリティは DBMS_JAVA パッケージによって管理される。
    (ファイル名は大文字小文字を区別するので正確に)
  • ファイルは常に上書きモードで書き込まれる。
  • 一時 LOB(キャッシュ有効) を使用している。
  • ファイルが大きくなると思いのほか遅い。(キャッシュ溢れ、一時表領域書き出し、再読み込み、ファイル書き出し)
    参考: LOB 型の格納方式

(※) FileChannel クラスが使えるのは Java 1.4 からのようなので Oralce 9i では他のクラスで書き換える必要がある。 Oracle 9i には Java 1.3.x / Oracle 10g には Java 1.4.x の OracleJVM(Aurora という名前が付いている) が搭載されているとマニュアルに書かれている。

PL/SQL ⇒ Java ストアド・パッケージ

対象ファイル名漢字コード
Java および PL/SQL パッケージファイルfilext_utl_file.sql.0.9Shift JIS
Oracle 9i 用(FileChannel未使用)filext_utl_file_FOS.sql.0.9Shift JIS

Java ストアド・パッケージ

クラス名: jp.rivus.io.UtilFileSurrogate

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED XT_UTL_FILE_PEER
AS
package jp.rivus.io;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import oracle.sql.BLOB;
class UtilFileSurrogate {
	public final static int DEBUG_LEVEL = 1;
	public final static int SUCCESS = 0;
	public final static int FAIL = 1;
	public final static int MULTI_WRITE_COUNT = 8;
	/**
	 * writeBlob to file.
	 * @return SUCCESS(0) if the LOB is written. / FAIL(1) errors
	 */
	public static int writeBlob(String pFilename, BLOB pBlob)
	throws Exception
	{
		int len = -1;
		int ret = FAIL; // (default)
		FileOutputStream fos = null;
		FileChannel channel = null;
		ByteBuffer channelBuffer = null;
		try{
			int chunk = MULTI_WRITE_COUNT * pBlob.getChunkSize();
			InputStream bis = pBlob.getBinaryStream();
			fos = new FileOutputStream(pFilename);
			channel = fos.getChannel();
			byte[] buffer = new byte[chunk];
			while((len = bis.read(buffer))!=-1){
				channelBuffer = ByteBuffer.wrap(buffer,0,len);
				channel.write(channelBuffer);
			}
			ret = SUCCESS;
		}
		catch (Exception e) {
			// hadleException(e);
			if (DEBUG_LEVEL > 0) e.printStackTrace();
		}
		finally {
			try {
				if(fos != null) fos.close();
			}
			catch (Exception e) {
				// nop
			}
		}
		return ret;
	}
}
/

Java ストアド・プロシージャをコンパイルさせるときには & や && が置換記号扱いされる。 ソースコードの投入前に以下のコマンドを実行しておく。(このソースには & が無いので関係ない)

-- 置換文字(&)を無効化する
SQL> SET DEFINE OFF

参考: DEFINE システム変数

PL/SQL から Java を使用するためのラッパ

XT_UTL_FILE パッケージ

CREATE OR REPLACE PACKAGE RIVUS.XT_UTL_FILE
IS
	--
	-- PUT_BLOB
	FUNCTION PUT_BLOB(P_FILE IN VARCHAR2,
	                  P_BLOB IN OUT NOCOPY BLOB)
	RETURN NUMBER;
	--
	-- PUT_CLOB
	FUNCTION PUT_CLOB(P_FILE IN VARCHAR2,
	                  P_CLOB IN OUT NOCOPY CLOB,
	                  P_CSNAME IN VARCHAR2:=NULL)
	RETURN NUMBER;
	--
	-- PUT_CHAR
	FUNCTION PUT_CHAR(P_FILE IN VARCHAR2,
	                  P_CHAR IN VARCHAR2,
	                  P_CSNAME IN VARCHAR2:=NULL)
	RETURN NUMBER;
	--
	-- PUT_RAW
	FUNCTION PUT_RAW(P_FILE IN VARCHAR2,
	                  P_RAW IN RAW)
	RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY RIVUS.XT_UTL_FILE
IS
-- Java Stored Package Peer
FUNCTION PUT_BLOB_PEER(P_FILE IN VARCHAR2,
                       P_BLOB IN BLOB)
RETURN NUMBER
AS LANGUAGE JAVA
NAME
'jp.rivus.io.UtilFileSurrogate.writeBlob(java.lang.String, oracle.sql.BLOB)
return java.lang.String';
--
-- PUT_BLOB 
FUNCTION PUT_BLOB(P_FILE IN VARCHAR2,
                  P_BLOB IN OUT NOCOPY BLOB)
RETURN NUMBER
IS
BEGIN
	-- DBMS_JAVA.SET_OUTPUT(2000);  -- printStackTrace -> DBMS_OUTPUT
	RETURN XT_UTL_FILE.PUT_BLOB_PEER(P_FILE, P_BLOB);
END;
--
-- PUT_CLOB
FUNCTION PUT_CLOB(P_FILE IN VARCHAR2,
                  P_CLOB IN OUT NOCOPY CLOB,
                  P_CSNAME IN VARCHAR2:=NULL)
RETURN NUMBER
IS
	vRet		NUMBER;
	vBlob		BLOB;
	vDstOffset	INTEGER := 1;				-- Dest Offset
	vSrcOffset	INTEGER := 1;				-- Source Offset
	vCSID		NUMBER;					-- Charset ID
	vLangContext	INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;	-- Lang Context
	vWarning	INTEGER := DBMS_LOB.NO_WARNING;		-- Warning
BEGIN
	vCSID := NVL(CASE WHEN P_CSNAME IS NOT NULL THEN NLS_CHARSET_ID(P_CSNAME)
		     ELSE DBMS_LOB.DEFAULT_CSID END, DBMS_LOB.DEFAULT_CSID);
	DBMS_LOB.CREATETEMPORARY(vBlob, TRUE, DBMS_LOB.CALL);	-- BLOB 初期化
	DBMS_LOB.CONVERTTOBLOB(vBlob, P_CLOB, DBMS_LOB.LOBMAXSIZE,
	                       vDstOffset, vSrcOffset, vCSID, vLangContext, vWarning);
	vRet := XT_UTL_FILE.PUT_BLOB(P_FILE, vBlob);
	DBMS_LOB.FREETEMPORARY(vBlob);
	RETURN vRet;
END;
--
-- PUT_CHAR
FUNCTION PUT_CHAR(P_FILE IN VARCHAR2,
                  P_CHAR IN VARCHAR2,
                  P_CSNAME IN VARCHAR2:=NULL)
RETURN NUMBER
IS
	vClob		CLOB;
BEGIN
	vClob := P_CHAR;
	RETURN XT_UTL_FILE.PUT_CLOB(P_FILE, vClob, P_CSNAME);
END;
--
-- PUT_RAW
FUNCTION PUT_RAW(P_FILE IN VARCHAR2,
                  P_RAW IN RAW)
RETURN NUMBER
IS
	vBlob		BLOB;
BEGIN
	vBlob := P_RAW;
	RETURN XT_UTL_FILE.PUT_BLOB(P_FILE, vBlob);
END;
 
END XT_UTL_FILE;
/

ファイルへのアクセス権の設定

RIVUS ユーザへの権限の付与と削除例

-- Grant (付与)
SQL> CALL DBMS_JAVA.GRANT_PERMISSION('RIVUS',
     'java.io.FilePermission', 'c:\hoge.dat', 'read,write');
-- Revoke (削除)
SQL> CALL DBMS_JAVA.REVOKE_PERMISSION('RIVUS',
     'java.io.FilePermission', 'c:\hoge.dat', 'read,write');
-- Grant UTL_FILE_DIR = * と同じレベル
SQL> CALL DBMS_JAVA.GRANT_PERMISSION('RIVUS',
     'java.io.FilePermission', '<<ALL FILES>>', 'read,write');

CREATE DIRECTORY や UTL_FILE_DIR 初期化パラメータのように再起動が必要になったり、動的 SQL を用いることなくプログラム内においても DIRECTORY オブジェクト管理に比べて低い権限でかつダイナミックに書き込み権限の作成、付与および削除が行ないやすいためセキュリティリスクが高くなる可能性がある。

バイナリデータの書き込み例

SQL*Plus で実行

  • コード変換後に書き込み(キャラクタセット:SJIS,EUC-JP などにて出力)

デフォルトのキャラクタセットはデータベースのキャラクタセットになる。

DECLARE
 vRet NUMBER;
BEGIN
 -- 常に上書きされるので最後の1行だけ残る。
 vRet := XT_UTL_FILE.PUT_CHAR('c:\hoge.txt', 'あいうえお');
 vRet := XT_UTL_FILE.PUT_CHAR('c:\hoge.txt', 'かきくけこ', 'JA16SJISTILDE');
 vRet := XT_UTL_FILE.PUT_CHAR('c:\hoge.txt', 'さしすせそ', 'JA16EUCTILDE');
 vRet := XT_UTL_FILE.PUT_CHAR('c:\hoge.txt', 'たちつてと', 'AL16UTF16');
 vRet := XT_UTL_FILE.PUT_CHAR('c:\hoge.txt', 'なにぬねの', 'AL32UTF8');
END;
/
  • RAW データの書き込み ASCII '012345'
DECLARE
 vRet NUMBER;
BEGIN
 vRet := XT_UTL_FILE.PUT_RAW('c:\hoge.txt', HEXTORAW('303132333435'));
 DBMS_OUTPUT.PUT_LINE(vRet);
END;
/
  • CLOB で 32KB 以上のデータ の書き込み
DECLARE
 vRet NUMBER;
 vClob CLOB;
BEGIN
 vClob := RPAD('X', 32*1024-1, 'X');
 vClob := vClob || RPAD('Y', 32*1024-1, 'Y');
 vRet := XT_UTL_FILE.PUT_CLOB('c:\hoge.txt', vClob, 'AL16UTF16');
 DBMS_OUTPUT.PUT_LINE(vRet);
END;
/


ファイルI/O 関連事項

日本オラクル
■ 日本オラクル 株式会社
■ オラクルマスター資格 (オラクルマスターとは
■ Oracle のライセンスがわからない…
Oracle Direct (ネットで聞いても最後はここで要確認)