トップ «前の日(08-14) 最新 次の日(08-16)» 追記

Masa's blog

検索キーワード:

2009年08月15日 Java儂(わし)的解釈によるメモ(JDBC + Oracle Database 10g Express Edition編)

_ Java儂(わし)的解釈によるメモ(JDBC + Oracle Database 10g Express Edition編)

さて、Oracleにも無償版があるとの事で、早速試してみた(Oracle Database 10g Express Edition)。

サーバはWindows版とLinux版が有る。とりあえずインストールの簡単そうだったWindows版で試してみる。

クライアントはLinuxで。

こちらも基本的にはJava儂(わし)的解釈によるメモ(JDBC + MySQL編)と同様だが、MS SQL Serverの時の様な方言的な動きは無く、ロードするドライバの部分だけをOracle用に書き換えれば動作した。

サーバ側(IP=192.168.0.1)にインストールする物

クライアント側にインストールする物

サンプルプログラム

//
// 「jdbc oracle」テスト
//
// compile : javac -encoding uutf-8 jdbc_oracle.java
// run     : java -cp .:ojdbc14.jar jdbc_oracle
//
//import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class jdbc_oracle {

	static Connection conn = null;
	static Statement stmt = null;
	static PreparedStatement pstmt = null;
	static ResultSet rs = null;
	static ResultSetMetaData rsmd = null;

	public static void main(String[] args) {
		try {
//
// JDBC for ORACLEのロード
//
			Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
			System.out.println("JDBCドライバをロードしました。");
//
// データベースへの接続
//
			String url = "jdbc:oracle:thin:@192.168.0.1:1521:XE";
			conn = DriverManager.getConnection(url, "system", "SYSTEM-PASSWORD");
			System.out.println("XE(データベース)に接続しました。");
//
// 自動コミット停止(トランザクションの開始)
//
			conn.setAutoCommit(false);
			System.out.println("自動コミットをOFFにしました。");
//
// SQLステートメントオブジェクト生成
//
			stmt = conn.createStatement();
//
// テーブルの削除
//
			try {
				stmt.executeUpdate("drop table member");
				System.out.println("テーブル member を削除しました。");
			} catch(Exception e){
				System.out.println("テーブル member を削除できませんでした(" + e.getMessage() + ")");
			}
//
// テーブルの作成
//
			stmt.executeUpdate(
				"create table member (" +
					"id INT PRIMARY KEY," +
					"name NVARCHAR2(50)," +
					"job NVARCHAR2(50)" +
				")"
			);
			System.out.println("テーブル member を作成しました。");
//
// わざと2重にテーブル作成してみる
//
			try {
				stmt.executeUpdate(
					"create table member (" +
						"id INT PRIMARY KEY," +
						"name NVARCHAR2(50)," +
						"job NVARCHAR2(50)" +
					")"
				);
				System.out.println("テーブル member を作成しました(2)。");
			} catch(Exception e){
				System.out.println("テーブル member を作成できませんでした(2)(" + e.getMessage() + ")");
			}
//
// 2次インデックス作成
//
			stmt.executeUpdate("create index i_member_name on member(name)");
			System.out.println("インデックスを作成しました。");
//
// タプルの追加
//
			try {
				pstmt = conn.prepareStatement("insert into member (id, name, job) values (?, ?, ?)");
//
// 1件目
//
				pstmt.setInt(1, 1);
				pstmt.setString(2, "伊藤 太郎");
				pstmt.setString(3, "サラリーマン");
				pstmt.executeUpdate();
				System.out.println("インサートしました。");
//
// 2件目
//
				pstmt.setInt(1, 2);
				pstmt.setString(2, "山田 太郎");
				pstmt.setString(3, "野球選手");
				pstmt.executeUpdate();
				System.out.println("インサートしました。");
//
// 3件目
//
				pstmt.setInt(1, 3);
				pstmt.setString(2, "山田 花子");
				pstmt.setString(3, "タレント");
				pstmt.executeUpdate();
				System.out.println("インサートしました。");
//
// わざと3件目をもう一度追加してみる
//
				pstmt.setInt(1, 3);
				pstmt.setString(2, "山田 花子");
				pstmt.setString(3, "タレント");
				pstmt.executeUpdate();
				System.out.println("インサートしました。");
			} catch (Exception e) {
				System.out.println("インサートできませんでした(" + e.getMessage() + ")");
			}
//
// タプルの検索
//
			try {
//
// クエリの実行
//
				pstmt = conn.prepareStatement("select * from member where id >= ?");
				pstmt.setInt(1, 1);
				rs = pstmt.executeQuery();
//
// 1件目の取り出し
//
				rs.next();
				System.out.println("セレクトしました(id=" +
					rs.getInt("id") + ")(name=" +
					rs.getString("name") + ")(job=" +
					rs.getString("job") + ")。");
//
// 2件目の取り出し
//
				rs.next();
				System.out.println("セレクトしました(id=" +
					rs.getInt("id") + ")(name=" +
					rs.getString("name") + ")(job=" +
					rs.getString("job") + ")。");
//
// 3件目の取り出し
//
				rs.next();
				System.out.println("セレクトしました(id=" +
					rs.getInt("id") + ")(name=" +
					rs.getString("name") + ")(job=" +
					rs.getString("job") + ")。");
//
// わざと存在しない4件目を取り出してみる
//
				rs.next();
				System.out.println("セレクトしました(id=" +
					rs.getInt("id") + ")(name=" +
					rs.getString("name") + ")(job=" +
					rs.getString("job") + ")。");
			} catch (Exception e) {
				System.out.println("セレクトできませんでした(" + e.getMessage() + ")");
			}
//
// タプルの更新
//
			try {
//
// 更新ステートメント実行
//
				pstmt = conn.prepareStatement("update member set job = ? where name = ?");
				pstmt.setString(1, "裁判官");
				pstmt.setString(2, "山田 太郎");
				pstmt.executeUpdate();
				System.out.println("アップデートしました。");
//
// 更新結果を取り出すクエリの実行
//
				pstmt = conn.prepareStatement("select * from member where name = ?");
				pstmt.setString(1, "山田 太郎");
				rs = pstmt.executeQuery();
//
// クエリ結果の取り出し
//
				rs.next();
				System.out.println("アップデート結果(id=" +
					rs.getInt("id") + ")(name=" +
					rs.getString("name") + ")(job=" +
					rs.getString("job") + ")。");
			} catch (Exception e) {
				System.out.println("アップデートできませんでした(" + e.getMessage() + ")");
			}
//
// タプルの削除
//
			try {
//
// 削除ステートメント実行
//
				pstmt = conn.prepareStatement("delete from member where name like ?");
				pstmt.setString(1, "山田%");
				pstmt.executeUpdate();
				System.out.println("デリートしました(" + pstmt.getUpdateCount() + "件)。");
//
// 削除後の状態の検索クエリ実行
//
				pstmt = conn.prepareStatement("select * from member");
				rs = pstmt.executeQuery();
//
// クエリ結果の取り出し
//
				while (rs.next()){
					System.out.println("デリート結果(id=" +
						rs.getInt("id") + ")(name=" +
						rs.getString("name") + ")(job=" +
						rs.getString("job") + ")。");
				}
			} catch (Exception e) {
				System.out.println("デリートできませんでした(" + e.getMessage() + ")");
			}
//
// メタデータの取得
//
			try {
//
// メタデータの取得
//
				rsmd = rs.getMetaData();
//
// カラム数の取得
//
				int numberOfColumns = rsmd.getColumnCount();
				System.out.print("メタデータを取得しました(");
				for (int i = 1; i <= numberOfColumns; i++) {
//
// カラム名称の取得
//
					String name = rsmd.getColumnName (i);
					System.out.print(name + "\t");
				}
				System.out.println(")。");
			} catch (Exception e) {
				System.out.println("メタデータを取得できませんでした(" + e.getMessage() + ")");
			}
//
// コミット(トランザクションの反映)
//
			try {
				conn.commit();
				System.out.println("コミットしました。");
			} catch (Exception e) {
				System.out.println("コミットできませんでした(" + e.getMessage() + ")");
			}
		} catch (SQLException e) {
			System.out.println("SQLException: " + e.getMessage());
			System.out.println("SQLState: " + e.getSQLState());
			System.out.println("VendorError: " + e.getErrorCode());
//
// ロールバック(トランザクションの破棄)
//
			try {
				conn.rollback();
			} catch(Exception e2){
				System.out.println(e2.getMessage());
			}
		} catch(Exception e){
			System.out.println(e.getMessage());
//
// ロールバック(トランザクションの破棄)
//
			try {
				conn.rollback();
			} catch(Exception e2){
				System.out.println(e2.getMessage());
			}
		}

		try {
//
// 検索クエリの結果セットオブジェクトを破棄
//
			rs.close();
//
// SQLステートメントオブジェクトを破棄
//
			stmt.close();
			pstmt.close();
//
// データベースからの切断
//
			conn.close();
		} catch(Exception e){
			System.out.println(e.getMessage());
		}
		System.out.println("XE(データベース)から切断しました。");

	}
}

_ 実行結果

JDBCドライバをロードしました。
XE(データベース)に接続しました。
自動コミットをOFFにしました。
テーブル member を削除しました。
テーブル member を作成しました。
テーブル member を作成できませんでした(2)(ORA-00955: すでに使用されているオブジェクト名です。
)
インデックスを作成しました。
インサートしました。
インサートしました。
インサートしました。
インサートできませんでした(ORA-00001: 一意制約(TESTUSER.SYS_C004065)に反しています
)
セレクトしました(id=1)(name=伊藤 太郎)(job=サラリーマン)。
セレクトしました(id=2)(name=山田 太郎)(job=野球選手)。
セレクトしました(id=3)(name=山田 花子)(job=タレント)。
セレクトできませんでした(空の結果セットです。)
アップデートしました。
アップデート結果(id=2)(name=山田 太郎)(job=裁判官)。
デリートしました(2件)。
デリート結果(id=1)(name=伊藤 太郎)(job=サラリーマン)。
メタデータを取得しました(ID     NAME    JOB     )。
コミットしました。
XE(データベース)から切断しました。

追記

  • stmt.executeUpdate(INSERT|DELETE|UPDATE) の戻り値は更新されたタプル数
  • stmt.executeUpdate(上記以外の更新系SQL) の戻り値は 0
  • エラー発生時は SQLExceptin 発生。getSQLState()にてエラーコードを取得できる。

_ Oracle 儂(わし)的解釈によるメモ(sqlplus編)

階層構造

データベース(XE)/スキーマ(ユーザ名と同じ)/テーブル
  • データベース : インスタンスと同義(?)。SIDという識別子で区別される。Express Editionの場合は XE に固定。データベース毎に異なる接続ポートを開く。慣例的に1521を使用する事が多い。
  • スキーマ : テーブル等をグループ分けする為の名前空間。データベースに接続するユーザ毎に自分のスキーマを所有する。その際のスキーマ名はユーザ名と同じになる。ユーザの作成したテーブル等のオブジェクトはデフォルトでユーザの所有するスキーマに(論理的に)格納される。
  • テーブル : 同左。

テーブルスペース

実際にデータが格納されるファイル(又はファイル群)に付けられる名称(表領域)。デフォルトで以下のテーブルスペースが有る。

  • USERS : 非 SYSTEM ユーザーにより作成される全てのデータベース・オブジェクトのデフォルト表領域
  • SYSAUX : SYSTEM 表領域の補助表領域
  • UNDO : ロールバックに必要なデータ領域
  • SYSTEM : データ・ディクショナリ表およびその他の管理データの領域

データベース作成

Express Editionの場合、同時に起動できるインスタンスが1個なので、実質あまり意味が無い。それでも新たに作った場合は既存のインスタンスをshutdownして、別のインスタンスをstartupすることになる。

ここでは説明しない。

sqlplusのヘルプ

C:\Documents and Settings\m-ito>sqlplus -?

SQL*Plus: Release 10.2.0.1.0 - Production

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

使用方法1: sqlplus -H | -V

    -H             SQL*Plusのバージョンおよび
                   使用方法のヘルプを表示します。
    -V             SQL*Plusのバージョンを表示します。

使用方法2: sqlplus [ [<option>] [<logon>] [<start>] ]

  <option>は次のとおりです: [-C <version>] [-L] [-M "<options>"] [-R <level>] [-S]

    -C <version>   影響されるコマンドの互換性を、
                   <version>で指定されたバージョンに設定します。バージョンのフォームは
                   "x.y[.z]"です。たとえば、-C 10.2.0となります
    -L             エラー時に再プロンプト表示するかわりに、
                   1回のみログオンを試行します。
    -M "<options>" 出力の自動HTMLマークアップを設定します。オプション
                   のフォームは次のとおりです:
                   HTML [ON|OFF] [HEAD text] [BODY text] [TABLE text]
                   [ENTMAP {ON|OFF}] [SPOOL {ON|OFF}] [PRE[FORMAT] {ON|OFF}]
    -R <level>     制限モードを設定し、ファイルシステムと対話する
                   SQL*Plusコマンドを無効にします。レベルは
                   1、2または3にできます。最も限定的なのは-R 3であり、
                   ファイルシステムと対話するすべてのユーザー・コマンドを
                   無効にします。
    -S             コマンドのSQL*Plusバナー、プロンプトおよび
                   エコーの表示を抑制するサイレント・モードを
                   設定します。

  <logon>は次のとおりです: (<username>[/<password>][@<connect_identifier>] | /)
              [AS SYSDBA | AS SYSOPER] | /NOLOG

    データベース接続用のデータベース・アカウント・ユーザー名、
    パスワードおよび接続識別子を指定します。接続識別子がないと、
    SQL*Plusはデフォルトのデータベースに接続します。

    AS SYSDBAおよびAS SYSOPERオプションはデータベース管理
    権限です。

    /NOLOGオプションでは、データベースに接続せずに
    SQL*Plusを起動します。

  <start>は次のとおりです: @<URL>|<filename>[.<ext>] [<parameter> ...]

    スクリプト内の置換変数に割り当てられる指定パラメータを使用して、
    Webサーバー(URL)またはローカル・ファイルシステム(filename.ext)から、
    指定のSQL*Plusスクリプトを実行します。

SQL*Plusの起動時でCONNECTコマンドの後に、サイト・プロファイル
($ORACLE_HOME/sqlplus/admin/glogin.sqlなど)およびユーザー・プロファイル
(作業ディレクトリ内のlogin.sqlなど)が実行されます。ファイルには
SQL*Plusコマンドが含まれる場合があります。

詳細は、SQL*Plusユーザーズ・ガイドおよびリファレンスを参照してください。

ユーザー作成

sqlplus sys/SYS-PASSWORD as sysdba
SQL> CREATE USER testuser IDENTIFIED BY USER-PASSWORD
  2> DEFAULT TABLESPACE users
  3> TEMPORARY TABLESPACE temp
  3> QUOTA UNLIMITED ON users;

ユーザ名はアルファベット、数字以外は避けた方が良い(ハイフン、アンダーバー等の記号を入れるとハマルかも...)。

テーブルスペースを指定しないと、デフォルトでSYSTEM表領域にデータを作りにいくので、明示的にusers表領域を指定しておく。またこの例では容量制限(quota)を外している。

ユーザー削除

sqlplus sys/SYS-PASSWORD as sysdba
SQL> DROP USER testuser;

ORA-01922のエラーメッセージが出る場合は、当該ユーザで既にデータを作成している場合。データごと消して良い場合は、

sqlplus sys/SYS-PASSWORD as sysdba
SQL> DROP USER testuser CASCADE;

アクセス権の付与(管理者)

sqlplus sys/SYS-PASSWORD as sysdba
SQL> GRANT connect,resource,dba TO testuser;
  • connect : データベースに接続する権限
  • resource : データを操作する権限
  • dba : 管理者としての権限(これだけ有れば十分?、sysdbaなら最強!)

アクセス権の付与(一般ユーザ)

データベースに接続する権限(connect)、テーブルを作成する権限(resource)と既存のテーブル(testtable)に対する操作権限(select,insert,update,delete)を与える。

sqlplus sys/SYS-PASSWORD as sysdba
SQL> GRANT connect,resource to testuser;
SQL> GRANT select,insert,update,delete ON testtable TO testuser;

select,insert,update,deleteはallで代用可能。

アクセス権の削除

sqlplus sys/SYS-PASSWORD as sysdba
SQL> REVOKE connect FROM testuser;
SQL> REVOKE select,insert,update,delete ON testtable FROM testuser;

ヘルプを表示

sqlplus sys/SYS-PASSWORD as sysdba
SQL> HELP;

ユーザ一覧

sqlplus sys/SYS-PASSWORD as sysdba
SQL> select * from all_users;

ユーザ名は全て大文字に変換されて登録されている。

テーブル一覧

sqlplus sys/SYS-PASSWORD as sysdba
SQL> select * from all_tables;
SQL> select * from user_tables;

テーブル名は全て大文字に変換されて登録されている。

テーブル構造を見る

sqlplus sys/SYS-PASSWORD as sysdba
SQL> DESC testtable;

バックアップ(フル)

まずバックアップ対称のファイルを調べる。

sqlplus sys/SYS-PASSWORD as sysdba
SQL> select name from v$datafile;
SQL> select name from v$controlfile;
SQL> select name from v$tempfile;

ORACLEデータベースを停止する。

sqlplus sys/SYS-PASSWORD as sysdba
SQL> shutdown immediate;

上記で表示されたファイルを外部媒体等にコピーする。Oracleのインストールディレクトリ以下をまるごとコピーしても良いかも(?)。

ORACLEデータベースを再起動する。

sqlplus sys/SYS-PASSWORD as sysdba
SQL> startup;

テーブルをCSVファイルに書き出す

sqlplus sys/SYS-PASSWORD as sysdba
SQL> set linesize 32767
SQL> set pagesize 0
SQL> set trimspool on
SQL> set colsep ','
SQL> set head off
SQL> spool Hoge.csv
SQL> select * from testtable;
SQL> spool off

CSVファイルからテーブルに読み込む

これと言った方法は無い...しいて言えば、CSVファイルに適当なスクリプトをかまして、insert文の形式に変換してsqlplusに食わせるぐらい。

_ Oracle 儂(わし)的解釈によるメモ(sqlldr編)

CSVファイルからテーブルに読み込む

sqlldrというコマンドで出来る。

制御ファイル作成

ファイル名を sqlldr.ctrl とする。

load data
characterset AL32UTF8        <- infileの文字セット(UTF8を指定)
infile 'INPUT.CSV'           <- 入力ファイルの指定
badfile 'BAD.CSV'            <- インポートに失敗したレコードファイル
discardfile 'DISCARD.CSV'    <- 破棄されたレコードファイル
append into table member     <- インポート先のテーブル
fields terminated by X'09'   <- 項目の区切り文字(UTF16でのTABを指定)
trailing nullcols            <- 項目が無い時、NULLを設定する
(
        id,                  <- テーブル項目の列挙
        name,                <- テーブル項目の列挙
        job                  <- テーブル項目の列挙
)
実行
sqlldr userid=USER/PASSWORD control=sqlldr.ctl log=sqlldr.log

USER/PASSWORDで認証し、sqlldr.ctlの内容に従ってデータをインポートする。実行ログがsqlldr.logに記録される。

_ Oracle 儂(わし)的解釈によるメモ(もろもろ追記編)

How to handle UTF-8 data by sqlplus on windows

Put next lines into operation.sql and save it with encoding UTF-8. If you use notepad, operation.sql will be saved with BOM.

set echo off;
set feedback off;
set termout off;
set colsep ',';
set trimspool on;
set linesize 32767;
set pagesize 0;

drop table tb_sample;
create table tb_sample(
    id char(8),
    name nvarchar2(20)
);

insert into tb_sample names(id,name) values('12345678','オラクル 太郎');
commit;

spool result.txt
select * from tb_sample where id='12345678';
spool off

quit;

rmbom.c

#include <stdio.h>
main()
{
    int ch;
    int i = 0;
    while ((ch = getchar()) != EOF){
        i++;
        if (i > 3){
            putchar(ch);
        }
    }
}

Remove BOM from operation.sql. Operate it in Cygwin.

$ ./rmbom.exe <operation.sql >/tmp/junk
$ mv /tmp/junk operation.sql

Just do it in command prompt!

> SET NLS_LANG=JAPANESE_JAPAN.AL32UTF8
> sqlplus -s _user_/_password_ @operation.sql
> SET NLS_LANG=
> notepad result.txt

How to make sure about encoding of database

sqlplus sys/sys-password as sysdba
SQL> select value from nls_database_parameters where parameter='NLS_CHARACTERSET';
SQL> select value from nls_database_parameters where parameter='NLS_NCHAR_CHARACTERSET';

How to change ARCHIVELOG mode

sqlplus sys/sys-password as sysdba
SQL> archive log list;
SQL> shutdown immediate;
SQL> startup mount;
SQL> alter database archivelog;
SQL> alter database open;
SQL> archive log list;

How to switch current logfile

sqlplus sys/sys-password as sysdba
SQL> alter system switch logfile;
SQL> select name from v$archived_log;
SQL> select * from v$log;
SQL> select * from v$logfile;

How to archive current log

sqlplus sys/sys-password as sysdba
SQL> alter system archive log current;
SQL> select name from v$archived_log;

How to see current logfile

sqlplus sys/sys-password as sysdba
SQL> select * from v$log;
SQL> select * from v$logfile;

How to delete unused archivelog safely

rman target=sys/sys-password
RMAN> list archivelog all;
RMAN> delete noprompt archivelog until time 'sysdate - 3';
RMAN> delete noprompt archivelog all;
RMAN> quit;