[読書記録] エンタープライズ アプリケーションアーキテクチャパターン 1章、2章

ドメイン駆動設計の学習のため、マーチン・ファウラー著のエンタープライズアプリケーションアーキテクチャパターンを読み、読書記録をつけていきます。

【前提】
リッチクライアントではなくWeb前提に内容記載。

【第一章:レイヤ分割】
レイヤは役割ごとに3つにわけるようにする。

①プレゼンテーションレイヤ
 →HTMLやユーザリクエストを行うレイヤ
②ドメインレイヤ
 →システムロジックを記載するレイヤ
 基本的に、サーバー側で実行するよう設計する
 ただし、レスポンス性とシステム切断時操作の項目を優遇して考慮すべき時は、クライアント側に実装を検討しても良いかも
 でもクライアントに全てのドメインレイヤを持ってくると大抵は保守で死ねる
③データソースレイヤ
 →データベース、外部システム通信を行うレイヤ
 基本的に、サーバー側でだけ実行するようにする

レイヤ分割の基準などは?
①簡易システム
 →サブルーチンレベル内の実装において分割すれば良い
②それなりに複雑なシステム
 →レイヤをクラスにして分割
③煩雑な処理が要求されるシステム
 →分割したクラスをさらに複数のパッケージに分類

【第二章:ドメインレイヤの実装】
ドメインレイヤには3つ基本パターンがあることを理解する。

①トランザクションスクリプト
 →最も基本でシンプル
 プレゼンテーションレイヤからの入力値を処理するため、必要な機能ごとに一連の処理をいくつかのサブルーチンに記述する
 共通の処理を共通サブルーチン化したとしても、処理の重複実装が多くなるデメリットがある
 ある意味レガシーなシステムは全部こいつで実装されている
 (レイヤ分割されていないものはそもそも論外として)

②ドメインモデル
 →オブジェクト指向でよく利用される(ている)のはこのパターン
 システムに登場する名詞は一定の基準や規則でオブジェクト化される(ているはず)ので、その単位で処理を記述する
 例) 賃貸契約オブジェクトなら契約計算処理も加える、など

 そういえばドメインモデルを利用したアーキテクチャって最近の.NETのアーキテクチャが参考になる気がする
 EntityFrameworkとか
 適用する際に死ぬほど複数のパターンが出てくるけど・・・(Unit of workとか)
 トランザクションスクリプトは、旧来のJavaフレームワークの使い方が参考になる
 例えばSeaserプロジェクトのSAStrutsとかそのへん
 というかDIコンテナ使ってるとどうしてもトランザクションスクリプトの構成になりやすい
 (Daoパターンは別物として)

③テーブルモジュール
 →データベースのResultSetの構造を基準にするパターン
 DataSetとかDataTableを使ってた旧来の.NETがこのパターン
 GUIと相性が良いが、アプリ基盤を作成する際にテーブル構造に縛られるので、複雑なデザインパターンの適用が難しい

 筆者は基本的にドメインモデルを中心に採用するよう指示している。
 ただし、ロジックの複雑さがそれほどでもない場合、トランザクションスクリプトとテーブルモジュールを採用することも許容できるといっている。
 さらにドメインモデルとテーブルモジュールの場合、データベーストランザクションを制御するサービスレイヤを置くことを推奨している。
 このレイヤーはファサードパターンを適用して、内部の挙動を隠すようにする。
 (ユースケースが増えた場合の対応を想定している)

ファサードパターンはサービスレイヤ以下の実装機能をAPI的に利用できるようにするためのパターン。
ファサードパターン

[Perl] 異なるバージョンのPerlをplenvで管理する

Perlは常にバージョンアップされています。
バージョンによってコア機能の挙動が変わることは稀ですが、あり得ないとは言えません。
Perlのバージョンを上げたら既存のアプリが動かなくなった、しかし以前のバージョンは上書きしてしまった…。
そんな状況を無くしてくれるのがplenvです。
異なるバージョンのPerlをインストールし、管理することができます。
※導入にはgitが必要です。

【インストール】

$ git clone git://github.com/tokuhirom/plenv.git ~/.plenv
$ git clone git://github.com/tokuhirom/Perl-Build.git ~/.plenv/plugins/perl-build/ 

【環境を整備】

$ vi ~/.bash_profile
・以下を追加
export PATH="$HOME/.plenv/bin:$PATH"
eval "$(plenv init -)"

【インストール、環境整備を確認】

$ plenv -v
plenv 2.2.0-12-g75267ce

【perlのバージョンを指定してインストール】

・インストール可能なバージョンを確認
$ plenv install -l
...
 5.25.0
 5.25.1
 5.25.2
 5.25.3
 5.25.4
 5.25.5
 5.25.6
 5.25.7

・バージョンを指定してインストール
$ plenv install 5.25.7

・インストール結果を確認
$ plenv versions
* system (set by ・・・)
  5.25.7

・利用するバージョンを変更
$ plenv global 5.25.7

・バージョンを確認
$ perl -v
This is perl 5, version xx, subversion x (v5.xx.x)

システムPerlを上書きしなくて良いのは個人的に助かります。

[Linux] CentOSインストール直後に行うべき設定

CentOS7をインストール後、ネットワーク接続がOFFになっていたりSSHポートがデフォルトで22になっていたりする。
つい色々忘れたまま利用するのでメモしておきます。

Ⅰ.SELinuxを無効化
本番運用サーバーでもなければSELinuxは無効化しておいた方が便利です。
一時的に無効化することもできますし、再起動時にも無効化されるように設定もできます。

・一時的に無効化
setenforce 0

・一時的に有効化
setenforce 1
・再起動後も無効化
vi /etc/selinux/config

SELINUX=enforcing
 ↓
SELINUX=disabled

Ⅱ.SSHポートを変更、ルートログインを不許可
デフォルトポート22で運用すると、必ず攻撃を受けるので変更します。
同様に、セキュリティ上の観点からルートユーザーによるログインを不許可とします。

・sshd_configを編集
vi /etc/ssh/sshd_config

# Port 22
 ↓
Port 任意のポート番号

# PermitRootLogin yes
 ↓
PermitRootLogin no

Ⅲ.firewalldを確認
SSHが許可されているか確認します。
firewalldがインストールされていない場合は至急インストールする。

vi /etc/firewalld/zones/public.xml

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.>/description>
  <service name="dhcpv6-client"/>
  <service name="ssh"/> ←これがあれば良い
</zone>

Ⅳ.起動時に自動でネットワーク接続するよう設定

cd /etc/sysconfig/network-scripts/

ifcfg-eth0など、環境によって異なるが接続用のスクリプトが存在するので編集
vi ifcfg-eth0

ONBOOT=no
 ↓
ONBOOT=yes

とりあえずここまでやって再起動。
変更後のポートで接続できること、22番ポートからはアクセスできないこと、ルートユーザーではアクセスできないことを確認すればOK。

要件定義フェーズのチェックポイントと成果物

要件定義フェーズからプロジェクトに携わると、いつもどんな情報をまとめるべきかよく迷うのでメモ。

チーム内の進捗管理とか、後続フェーズの予定を資料として起こすことも必要だが、今回は顧客要件の把握に必要なポイントや手順・作成すべき成果物を中心に自分なりにまとめてみる。
(あくまで個人的な考え方や方法をまとめただけです)

まずチェックポイント。
最終的にフェーズ中での自己チェックリストにできたら良いと思っているので、今後も追記・改訂予定。

Ⅰ.対象システムのシステム価値と周辺環境を明確にする
 ・対象システムが顧客の業務内でどのような役割を果たし、どのような価値をもつのか
 ・対象システムに関係する他システムはどのようなものがあるか
 ・現時点での業務フローはどのようなものか
 ・現行フロー、業務における問題点は何か?
 ・各フロー、業務のステークホルダーとユーザーは誰か
 ・対象システムの外部環境(他システムや業務フロー)は変化する必要があるか
 ・対象システムへの要求は明確か?
 ・対象システムの利用シーンはユースケースで明確に定義できるか?
 上記にチェックがつかない場合、ヒアリングが不足しているか顧客自身が要求を把握できていない可能性が高い。

Ⅱ.対象システムの境界を明確にする
 開発のスコープを決めるために非常に重要。
 顧客にとってはアウトプットが全てなので、ここの要求把握が曖昧だと後続フェーズで収拾がつかない。
 ・外部環境To/Fromの入出力情報は明確か?
 ・関係する他システムや業務との連携の定義に漏れはないか?(ユースケースごとに)
 ・各入出力のタイミングはどのようなものがあるか?

Ⅲ.対象システムの機能と必要なデータ定義を明確にする
 ・ユーザー、ステークホルダーが利用する機能は網羅されているか?(from 利用シーン、ユースケース)
 ・外部システム連携で必要な機能は網羅されているか?
 ・明確になった要求が上記までの機能で実現できるか?
 ・システムに必要なデータの種別と処理区分(CRUD)は網羅できているか?
 ・定義したデータのスコープ、ライフサイクルは把握できているか?
 ・網羅した機能が実現方法の考慮によって制限されていないか?

次に、作成すべきと思っている成果物とチェックポイント。
これも忘れやすいのでメモ。

Ⅰ.コンテキストモデル(1つにまとめる)
 ・全ユーザーが網羅されているか?(登場しているか?)
 ・全外部システムが網羅されているか?
 ・システムの目的は明確に記載されているか?
 システム概念図のような旧来の書き方でもよいので、とにかく1つにまとめてみて抜けが無いか確かめる。

Ⅱ.要求モデル(コンテキストモデルを元に)
 ・各ユーザーの要求をモデル化できているか?網羅されているか?

他、定義する成果物(プロジェクトの状態によって変化する)
 ・業務モデルあるいは業務フロー(コンテキストモデルを元に)
 ・ユースケースモデル
 ・画面、帳票モデル
 ・イベントモデル
 ・データモデル

要件定義フェーズの進捗管理手法は別問題になるので特に触れない。
一番大事なのは「チームで認識が共通化できるか」だと思うので、ベストプラクティスはチーム毎に異なって当然と思う。

[Java] GC overhead limit exceeded なOutOfMemoryError

今回はJavaのOutOfMemoryエラーについて。
ですが、その中でもお目にかかることが結構特殊な「GC overhead limit exceeded」を取り上げてみます。

最近はシステムで利用できるリソースが大きくなってきているため、特にメモリの利用状況や解放を意識せずプログラムを組む人も多いと思います。
※Javaの場合、どんなに意識してもGCの機構的に、プログラマーからメモリを完全に制御できるような状態にはならないんですが、そこは置いておきます

■Javaのメモリ領域管理
Javaはどうやってデータをメモリに管理するのかを知ると、GC overhead limit exceededについても理解できます。
このメモリ領域の内容ですが、Java7までと8で変化がありました。

以前、JJUGナイトセミナーに参加した際、櫻庭 祐一さんが解説して下さってましたので、動画を貼っておきます。
Java8の内容だけだったかな・・・、ちょっと記憶が怪しいですが、動画の後半で解説されています。

[Java7まで]
以下のページあたりが詳しいです。
http://www.itmedia.co.jp/enterprise/articles/0905/27/news002.html

・Javaヒープ
 New領域のEden領域・Survivor領域、Old領域に分割され、アプリから生成されるオブジェクトが格納される。
 まずEdenが使用され、GCが参照破棄されたオブジェクトを破棄し、参照が生きているものをSurvivorに移す。
 長い期間参照され続け、今後も参照され続けるオブジェクトはOld領域に移す。
 Old領域もいっぱいの場合は、後述のPermヒープなども含めてのFullGCが走る。
・Permヒープ
 Permanent領域と呼ばれるもの。
 クラスやメソッドのメタ情報を格納する。
・Cヒープ
 Java APIのネイティブ実装が利用する領域。
 圧縮処理を行うAPIを利用したり、ファイルIOで大量のデータを一度に読み込んだりすると領域圧迫されることがある。
・スレッドスタック
 スレッド実行時に利用される領域。

[Java8]
上記のJava7のメモリ管理のうち、Permヒープ(Permanent領域)がMetaspaceという名称に変わり、管理する方法もされる内容も変わる。
JVMで管理されているヒープ領域から、メタ情報を扱う「ネイティブ領域」に変更。
また、これまではstaticな変数や定数などもPermanent領域で管理していたが、これらはJavaヒープで管理されるようになった。

■どんな時にGC overhead limit exceededが発生するか
Javaヒープ領域を使い切って、それ以上割り当てができなくなればJava Heap Errorが発生したりします。
ではGC overhead limit exceededはどんな時に発生するのか?

①システムの一連の処理の中で、GCが発生しまくっている
②VMがGCを実施しても、解放されるヒープスペースが僅かしかない

通常、FullGCが走ると、Old領域を含めたすべてのJVM管理領域上にある不要なオブジェクトは解放されます。
これができなくなっていることが原因です。
プログラムから大量のインスタンスを作成し続けて、全く解放を意識しないで保持し続けると発生することがあります。

上記の場合、FullGCが連続で何回も実行されていることが多いです。
FullGCが発生している間は他のプロセスも停止するので、システム全体が一定時間ハングアップする可能性の高い危険なエラーです。

■対応策
ヒープを増やせれば発生する可能性も減りますが、このエラーが発生する場合はプログラムの内容そのものに問題があることが多いので根本解決になりません。
物理的にメモリを増やすにも限界がありますし。
なので、ここではプログラムからの解決方法を挙げておきます。

①インスタンスの生成量を意識する
特に注意したいのは文字列結合時などです。
String型の結合をしまくる場合、都度インスタンスが新規に作成されていることを意識します。
ありきたりですが、StringBuilderとか利用するとうまくいくかもしれません。

オブジェクトは必要な時に作成する、可能な限り再利用するというのも大事な考え。
生成には時間的にもリソース的にもコストがかかります。

②インスタンスの解放を意識する
JVMのGC実行のタイミングは、プログラム上から明示的に指定することができません。
System.gc()を呼び出しても、そのタイミングはGC任せのようです。
そしてなるだけFullGCが起きる状況を作らないことが大事です。

GCがあるタイミングで実行された際、メモリが的確に解放されるには
「変数などにnullを明示的に設定する」必要があります。

不要なオブジェクトの参照は、可能な限りプログラマが明示的に外しておいたほうが良いようです。
Java7からはtry-with-resourcesが使えるようになったので、これもうまく利用すべきです。

※.NET系で有効なDispose/FinalizeパターンをJavaで実装することは危険なようなので、ここでは触れません

———————————————–
かなりざっくりな解説と対応方法になってしまいました。。。
DBからデータ取得する際なども、ROWNUMやLIMITを利用して分割取得するなどして、一度に大量のリソースを食わないようにすることで対応する、などいくらでも方法はありますが、今回は一番基本的なところだけ。。。

[Perl] Mouse::Role インターフェースを実現する

Perl5には、Javaや.NETのようなインターフェースを定義するための機能は提供されていません。
しかし、Mouse::Roleを利用することで近いものを実現できそうです。
※Mouseモジュールについては別の記事を参考にしてください

■インターフェース(Roleクラス)

package MouseRoleTest;

use Mouse::Role;
requires 'print_prop1';
requires 'print_all';

1;

■実装クラス

package MouseImplement;

use Mouse;
use Data::Dumper;
use MouseRoleTest;
with 'MouseRoleTest';

has 'Prop01' => (is => 'rw', isa => 'Str');
has 'Prop02' => (is => 'rw', isa => 'Str');

sub BUILD {
}

sub print_prop1 {
  my ($self) = @_;
  print Dumper $self->Prop01;
}

sub print_all {
  my ($self) = @_;
  my ($self) = @_;
  print Dumper $self;
}

__PACKAGE__->meta->make_immutable();

■実行処理

use MouseImplement;

# 実行処理
my $mouse_obj = new MouseImplement;
$mouse_obj->Prop01('test string 1.');
$mouse_obj->print_prop1();
$mouse_obj->print_all();

1;

至って単純な実装ですが、一応解説。
MouseRoleTestモジュールのrequiresで、このRoleを実装クラスに対して実装を強要するメソッドを指定しています。
MouseImplement側ではwith ‘MouseRoleTest’でRoleを明示し、そこでrequires定義されたメソッドを実装します。
またインターフェースの役割を担うのでMouseRoleTest内でデータは持たないようにします。

上記を実行すると、以下の表示が得られます。

$VAR1 = 'test string 1.';
$VAR1 = bless( {
  'Prop01' => 'test string 1.'
  }, 'MouseImplement' );

ちゃんと実装の強要がされているか、試しにMouseImplementのメソッドを外し実行してみるとエラーが吐かれます。

ちなみにRoleのインスタンスは生成できません。
また、JavaのようにnewしたオブジェクトをRole型の変数に格納するなどということもできません。
純粋なインターフェースというよりMixin提供用の機能のようですが、多重継承をするよりはクラス定義などが把握しやすくなると思われます。

これだけ書いても寂しいので、Role関連の機能を他にも紹介します。

[メソッドモディファイア]
これは正確にはRoleの機能ではなく、Mouseで実装している機能ですが。
Roleに使うと色々便利なので紹介します。
メソッドに対する振る舞いを定義する機能です。

before・after・aroundなどのモディファイアがあり以下のように使えます。
例えば、あるメソッドが呼ばれる前に何らかの処理を追加したい場合はbeforeモディファイアを使用します。

■インターフェース(Roleクラス)

package MouseRoleTest;

use Carp;
use Mouse::Role;
requires 'print_prop1';
requires 'print_all';

### beforeモディファイアを追加
before 'print_prop1' => sub {
  my $self = shift;
  # print_prop1が処理される前にプロパティをチェックする
  if(!defined $self->Prop01()) {
    croak;
  }
};

1;

上記のようにするとprint_prop1メソッドを呼び出す前に、Roleで定義しているプロパティチェック処理が走ります。
適当なところでブレークポイントを貼ってテストしてみると、ちゃんとbefore内のチェックが行われるはずです。

試しに実行処理モジュールのプロパティ代入処理を外して、Prop01がundefの状態のままbefore内の処理が呼ばれるようにしてみます。

■実行処理

use MouseImplement;

# 実行処理
my $mouse_obj = new MouseImplement;
# $mouse_obj->Prop01('test string 1.');  <---外した
$mouse_obj->print_prop1();
$mouse_obj->print_all();

1;

この状態でテストすると、croakでエラーがちゃんと吐かれます。

同じように、afterモディファイアを利用するとメソッド処理後の処理を定義できます。
aroundは少し特殊で、引数にメソッド・オブジェクト・パラメータをそれぞれ受け取ります。
なので、例えば引数の値をチェックして、その結果によってはメソッドを全く呼ばないなど特殊な処理を定義することが可能です。

[Perl] Win32::TieRegistry Windowsのレジストリをいじる

Perlからのレジストリ操作の方法をメモ。

Win32::TieRegistryモジュールを利用すると簡単にできます。
テストのため、事前にレジストリを編集しHKEY_CURRENT_USER/SYSTEM/PerlTestを作成してTestKeyというKeyを登録しておきました。

use utf8;
use strict;
use Win32::TieRegistry qw[:KEY_];

my $env = $Registry->Open(
  'HKEY_CURRENT_USER/SYSTEM/PerlTest',
  { Access => KEY_READ() | KEY_WRITE(), Delimiter => '/' }
);
$env->SetValue('TestKey', 'Value');

これを実行してレジストリエディタで該当のパスを見ると、Valueという文字列が入っていると思います。

$env->GetValue('TestKey');

とすれば、キーに設定してあるデータを取得できるようです。

[Perl] MouseオブジェクトのArrayRef型プロパティの扱い

mouseオブジェクト使用時のArrayRef型プロパティの扱いでいつも迷うのでメモ。
別サブルーチンに配列渡す際はリファレンスで渡す、であるとか色んな制約とごっちゃになったりしているらしく。。。

■呼び出し元クラス

package oya;
use Mouse;
use Data::Dumper;

my $obj;

sub BUILD {
  my ($self) = @_;
  $obj = new ko();
}

sub call_ko_set {
  my ($self) = @_;
  my @arr_test = ('str01','str02','str03');
  print Dumper @arr_test; ---①
  $obj->pro(\@arr_test);
  print Dumper $obj->pro; ---②
}

sub call_ko_get {
  my ($self) = @_;
  my $test01 = $obj->pro;
  print Dumper $test01; ---③
  my @test01 = @$test01;
  print Dumper @test01; ---④
  my @test02 = $obj->pro;
  print Dumper @test02; ---⑤
}

__PACKAGE__->meta->make_immutable();

■呼び出し先クラス

package ko;
use Mouse;

has 'pro' => (is => 'rw', isa => 'ArrayRef');

__PACKAGE__->meta->make_immutable();

■各ポイントでの出力結果

①
$VAR1 = 'str01';
$VAR2 = 'str02';
$VAR3 = 'str03';
-----------------------
②
$VAR1 = [
'str01',
'str02',
'str03'
];
-----------------------
③
$VAR1 = [
'str01',
'str02',
'str03'
];
-----------------------
④
$VAR1 = 'str01';
$VAR2 = 'str02';
$VAR3 = 'str03';
-----------------------
⑤
$VAR1 = [
'str01',
'str02',
'str03'
];

注意点としては以下ですかね。
・ArrayRefプロパティへの格納時は配列リファレンスで渡す
・ArrayRefプロパティに格納したオブジェクトを直接配列変数に格納しない
(これを続けると配列の次元がおかしいことに・・・)

[Perl] Unicode::Escape 日本語文字列を簡単にデコードするモジュール

Unicodeエスケープされた日本語文字列は、そのままダンプしたりすると「\uxxxx」形式で表示されます。
これをPerlで日本語文字列にデコードしようとして手こずったのでメモ。

utf-8フラグを気にしつつ変換してやらないといけないんだろうなー、と考えながらCPANモジュールを漁っていたら、簡単に変換してくれるモジュールがありました。
CPAN – Unicode::Escape

上記のSYNOPSISにある通り、unescapeサブルーチンにエスケープ文字列を渡してやればOKです。
UTF8以外のエンコードを利用する場合は少し注意する必要があるようですが、非常に簡単に変換が行えます。

一応テストしたコードも記載。

use utf8;
use Unicode::Escape qw(escape unescape);
use Data::Dumper;

my $data = "\u3053\u306e\u30a4\u30d9\u30f3\u30c8\u306b\u3064\u3044\u3066";
my $unescaped = unescape($data);
print Dumper $unescaped;

1;

出力結果は以下です。

$VAR1 = 'このイベントについて'

[Oracle / .NET] SQL*Loaderを使用してcsvファイルを高速インポート サンプル

数万件~100万件規模の大量データをOracleに対してインポート(INSERT)する場合、そのままinsert文を発行していると気が遠くなるような時間が必要です。

単純なデータを大量にインポートする場合は、SQL*Loaderを使用すると高速に処理することが可能です。
(方法によっては諸々の制限がつきますが、ここでは割愛します)
このツールはOracle Clientがインストールされているマシンから起動することができます。

SQL*Loaderを使用するには以下の手順を踏みます。
1.インポート対象のCSV、TSVファイルなどを用意する
2.制御ファイルを用意する(こちらなどを参照)

OPTIONS(LOAD=-1,SKIP=0,ERRORS=-1,ROWS=-1)
LOAD DATA
INFILE 'c:\import_data.csv'
BADFILE 'c:\import_data.bad'
APPEND
INTO TABLE T_KEIHI_SINSEI_HEADER
FIELDS TERMINATED BY ","
OPTIONALLY ENCLOSED BY "'"
TRAILING NULLCOLS
(
    SINSEI_YM,
    SYAIN_NO,
    STATUS,
    TEST_COLUMN_01,
    TEST_COLUMN_02,
    TEST_COLUMN_03,
    TEST_COLUMN_04,
    TEST_COLUMN_05
)

3.SQL*Loaderをコマンドラインから起動し、オプションを指定して実行する
c:\>sqlldr userid=[対象スキーマ]/[パスワード]@[sid] control=[ファイルパス]

結果、ログファイルやBADファイル(処理不良データの載るファイル)が生成され、データがoracleにインポートされます。

下では、C#コンソールアプリからSQL*Loaderを呼び出し、CSVファイルのインポートをさせてみました。
処理的には
・Aスキーマからデータを取得してCSVを生成
・CSVデータをBスキーマへ流し込む
という感じです。

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
using System.Data;

namespace ConsoleSqlLoaderTest
{
    class Program
    {
        private const String CONNECT_STRING_01 = "Data Source=****;
              Persist Security Info=True;User ID=****;password=****";
        private const String CSV_PATH = "c:/import_data.csv";
        private const String LOADER_FILE_NAME = "sqlldr.exe";
        private const String LOADER_ARGUMENTS = 
            "userid=****/****@**** control=import_data.ctl";

        private const String MESSAGE_01 = "SQL Loaderは正常に処理を終了しました";
        private const String MESSAGE_02 = 
         "CTLファイルなし、もしくはパラメーターエラーが発生しています";
        private const String MESSAGE_03 = "BADファイルが出力されています";

        static void Main(string[] args)
        {
            try
            {
                //DB対応のクラスを生成
                T_KEIHI_SINSEI_HEADER objDao = new T_KEIHI_SINSEI_HEADER();
                //データベースからデータ抽出
                objDao.FetchHeaderData(CONNECT_STRING_01);
                //抽出データをExcelにCSV形式で保存
                objDao.OutputCsv(CSV_PATH);
                //SQLLoaderでデータベースへCSVの内容を格納
                ImportCsvForLoader();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
        //SQLLoaderでデータベースへCSVの内容を格納
        private static void ImportCsvForLoader()
        {
            // SQLLDRの実行
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.WorkingDirectory = "c:/"; //作業フォルダを指定する
            p.StartInfo.FileName = LOADER_FILE_NAME;
            p.StartInfo.Arguments = LOADER_ARGUMENTS;
            p.Start(); //ここで実行
            p.WaitForExit(); //終了まで待機
            //実行結果を確認
            switch(p.ExitCode){
                case 0: //正常終了
                    Console.WriteLine(MESSAGE_01);
                    break;
                case 1: //CTLファイルなし、パラメーターエラー
                    Console.WriteLine(MESSAGE_02);
                    break;
                case 2: //BADファイル出力あり
                    Console.WriteLine(MESSAGE_03);
                    break;
            }
        }
    }
    class T_KEIHI_SINSEI_HEADER
    {
        //発行クエリ
        private const string SELECT_QUERY_01 = "SELECT * FROM T_KEIHI_SINSEI_HEADER";
        //---コンストラクタ---
        public T_KEIHI_SINSEI_HEADER(){
            _sinseiYm = new ArrayList();
            _syainNo = new ArrayList();
            _status = new ArrayList();
            _testColumn01 = new ArrayList();
            _testColumn02 = new ArrayList();
            _testColumn03 = new ArrayList();
            _testColumn04 = new ArrayList();
            _testColumn05 = new ArrayList();
            _connection = new OracleConnection();
            _command = new OracleCommand();
        }
        //---フィールド---
        private ArrayList _sinseiYm;
        private ArrayList _syainNo;
        private ArrayList _status;
        private ArrayList _testColumn01;
        private ArrayList _testColumn02;
        private ArrayList _testColumn03;
        private ArrayList _testColumn04;
        private ArrayList _testColumn05;
        private OracleConnection _connection;
        private OracleCommand _command;
        private OracleDataReader _reader;
        //---メソッド---
        //ヘッダーデータを自身に格納
        public void FetchHeaderData(String pConnectStr)
        {
            try
            {
                _connection.ConnectionString = pConnectStr;
                _connection.Open();
                try
                {
                    _command.Connection = _connection;
                    _command.CommandType = CommandType.Text;
                    _command.CommandText = SELECT_QUERY_01;
                    //対象カウントを取得
                    _reader = _command.ExecuteReader();
                    while (_reader.Read())
                    {
                        _sinseiYm.Add(_reader.GetString(0));
                        _syainNo.Add(_reader.GetInt32(1));
                        _status.Add(_reader.GetString(2));
                        _testColumn01.Add(_reader.GetString(3));
                        _testColumn02.Add(_reader.GetString(4));
                        _testColumn03.Add(_reader.GetString(5));
                        _testColumn04.Add(_reader.GetString(6));
                        _testColumn05.Add(_reader.GetString(7));
                    }
                }
                catch (Exception ei)
                {
                    throw ei;
                }
                finally
                {
                    _connection.Close();
                    _connection.Dispose();
                    _command.Dispose();
                    if (_reader != null)
                    {
                        _reader.Dispose();
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
            finally
            {
            }
        }
        //抽出データをExcelにCSV形式で保存
        public void OutputCsv(String pCsvPath)
        {
            System.Text.Encoding enc =
                System.Text.Encoding.GetEncoding("Shift_JIS");
            try
            {
                //開く
                System.IO.StreamWriter writer =
                    new System.IO.StreamWriter(pCsvPath, false, enc);
                try
                {
                    //フィールドを格納
                    for (int i = 0; i < _sinseiYm.Count; i++)
                    {
                        writer.Write(_sinseiYm[i] + ",");
                        writer.Write(_syainNo[i] + ",");
                        writer.Write(_status[i] + ",");
                        writer.Write(_testColumn01[i] + ",");
                        writer.Write(_testColumn02[i] + ",");
                        writer.Write(_testColumn03[i] + ",");
                        writer.Write(_testColumn04[i] + ",");
                        writer.WriteLine(_testColumn05[i]);
                    }
                }
                catch (Exception ei)
                {
                    throw ei;
                }
                finally
                {
                    //閉じる
                    writer.Close();
                }
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
            finally
            {
            }
        }
    }
}