Oracle + JUnit4 で DBTestCaseを継承せずに dbUnit を使う
Oracle + JUnit4 で DBTestCaseを継承せずに dbUnit 使おうと思ったら、ちょっと苦労したのでメモ。
http://www.dbunit.org/howto.html#noextend によると、DBTestCase を継承しないで DBUnit のテストケースを書くには、dbUnit 2.2以降はIDatabaseTester が使えるようである。そこで、サンプルをもとに、以下の様に書いてみた。
public class HogeTest { private static IDatabaseTester dbTester; @BeforeClass public static void setUpBeforeClass() throws Exception { dbTester = new JdbcDatabaseTester("oracle.jdbc.driver.OracleDriver" ,"jdbc:oracle:thin:@xxxx:1521:xxx" ,"user" ,"pass"); dbTester.setSetUpOperation(DatabaseOperation.INSERT); dbTester.setTearDownOperation(DatabaseOperation.DELETE); IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream("testdata/hogeDataset.xml" )); dbTester.setDataSet(dataset); } @AfterClass public static void tearDownAfterClass() throws Exception { } @Before public void setUp() throws Exception { dbTester.onSetup(); } @After public void tearDown() throws Exception { dbTester.onTearDown(); } @Test public void testMethod() { // test } }
これでテストを実行すると、以下の様なエラーが発生する。
AmbiguousTableNameException
これは、http://www.dbunit.org/faq.html#AmbiguousTableNameException に記載があった。
スキーマ名を指定せよとのこと。とくに、Oracle の場合は、スキーマ名を大文字でしてする必要があるらしい。
以下の様に書き換える。
dbTester = new JdbcDatabaseTester("oracle.jdbc.driver.OracleDriver" ,"jdbc:oracle:thin:@xxxx:1521:xxx" ,"user" ,"pass","SCHEMA");
これで、AmbiguousTableNameException は発生しなくなった。
しかし、今度は、dbTester.onSetup() 、テストデータの登録時に以下の様な例外が発生するようになった。
org.dbunit.dataset.NoSuchColumnException:
dataset の xml に誤りは無く、Column名が間違っていたわけではない。
ぐぐってみると、dataTypeFactory とやらを Oracle 用にしてみれば良いらしい。そこで、以下の記述を追加する。
dbTester.getConnection().getConfig().setProperty(DatabaseConfig. PROPERTY_DATATYPE_FACTORY, new OracleDataTypeFactory());
しかし、これでは例外が解決しなかった。
考えたあぐねて、デバッグモードでdbTester.getConnection().getConfig() のプロパティを見てみたら、DefaultDataTypeFactory のままだった。
なぜ設定が反映されないのか。さっぱりわからなくなって、 JdbcDatabaseTester#getConnection のソース読んだら以下の様になっていた。
public IDatabaseConnection getConnection() throws Exception { logger.debug("getConnection() - start"); assertNotNullNorEmpty( "connectionUrl", connectionUrl ); Connection conn = null; if( username == null && password == null ){ conn = DriverManager.getConnection( connectionUrl ); }else{ conn = DriverManager.getConnection( connectionUrl, username, password ); } return new DatabaseConnection( conn, getSchema() ); }
最後でDatabaseConnectionをnewしてやがる…
つまり、getConnectionでは常に新しいIDatabaseConnectionが返るので、せっかくプロパティをセットしても保持してくれないのだ。
ちなみに、他のIDatabaseTesterの実装も同様の実装だった。
しょうが無いので、JdbcDatabaseTesterを継承し、getConnectionをオーバーライドしてその都度プロパティをセットするようにした。
最終的には、以下の様なコードになった。
public class HogeTest { private static IDatabaseTester dbTester; @BeforeClass public static void setUpBeforeClass() throws Exception { dbTester = new JdbcDatabaseTester("oracle.jdbc.driver.OracleDriver" ,"jdbc:oracle:thin:@xxxx:1521:xxx" ,"user" ,"pass","SCHEMA") { @Override public IDatabaseConnection getConnection() throws Exception { IDatabaseConnection conn = super.getConnection(); conn.getConfig().setProperty(DatabaseConfig. PROPERTY_DATATYPE_FACTORY, new OracleDataTypeFactory()); return conn; } }; dbTester.setSetUpOperation(DatabaseOperation.INSERT); dbTester.setTearDownOperation(DatabaseOperation.DELETE); IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream("testdata/hogeDataset.xml" )); dbTester.setDataSet(dataset); } @AfterClass public static void tearDownAfterClass() throws Exception { } @Before public void setUp() throws Exception { dbTester.onSetup(); } @After public void tearDown() throws Exception { dbTester.onTearDown(); } @Test public void testMethod() { // test } }
これでようやく動作した。
Python3 で os.walk() を使ってファイル一覧を生成しようとしてyeildを使うまで
Python3 でツールを書いていて、あるフォルダ以下のファイル一覧を再帰的に取得したくなった。
MS-DOS の 「DIR /S /B」みたいなイメージで。
Python の osモジュールには、os.walkというこういうときに便利な関数があるので、これを使ってファイル一覧を作ることにした。最初に書いたのが以下。
import os result = [] for path, dirs, files in os.walk(rootDir): result += map(lambda x: os.path.join(path, x), files)
まあ、これで十分シンプルでいいんだけど、for文使って足し込むのがイヤだったり、結果がiteratorでなくてリストそのもので作られてしまうので、リスト内包表記を使ってみることにした。
import os files = [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)]
でもこれじゃ望んだ結果がこない。リストのリスト(のiterator)が返された。失敗失敗。
そこで、reduceを使って、リスト内のリストを結合することにしよう。…と思ったら、Python3からはreduce関数が標準関数でなくなって、functoolsモジュールに移動したらしい。使えないからビックリした。
functoolsモジュールからreduceをimportして、以下の様に書いた。
import os from functools import reduce files = reduce(lambda a, b: a+b, [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)])
これは動かなかった。なぜなら、リスト内包表記の方が返すリストが、mapオブジェクトのリストだから。
'+'演算子はmapオブジェクトに対しては使えない。そこで、mapの結果をリストに変換して以下の様に書く
import os from functools import reduce files = reduce(lambda a, b: list(a)+list(b), [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)])
これで、filesにrootDir以下のファイルをリストで格納できた。なんか冗長になっちゃった。
しかし、ここに至って、結果がiteratorでなくてリストになってしまっていることに気付いた。大量のファイルを扱う場合であれば、メモリ効率を考えて、できればiteratorで受け取りたいところ…
そこでPythonのドキュメントを探していたら、どうも、yield文というのを使って実現するらしい。
そこで、1行で書くのはあきらめて、yield を使ってみることにした
yeildを使ってファイルの一覧を返すiterator
def filesIterator(rootDir): for path, dirs, files in os.walk(rootDir): for filePath in map(lambda x: os.path.join(path, x), files): yield filePath
これでファイルの一覧をIteratorで取得できるようになったので、あとはfor文なりなんなりで自由に処理することができる。
でもこれ、for文がネストしているのをなんとか出来ないかなあ…
Snow Leopard に git をインストールする(インストーラ使用)
SCM Bootcamp に参加したので、MBA (まだSnowLepardです…)にGitをインストールしたのでメモ。
まず、ググる先生に聞いて、以下のサイトを参照。
最速で Git を Mac にインストールして基本的なコマンドを使う方法
このサイトではMacPortsを使えば簡単!最速!と紹介されているんだけど、残念ながら自分のMBAにはMacPortsは未インストール。
そこで、「インストーラを使用する」方法を選択。
以下のサイトからインストーラを取得。せっかくだから俺はこの64bit版を選ぶぜ!
http://code.google.com/p/git-osx-installer/downloads/list?can=3
ゲットできたdmgファイルを開いて、その中にあるpkgファイルをダブルクリック。
インストール完了。はやっ!
$which git
$man git
の動作を確認。とりあえず、大丈夫かな?
MacBook Air 11インチ欲しい!
「MacBook Air 11インチ欲しい!」ってはてなダイアリーに書くと抽選で一名様にもらえるらしいんで2ヶ月ぶりに書いてます。
しかもこんな内容でごめんなさい。
現在MacBook Air(late 2009) モデルを使っていて、とくに不満は無いんだけど、鞄に入れて持ち歩くと歳のせいか腰にくるようになってしまって。
軽いMacBook Airが欲しいんですよ!
WiMAXも導入したのでモバイルの準備は万端!あとは軽いMacBookだけあればいいんです!
あ、商品のMacBook Air ってキーボード選べたりするのかしら。USキーボードの方がいいいなあ
恥ずかしながら何度でも書きましょう
Oracle JDBCドライバでLONG列にアクセスする際の注意点
LONG列なんかはもう非推奨で普通はBLOB使うからほとんど問題にはならない内容なのですが。
Oracleのデータディクショナリテーブルの一つALL_TAB_COLUMNS(USER_TAB_COLUMNS)のDATA_DEFAULT列はなんとLONG型で、これにアクセスするときにこの問題にはまって2時間ほど損失したのでメモしておく。
「Oracle JDBCドライバでLONG列を含むテーブルにアクセスする場合は、ResultSetからデータを取得するときLONG列を一番最初に取得しなければならない」
な ん だ そ りゃ
具体的には以下のような現象。
final String SQL_COLUMNINFO = "SELECT " + "col.column_name COLUMN_NAME, " + "col.data_default DATA_DEFAULT, " + //こいつがLONG列 "co.comments COMMENTS " + "FROM " + "user_tab_columns col " + "WHERE " + "col.table_name = 'HOGE' " + "ORDER BY " + "col.table_name, " + "col.column_id "; Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(); rs = stmt.executeQuery(SQL_COLUMNINFO); while(rs.next()) { String columnName = rs.getString("COLUMN_NAME"); String comments = rs.getString("COMMENTS"); String dataDefault = rs.getString("DATA_DEFAULT"); //ここで例外発生 System.out.println(ColumnName + ':' + comments + ':' + dataDefault); } } finally { try { if(rs!=null) rs.close(); } catch (SQLException e) {} try { if(ps!=null) ps.close(); } catch (SQLException e) {} }
上記のような記述だと、getString("DATA_DEFAULT") とした時点で「ORA-17027 ストリームはすでにクローズ済です」が発生する
ここは、以下のようにすると問題が回避できた
final String SQL_COLUMNINFO = "SELECT " + "col.column_name COLUMN_NAME, " + "col.data_default DATA_DEFAULT, " + //こいつがLONG列 "co.comments COMMENTS " + "FROM " + "user_tab_columns col " + "WHERE " + "col.table_name = 'HOGE' " + "ORDER BY " + "col.table_name, " + "col.column_id "; Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(); rs = stmt.executeQuery(SQL_COLUMNINFO); while(rs.next()) { String dataDefault = rs.getString("DATA_DEFAULT"); //最初に読む String columnName = rs.getString("COLUMN_NAME"); String comments = rs.getString("COMMENTS"); System.out.println(ColumnName + ':' + comments + ':' + dataDefault); } } finally { try { if(rs!=null) rs.close(); } catch (SQLException e) {} try { if(ps!=null) ps.close(); } catch (SQLException e) {} }
Oracle Database JDBC開発者ガイドおよびリファレンスにはたしかに「LONG列に後でアクセスしようとしても、データは使用できず、ドライバは「ストリームがクローズされています。」エラーを戻します。」と書いてあるけど。
気づかないよこんなの。
このOracle JDBCドライバのLONG列に対する制限って、Oracle+Javaな界隈では常識レベルの有名な話だったりするのかなあ?ググっても全然出てこなかったよ…
2010/11/13 Javaのコードに一部誤りがあったので修正しました。また、文章の一部を修正しました
Windows7 で Oracle10g を
職場で Windows7 に Oracle10g クライアントをインストールすることになった。
Oracle10gってWindows7対応はどうなってるのかな〜とおもってググって見ると、Oracle10g Exはうまくいっているようだけど、それ以外はなんかうまくいってない話ばかりが検索されてくる。
実際、手持ちのOracle10g Client(10.2)のインストーラを使うと、互換性がどうのといわれるのでひとまず途中でやめた。
ま、こういうときは普通にドキュメントにあたるのが一番なので、ドキュメントにあたってみると、Vistaには対応しているらしく、Vistaにインストールするにはこうしなさいとかかれていた(要約)
- インストーラは最新版を使え
- 管理者権限で実行せよ
2点目の「管理者権限で実行せよ」はわかるけど、最新のインストーラはどうするの?とおもって調べてみると、以下からダウンロードできることがわかった
http://www.oracle.com/technology/software/products/database/oracle10g/htdocs/10203vista.html
なんと、本国OTNのページ。
で、ここはOTN JapanのIDは通用しないので本国IDを取得してダウンロード。
これを使うことで、問題なくWindows7にインストールできました。
しかし、このダウンロードサイト、10.2.0.3のCDを丸ごとダウンロードできるんだがいいのか…?
scheduleAtFixedRate() を実行中のタスク側から停止する
Javaで、一定時間ごとに実行するプログラムをかく必要があったのでjava.util.concurrentのScheduledExecutorServiceを利用してプログラムを書いた。
一定時間ごとの実行についてはscheduleAtFixedRate()を使って難なく書けた。
しかし、このとき実行されるタスクが異常終了した場合に、定期的な実行を停止するように書きたかったのだがそこでちょっと引っかかった。
scheduleAtFixedRate()のドキュメントによると、
いずれかのタスク実行が例外に遭遇すると、後続の実行は抑制されます。
と書かれてある。
でも、scheduleAtFixedRate()に渡すタスクはRunnable。
Runnable.run()って例外スローできないよね?じゃ、どうやって例外スローするのさ。
と、ここまで考え至って思いついたのは、throws宣言を書かなくてもいいThrowableなクラス、Errorを投げればいいんじゃないか?ということ。
これでうまくいくか下のソースを書いて試してみた。
public class ScheduledAtFixedRateTest { public static void main(String[] args) { ScheduledExecutorService exSvc = Executors.newSingleThreadScheduledExecutor(); final ScheduledFuture<?> f = exSvc.scheduleAtFixedRate(new Runnable() { int execCnt = 0; @Override public void run() { System.out.println("[" + ++execCnt + "]" + new Date()); if(execCnt>=2) throw new Error(); //2回超えたらストップ } }, 2, 2, TimeUnit.SECONDS); //2秒毎に実行 exSvc.schedule(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return f.cancel(true); } }, 10, TimeUnit.SECONDS); //10秒後に停止 System.out.println("StartTime = " + new Date()); while(!f.isDone()) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } } exSvc.shutdown(); System.out.println("EndTime = " + new Date()); } }
結果:
StartTime = Tue Mar 23 07:09:03 JST 2010 [1]Tue Mar 23 07:09:05 JST 2010 [2]Tue Mar 23 07:09:07 JST 2010 EndTime = Tue Mar 23 07:09:07 JST 2010
10秒後の停止ではなく、2回目の実行でうまく停止した様子。
今回はこのやり方で書いてみるが、この使い方、正しいんだろうか?