ブログ アーカイブ

自己紹介

本ブログをご覧いただき、ありがとうございます。
株式会社ヒューマンインタラクティブテクノロジー(HIT)の技術グループ担当者です。

弊社ホームページ

2014年4月17日木曜日

[Java8]ParallelStream内で例外が発生した時スレッドは終了するのか?

どうも、トムです。

そろそろ世間(?)でも徐々にJava SE8の話題が増えてきた今日この頃
Web上を彷徨っているとチラッとこんな記事↓を見かけました。

もしもラムダの中で例外が発生したら(前編)
この記事の中でParallelStreamについての検証があるのですが
発生した例外は誰が受け取るのか?どこまで伝播するのか?
例外が発生したスレッド全てでcatch処理が行われるのか?
例外が発生しなかった他のスレッドはどのような影響を受けるのか?
そもそもちゃんと全部のスレッド処理が終了するのか?
皆さんも気になりますよね?
すごく気になりますね。

ParallelStream例外時のスレッドの動きを観察

引用記事の中ではParallelStream内で例外が発生した場合の結果として
幾つかのスレッドが残ったままになっているように見えますが
例外が発生するたびスレッドがどんどん増え続けるなんて事にならないのかちょっと確かめてみます。

コードは引用記事の内容を拝借しました。
parallelStreamで並列処理中に"ERROR"という文字列が出てきたらRuntimeExceptionをthrowするという処理を10回繰り返しスレッド数の変化を確認します。
        List<String> lines = Arrays.asList("A", "B", "C", "D", "ERROR", "F", "G", "ERROR", "I", "J");

        // 取り敢えず10回繰り返して様子見
        for (int i=0; i<10; i++) {
            System.out.println("スレッド数: " + String.valueOf(Thread.activeCount()));
            System.out.println("親スレッド Thread=" + Thread.currentThread().getId());
            Set<String> threads = new CopyOnWriteArraySet<>();
            try {
                lines.parallelStream().forEach(s -> {
                    System.out.println("=>開始[" + Thread.currentThread().getId() + "] 文字[" + s + "]" + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                    threads.add(String.valueOf(Thread.currentThread().getId()));
                    try {
                        Thread.sleep(100L);
                        if (s.equals("ERROR")) {
                            System.out.println("◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=" + Thread.currentThread().getId() + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                            System.out.println("<=エラー終了[" + Thread.currentThread().getId() + "] 文字[" + s + "]" + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                            threads.remove(String.valueOf(Thread.currentThread().getId()));
                            throw new RuntimeException();
                        }
                    } catch (InterruptedException e) {
                        System.out.println("InterruptedException in lambda. Thread=" + Thread.currentThread().getId() + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                    }
                    System.out.println("<=終了[" + Thread.currentThread().getId() + "] 文字[" + s + "]" + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                    threads.remove(String.valueOf(Thread.currentThread().getId()));
                });

            } catch (Exception ex) {
                System.out.println("◆◆◆◆ラムダの外で例外キャッチ◆◆◆◆ Thread=" + Thread.currentThread().getId() + ", スレッド数=" + String.valueOf(Thread.activeCount()));
                System.out.println("ラムダ内で終わっていないように見えるスレッド: [" + String.join(",", threads) + "]" + ", スレッド数=" + String.valueOf(Thread.activeCount()));
            }
            System.out.println("スレッド数: " + String.valueOf(Thread.activeCount()));
            System.out.println("----------------------------------------------------------------------");
        }


動かしてみると………
スレッド数: 2
親スレッド Thread=1
=>開始[1] 文字[G], スレッド数=5
=>開始[12] 文字[I], スレッド数=5
=>開始[11] 文字[C], スレッド数=5
=>開始[13] 文字[ERROR], スレッド数=5
<=終了[1] 文字[G], スレッド数=5
<=終了[11] 文字[C], スレッド数=5
=>開始[11] 文字[ERROR], スレッド数=5
<=終了[12] 文字[I], スレッド数=5
=>開始[12] 文字[J], スレッド数=5
◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=13, スレッド数=5
<=エラー終了[13] 文字[ERROR], スレッド数=5
=>開始[13] 文字[B], スレッド数=5
=>開始[1] 文字[F], スレッド数=5
◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=11, スレッド数=5
<=エラー終了[11] 文字[ERROR], スレッド数=5
=>開始[11] 文字[D], スレッド数=5
<=終了[1] 文字[F], スレッド数=5
◆◆◆◆ラムダの外で例外キャッチ◆◆◆◆ Thread=1, スレッド数=5
ラムダ内で終わっていないように見えるスレッド: [12,13,11], スレッド数=5
スレッド数: 5
----------------------------------------------------------------------
<=終了[12] 文字[J], スレッド数=5
<=終了[13] 文字[B], スレッド数=5
<=終了[11] 文字[D], スレッド数=5
【↑ここまで前回のラムダ処理のスレッド↑】 ※この部分だけ順番入れ替えています

スレッド数: 5
親スレッド Thread=1
=>開始[12] 文字[A], スレッド数=5
=>開始[1] 文字[G], スレッド数=5
=>開始[13] 文字[C], スレッド数=5
=>開始[11] 文字[B], スレッド数=5
<=終了[1] 文字[G], スレッド数=5
=>開始[1] 文字[F], スレッド数=5
<=終了[13] 文字[C], スレッド数=5
=>開始[13] 文字[ERROR], スレッド数=5
<=終了[12] 文字[A], スレッド数=5
=>開始[12] 文字[D], スレッド数=5
<=終了[11] 文字[B], スレッド数=5
=>開始[11] 文字[A], スレッド数=5
◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=13, スレッド数=5
<=エラー終了[13] 文字[ERROR], スレッド数=5
<=終了[1] 文字[F], スレッド数=5
=>開始[1] 文字[I], スレッド数=5
=>開始[13] 文字[ERROR], スレッド数=5
<=終了[12] 文字[D], スレッド数=5
<=終了[11] 文字[A], スレッド数=5
<=終了[1] 文字[I], スレッド数=5
◆◆◆◆ラムダの外で例外キャッチ◆◆◆◆ Thread=1, スレッド数=5
ラムダ内で終わっていないように見えるスレッド: [13], スレッド数=5
スレッド数: 5
----------------------------------------------------------------------
◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=13, スレッド数=5
<=エラー終了[13] 文字[ERROR], スレッド数=5
【↑ここまで前回のラムダ処理のスレッド↑】 ※この部分だけ順番入れ替えています

スレッド数: 5
親スレッド Thread=1
=>開始[11] 文字[C], スレッド数=5
=>開始[12] 文字[B], スレッド数=5
=>開始[1] 文字[G], スレッド数=5
=>開始[13] 文字[ERROR], スレッド数=5
<=終了[11] 文字[C], スレッド数=5
=>開始[11] 文字[I], スレッド数=5
<=終了[12] 文字[B], スレッド数=5
=>開始[12] 文字[A], スレッド数=5
<=終了[1] 文字[G], スレッド数=5
=>開始[1] 文字[F], スレッド数=5
◇◇◇◇ラムダ内で例外発生◇◇◇◇ Thread=13, スレッド数=5
<=エラー終了[13] 文字[ERROR], スレッド数=5
=>開始[13] 文字[D], スレッド数=5
<=終了[12] 文字[A], スレッド数=5
=>開始[12] 文字[ERROR], スレッド数=5
<=終了[11] 文字[I], スレッド数=5
<=終了[1] 文字[F], スレッド数=5
◆◆◆◆ラムダの外で例外キャッチ◆◆◆◆ Thread=1, スレッド数=5
ラムダ内で終わっていないように見えるスレッド: [13,12], スレッド数=5
スレッド数: 5
----------------------------------------------------------------------

~~~(中略)~~~

スレッド数: 5
----------------------------------------------------------------------
はい、スレッドは増えてはいないですね。
例外でスレッド数がもりもり増える事はないようです。
他に、気になる点は3点
  • ParallelStream内のスレッドでは親スレッドと同じスレッドも使用する…らしい
  • ParallelStream内で例外が発生しても正常処理のスレッドは継続する…らしい
  • ParallelStreamでの同時実行可能スレッド数は5スレッド…らしい
  • ※同時実行可能スレッド数に関してはJVMの設定や環境によるかもしれません

    う~ん……ここら辺はもっと検証してみないと何とも言えないですね。
    ただ、親スレッドと同じスレッドで並列処理が走るのは何とも奇妙な……



    サーバサイドアプリでの検証

    念のためサーバサイドで実装した場合にも同じ動きをするのか検証してみましょう。

    今回検証に利用したのはPlay Framework 2.2.2
    Play Frameworkは、J2EEを完全に排除し複雑なサーバサイドの構築やらTomcatのようなサーブレットコンテナを必要とせずにRubyのRailsやPHPのCakePHPのようにお手軽にMVCアーキテクチャを利用したアプリを作成できる生産性を重視するWebアプリ開発に特化した次世代フレームワークです。

    Play Frameworkの構築記事はまた次の機会という事で、さっそく検証してみましょう。


    何回か実行してみます。


    ~~~(中略)~~~

    サーバサイドでも特に動きに変わりはないようです。
    スレッド数に関しては安心ですが、ParallelStreamの例外時には正常処理のスレッドが残ってしまうので気を付けた方がよさそうです。
    やる人はいないと思いますがParallelStreamでDB操作とかは絶対やっちゃダメですね。

    2014年4月12日土曜日

    IE11で動かないFlash(Flex)を何とか動かしてみる

    どうも、トムです。

    Flex+BlazeDS+AMFの組み合わせで動作するシステムの構築をしていたおり
    どうもIE11限定でFlashPlayer側の挙動がおかしくメッセージングが正常に動作せず、どうしたものかと頭を抱えていました。
    ……が、どうもIE11側で互換設定を有効にすると正常に動作する事が最近分りましたので
    IE側の設定なしで何とか動かないものかと試作してみました。


    ちなみにIE11側の互換設定はこれ↓の事です。


    以降の説明を読むとわかりますが、HTTPヘッダの情報を書き換えるので、非推奨な方法です。
    あくまでも技術的に実現できたという形で捉えてください。
    本方法を採用し、発生した障害やトラブルに関して当ブログでは責任は負いません


    試作

    まずは、IEバージョン違い問題の応急処置でお約束となるドキュメントモードの変更を試作
    Apacheの応答ヘッダーにX-UA-Compatibleを仕込んでIE10以上のIEを強制的にIE10モードで表示させてみます。
    ・参考
    META スイッチを Apache に実装する
    <Location />
        Header set X-UA-Compatible "IE=10"
    </Location>
    

    Apacheを再起動して動作を確認してみると………動かない!

    仕方ないので次はIE11の互換モードを有効にしてheaderをのぞいてみます。
    IE11には開発者ツール(F12)という便利な機能が搭載されていますのでそれを利用します。

    確認手順は以下

    互換モード設定

    まずは適当なサイトを互換モードの対象に指定し


    管理者ツールのネットワークキャプチャ有効化

    F12を押して管理者ツールを起動。
    ネットワークタブを選択して、ネットワークのキャプチャを有効化

    互換表示対象のサイトへアクセス

    互換モードの対象のサイトへアクセスして、先頭のリクエストヘッダーを確認してみると

    むむむ……互換モードではドキュメントモードだけでなくUser-Agentの中身もIE7になっているようです。

    ご存知の方も多いと思いますがIE11では今までのIEとUser-Agentが大幅に変更されています。
    IE11 の互換性の変更点 | Internet Explorer 11 開発者向けガイド
    互換性 ("compatible") トークンとブラウザー ("MSIE") トークンが削除されました。 "like Gecko" トークンが追加されました (他のブラウザーとの一貫性のため)。 ブラウザーのバージョンが、新しいリビジョン ("rv") トークンによって報告されます。

    本来、IE11のUser-Agentは↓のように表示されます。
    Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
    

    上記のリクエストヘッダーではUser-Agentに"MSIE 7.0"の記述があるため、IE7のように振る舞っている事が分かります。

    ちなみに、管理者ツールのエミュレーションで現在のエミュレートモードを確認すると

    ドキュメントモードがIE7になっているのは分かりますが、User-Agentは別にIE7とは設定されていません。なんでやねん。



    まさかと思いつつ、ドキュメントモードは変更せずにUser-Agentだけ変更し、IE11で動かなかったシステムへアクセスしてみると


    ………動いた!

    システム内でIEのUser-Agentを見ているような箇所は存在しないため、FlashPlayer側でUser-Agentに"MSIE"が現れた場合はIEの処理を行うような分岐がされているようです。……推測ですが

    いろいろと納得いかない部分はありますが、ひとまず解決の糸口がつかめました。



    解決編

    Apacheのhttpd.confに以下を追加
    LoadModule headers_module modules/mod_headers.so
    <IfModule headers_module>
    
       Header edit User-Agent "(Trident/[\d]+\.[\d]+;).*rv:([1-9][\d]\.[\d]+)" "$1 MSIE $2"
    
    </IfModule>
    

    解決方法を簡潔に言ってしまうと
    ApacheでIE11のUser-Agentを無理やり"MSIE"を含めた文字列に変換するという結構バッドノウハウ的な手法で解決しています。

    例えばIE11から接続すると以下の様なリクエストヘッダのUser-Agentが
    Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
    

    Apacheを通して以下のように変換されます。
    Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MSIE 11.0) like Gecko
    

    動いてしまうとなんだかなー、という内容でした。


    ……FlashPlayerが早くIE11に対応してくれると良いですね。

    2014年4月2日水曜日

    Java8 Date and Time API 変換メモ

    トムです。
    少し話題は遅いですが、日本時間で3月19日にJava8が正式に公開されました。

    Java8では多くのAPIが追加され、概ね使いやすくなっているのですが
    新規に追加されたDate and Time APIに関しては今までの日付型と比べ使い方に癖があり使用の際に躓く方も多いと思います。
    今回はメモ書きとしてJava8のDate and Time APIについて動作確認した内容を書いていきます。




    型変換サンプル

    java.util.Date から型変換

    java.util.Date から java.time.ZonedDateTime に変換

            Date d1 = new Date();
            ZonedDateTime date2zd = d1.toInstant().atZone(ZoneId.systemDefault());
    


    java.util.Date から java.time.OffsetDateTime に変換

            Date d2 = new Date();
            // UTC
            OffsetDateTime date2odt1 = d2.toInstant().atOffset(ZoneOffset.UTC);
    
            // System offset
            OffsetDateTime date2odt2 = d2.toInstant().atOffset(ZoneId.systemDefault().getRules().getOffset(d2.toInstant()));
    
            // System offset (一度java.time.ZonedDateTimeに変換)
            OffsetDateTime date2odt3 = d2.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime();
    

    java.util.Date から java.time.OffsetTime に変換

            Date d3 = new Date();
            //UTC
            OffsetTime date2ot1 = d3.toInstant().atOffset(ZoneOffset.UTC).toOffsetTime();
    
            // System offset
            OffsetTime date2ot2 = d3.toInstant().atOffset(ZoneId.systemDefault().getRules().getOffset(d3.toInstant())).toOffsetTime();
    
            // System offset (一度java.time.ZonedDateTimeに変換)
            OffsetTime date2ot3 = d3.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime().toOffsetTime();
    

    java.util.Date から java.time.LocalDateTime に変換

            Date d4 = new Date();
            LocalDateTime date2ldt1 = d4.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            // または
            LocalDateTime date2ldt2 = LocalDateTime.from(d4.toInstant().atZone(ZoneId.systemDefault()));
    

    java.util.Date から java.time.LocalDate に変換

            Date d5 = new Date();
            LocalDate date2ld1 = d5.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            // または
            LocalDate date2ld2 = LocalDate.from(d5.toInstant().atZone(ZoneId.systemDefault()));
    

    java.util.Date から java.time.LocalTime に変換

            Date d6 = new Date();
            LocalTime date2lt1 = d6.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
            // または
            LocalTime date2lt2 = LocalTime.from(d6.toInstant().atZone(ZoneId.systemDefault()));
    

    java.util.Date から java.time.chrono.JapaneseDate に変換

            Date d7 = new Date();
            JapaneseDate date2jd = JapaneseDate.from(d7.toInstant().atZone(ZoneId.systemDefault()));
    




    java.time.ZonedDateTime から型変換

    java.time.ZonedDateTime から java.util.Date に変換

            ZonedDateTime zd1 = ZonedDateTime.now(ZoneId.systemDefault());
            Date zd2date = Date.from(zd1.toInstant());
    

    java.time.ZonedDateTime から java.time.LocalDateTime に変換

            ZonedDateTime zd2 = ZonedDateTime.now(ZoneId.systemDefault());
    
            LocalDateTime zd2ldt1 = zd2.toLocalDateTime();
            // または
            LocalDateTime zd2ldt2 = LocalDateTime.from(zd2);
    

    java.time.ZonedDateTime から java.time.LocalDate に変換

            ZonedDateTime zd3 = ZonedDateTime.now(ZoneId.systemDefault());
    
            LocalDate zd2ld1 = zd3.toLocalDate();
            // または
            LocalDate zd2ld2 = LocalDate.from(zd3);
    

    java.time.ZonedDateTime から java.time.LocalTime に変換

            ZonedDateTime zd4 = ZonedDateTime.now(ZoneId.systemDefault());
    
            LocalTime zd2lt1 = zd4.toLocalTime();
            // または
            LocalTime zd2lt2 = LocalTime.from(zd4);
    

    java.time.ZonedDateTime から java.time.OffsetDateTime に変換

            ZonedDateTime zd5 = ZonedDateTime.now(ZoneId.systemDefault());
    
            OffsetDateTime zd2odt1 = zd5.toOffsetDateTime();
            // または
            OffsetDateTime zd2odt2 = OffsetDateTime.from(zd5);
    

    java.time.ZonedDateTime から java.time.OffsetTime に変換

            ZonedDateTime zd6 = ZonedDateTime.now(ZoneId.systemDefault());
            OffsetTime zd2ot = OffsetTime.from(zd6);
    

    java.time.ZonedDateTime から java.time.chrono.JapaneseDate に変換

            ZonedDateTime zd7 = ZonedDateTime.now(ZoneId.systemDefault());
            JapaneseDate zd2jd = JapaneseDate.from(zd7);
    




    文字列フォーマット変換サンプル



    java.time.ZonedDateTime の文字列変換

            // System.out.println()のメソッド参照
            Consumer<Object> pl = System.out::println;
    
           //----------------------------------------------------------------------------------------------
           // ZonedDateTime methods
           //----------------------------------------------------------------------------------------------
            ZonedDateTime datetime = ZonedDateTime.now(ZoneId.systemDefault()); // Asia/Tokyo
            pl.accept("[getYear] 年");
            pl.accept(datetime.getYear());
            // 2014
            
            pl.accept("[getMonth] 月(Month型)");
            pl.accept(datetime.getMonth().name());
            // APRIL
            
            pl.accept("[getMonthValue] 月");
            pl.accept(datetime.getMonthValue());
            // 4
            
            pl.accept("[getDayOfMonth] 日");
            pl.accept(datetime.getDayOfMonth());
            // 2
    
            pl.accept("[getDayOfWeek] 曜日(DayOfWeek型)");
            pl.accept(datetime.getDayOfWeek().name());
            // WEDNESDAY
    
            pl.accept("[getDayOfYear] 1/1からの経過日");
            pl.accept(datetime.getDayOfYear());
            // 92
    
            pl.accept("[getHour] 時");
            pl.accept(datetime.getHour());
            // 19
    
            pl.accept("[getMinute] 分");
            pl.accept(datetime.getMinute());
            // 37
    
            pl.accept("[getSecond] 秒");
            pl.accept(datetime.getSecond());
            // 15
    
            pl.accept("[getMillis] ミリ秒");
            pl.accept(datetime.get(ChronoField.MILLI_OF_SECOND));
            // 798
    
            pl.accept("[getMicros] マイクロ秒");
            pl.accept(datetime.get(ChronoField.MICRO_OF_SECOND));
            // 798000
    
            pl.accept("[getNano] ナノ秒");
            pl.accept(datetime.getNano());
            // 798000000
    
            pl.accept("[The aligned day-of-week within a month] 月初から月末まで1からカウントアップして8日で1にリセットされる1~7の整数");
            pl.accept(datetime.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH));
            // 2
    
            pl.accept("[The aligned day-of-week within a year] 年初から年末まで1からカウントアップして8日で1にリセットされる1~7の整数");
            pl.accept(datetime.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR));
            // 1
    
            pl.accept("[The clock-hour-of-am-pm] AM/PMを考慮した時間");
            pl.accept(datetime.get(ChronoField.CLOCK_HOUR_OF_AMPM));
            // 7
    
            pl.accept("[The am-pm-of-day] 0=AM/1=PM");
            pl.accept(datetime.get(ChronoField.AMPM_OF_DAY));
            // 1
    
            pl.accept("[Era] 0=紀元前(B.C.)/1=西暦(A.D.)");
            pl.accept(datetime.get(ChronoField.ERA));
            // 1
    
            pl.accept("[InstantSeconds] エポック秒(Unix時間/秒) 1970-01-01T00:00Zからの経過秒");
            pl.accept(datetime.toEpochSecond());
            // 1396435035
    
            pl.accept("[InstantSeconds] エポック日(Unix時間/日) 1970-01-01からの経過日");
            pl.accept(datetime.getLong(ChronoField.EPOCH_DAY));
            // 16162
    
    
           //----------------------------------------------------------------------------------------------
           // format use DateTimeFormatter Constant
           //----------------------------------------------------------------------------------------------
            pl.accept("[toString]");
            pl.accept(datetime.toString());
            // 2014-04-02T19:37:15.798+09:00[Asia/Tokyo]
            
            pl.accept("[BASIC_ISO_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.BASIC_ISO_DATE));
            // 20140402+0900
            
            pl.accept("[ISO_LOCAL_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_LOCAL_DATE));
            // 2014-04-02
    
            pl.accept("[ISO_OFFSET_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_OFFSET_DATE));
            // 2014-04-02+09:00
            
            pl.accept("[ISO_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_DATE));
            // 2014-04-02+09:00
    
            pl.accept("[ISO_LOCAL_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_LOCAL_TIME));
            // 19:37:15.798
            
            pl.accept("[ISO_OFFSET_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_OFFSET_TIME));
            // 19:37:15.798+09:00
    
            pl.accept("[ISO_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_TIME));
            // 19:37:15.798+09:00
    
            pl.accept("[ISO_LOCAL_DATE_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
            // 2014-04-02T19:37:15.798
    
            pl.accept("[ISO_OFFSET_DATE_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            // 2014-04-02T19:37:15.798+09:00
    
            pl.accept("[ISO_ZONED_DATE_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
            // 2014-04-02T19:37:15.798+09:00[Asia/Tokyo]
    
            pl.accept("[ISO_DATE_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T19:37:15.798+09:00[Asia/Tokyo]
    
            pl.accept("[ISO_ORDINAL_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_ORDINAL_DATE));
            // 2014-092+09:00
    
            pl.accept("[ISO_WEEK_DATE]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_WEEK_DATE));
            // 2014-W14-3+09:00
    
            pl.accept("[ISO_INSTANT]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_INSTANT));
            // 2014-04-02T10:37:15.798Z
    
            pl.accept("[RFC_1123_DATE_TIME]");
            pl.accept(datetime.format(DateTimeFormatter.RFC_1123_DATE_TIME));
            // Wed, 2 Apr 2014 19:37:15 +0900
    
    
           //----------------------------------------------------------------------------------------------
           // format use DateTimeFormatter ofLocalizedDate()
           //----------------------------------------------------------------------------------------------
            pl.accept("[ofLocalizedDate] [DateStyle=FULL]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));
            // 2014年4月2日
    
            pl.accept("[ofLocalizedDate] [DateStyle=MEDIUM]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
            // 2014/04/02
    
            pl.accept("[ofLocalizedDate] [DateStyle=SHORT]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));
            // 14/04/02
    
            pl.accept("[ofLocalizedDate] [DateStyle=LONG]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));
            // 2014/04/02
    
    
           //----------------------------------------------------------------------------------------------
           // format use DateTimeFormatter ofLocalizedTime()
           //----------------------------------------------------------------------------------------------
            pl.accept("[ofLocalizedTime] [TimeStyle=FULL]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)));
            // 19時37分15秒 JST
    
            pl.accept("[ofLocalizedTime] [TimeStyle=MEDIUM]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)));
            // 19:37:15
    
            pl.accept("[ofLocalizedTime] [TimeStyle=SHORT]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
            // 19:37
    
            pl.accept("[ofLocalizedTime] [TimeStyle=LONG]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG)));
            // 19:37:15 JST
    
    
           //----------------------------------------------------------------------------------------------
           // format use DateTimeFormatter ofLocalizedDateTime()
           //----------------------------------------------------------------------------------------------
            pl.accept("[ofLocalizedDateTime] [DateTimeStyle=FULL]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)));
            // 2014年4月2日 19時37分15秒 JST
    
            pl.accept("[ofLocalizedDateTime] [DateTimeStyle=MEDIUM]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
            // 2014/04/02 19:37:15
    
            pl.accept("[ofLocalizedDateTime] [DateTimeStyle=SHORT]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)));
            // 14/04/02 19:37
    
            pl.accept("[ofLocalizedDateTime] [DateTimeStyle=LONG]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)));
            // 2014/04/02 19:37:15 JST
    
    
            pl.accept("[ofLocalizedDateTime] [DateStyle=FULL] [TimeStyle=FULL]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL)));
            // 2014年4月2日 19時37分15秒 JST
    
            pl.accept("[ofLocalizedDateTime] [DateStyle=FULL] [TimeStyle=MEDIUM]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM)));
            // 2014年4月2日 19:37:15
    
            pl.accept("[ofLocalizedDateTime] [DateStyle=FULL] [TimeStyle=SHORT]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.SHORT)));
            // 2014年4月2日 19:37
    
            pl.accept("[ofLocalizedDateTime] [DateStyle=LONG] [TimeStyle=MEDIUM]");
            pl.accept(datetime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.MEDIUM)));
            // 2014/04/02 19:37:15
    
    
           //----------------------------------------------------------------------------------------------
           // format use DateTimeFormatter ofPattern()
           //----------------------------------------------------------------------------------------------
            pl.accept("[ofPattern] era 暦:型により異なる");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("G")));
            // 西暦
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("GGGG")));
            // 西暦
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("GGGGG")));
            // A
    
            // java.time.chrono.JapaneseDate の場合
            pl.accept(JapaneseDate.now().format(DateTimeFormatter.ofPattern("G")));
            // 平成
            pl.accept(JapaneseDate.now().format(DateTimeFormatter.ofPattern("GGGG")));
            // 平成
            pl.accept(JapaneseDate.now().format(DateTimeFormatter.ofPattern("GGGGG")));
            // H
    
            pl.accept("[ofPattern] year 年");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("uuuu")));
            // 2014
    
            // java.time.chrono.JapaneseDate の場合
            pl.accept(JapaneseDate.now().format(DateTimeFormatter.ofPattern("uuuu")));
            // 2014
    
            pl.accept("[ofPattern] year-of-era (暦を考慮した)年");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("yyyy")));
            // 2014
    
            // java.time.chrono.JapaneseDate の場合
            pl.accept(JapaneseDate.now().format(DateTimeFormatter.ofPattern("yy")));
            // 26
    
            pl.accept("[ofPattern] day-of-year 1/1からの経過日数");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("D")));
            // 92
    
            pl.accept("[ofPattern] month-of-year 月");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("M")));
            // 4
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("MM")));
            // 04
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("MMM")));
            // 4
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("MMMM")));
            // 4月
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("MMMMM")));
            // A
    
            pl.accept("[ofPattern] day-of-month 日");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("d")));
            // 2
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("dd")));
            // 02
    
            pl.accept("[ofPattern] quarter-of-year クオーター");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("Q")));
            // 2
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("QQ")));
            // 02
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("QQQ")));
            // Q2
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("QQQQ")));
            // 第2四半期
    
            pl.accept("[ofPattern] week-based-year 週基準年");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("YYYY")));
            // 2014
    
            // 例えば2014/1/1は水曜日なので2013/12/31は2014に組み込まれます
            pl.accept(ZonedDateTime.of(2013,12,31,0,0,0,0,ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("YYYY")));
            // 2014
    
            pl.accept("[ofPattern] week-of-week-based-year 年を通しての週基準年の週 (1~52 or 53)");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("w")));
            // 14
    
            // 2013/12/31(火)は2014年の1週目
            pl.accept(ZonedDateTime.of(2013,12,31,0,0,0,0,ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("w")));
            // 1
    
            // 2013/12/28(日)は2013年の52週目
            pl.accept(ZonedDateTime.of(2013,12,28,0,0,0,0,ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("w")));
            // 52
    
            pl.accept("[ofPattern] week-of-month 週");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("W")));
            // 1
    
            pl.accept("[ofPattern] day-of-week 曜日");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("E")));
            // 水
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("EE")));
            // 水
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("EEE")));
            // 水
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("EEEE")));
            // 水曜日
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("EEEEE")));
            // 水
    
            pl.accept("[ofPattern] localized day-of-week");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("e")));
            // 4
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ee")));
            // 04
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("eee")));
            // 水
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("eeee")));
            // 水曜日
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("eeeee")));
            // 水
    
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("c")));
            // 4
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ccc")));
            // 水
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("cccc")));
            // 水曜日
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ccccc")));
            // 3
    
            pl.accept("[ofPattern] week-of-month");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("F")));
            // 2
    
            pl.accept("[ofPattern] am-pm-of-day");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("a")));
            // 午後
    
            pl.accept("[ofPattern] clock-hour-of-am-pm (1-12)");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("h")));
            // 9
    
            pl.accept("[ofPattern] hour-of-am-pm (0-11)");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("K")));
            // 9
    
            pl.accept("[ofPattern] clock-hour-of-am-pm (1-24)");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("k")));
            // 21
    
            pl.accept("[ofPattern] hour-of-day (0-23) 時");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("H")));
            // 21
    
            pl.accept("[ofPattern] hour-of-day (00-23) 時");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("HH")));
            // 21
    
            pl.accept("[ofPattern] minute-of-hour 分");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("mm")));
            // 20
    
            pl.accept("[ofPattern] second-of-minute 秒");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ss")));
            // 58
    
            pl.accept("[ofPattern] fraction-of-second ミリ秒");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("SSS")));
            // 094
    
            pl.accept("[ofPattern] nano-of-second ナノ秒");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("n")));
            // 94000000
    
            pl.accept("[ofPattern] milli-of-day 1日の経過ミリ秒");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("A")));
            // 76858094
    
            pl.accept("[ofPattern] nano-of-day 1日の経過ナノ秒");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("N")));
            // 76858094000000
    
            pl.accept("[ofPattern] time-zone ID タイムゾーンID ZonedDateTime以外では使用不可");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("VV")));
            // Asia/Tokyo
    
            pl.accept("[ofPattern] time-zone name タイムゾーン名 ZonedDateTime以外では使用不可");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("z")));
            // JST
    
            pl.accept("[ofPattern] localized zone-offset");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("0")));
            // 0
    
            pl.accept("[ofPattern] zone-offset オフセット UTC(+0000)の場合 +0000の代わりにZを表示");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("XXXX")));
            // +0900
    
            // UTCの場合
            pl.accept(ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.ofPattern("XXXX")));
            // Z
    
            pl.accept("[ofPattern] zone-offset オフセット");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("xxxx")));
            // +0900
    
            // UTCの場合
            pl.accept(ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.ofPattern("xxxx")));
            // +0000
    
            pl.accept("[ofPattern] zone-offset オフセット");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("Z")));
            // +0900
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ZZ")));
            // +0900
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ZZZ")));
            // +0900
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("ZZZZ")));
            // GMT+09:00
    
            pl.accept("[ofPattern] escape for text / [] # {} 次の文字列はシングルクォートでエスケープが必要です(/[]#{})");
            pl.accept(datetime.format(DateTimeFormatter.ofPattern("'['yyyy'/'MM'/'dd']' '{'HH'#'mm'#'ss'}'")));
            // [2014/04/02] {21#20#58}
    
            pl.accept("[withZoneSameInstant] タイムゾーン変換");
            pl.accept(datetime.withZoneSameInstant(ZoneId.of("America/Los_Angeles")).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T05:20:58.094-07:00[America/Los_Angeles]
    
    




    java.time.chrono.JapaneseDate の文字列変換

            Consumer<Object> pl = System.out::println;
    
            JapaneseDate jd = JapaneseDate.now();
            pl.accept("[Japanese Era Name List] 和暦名一覧");
            jd.getChronology().eras().forEach(pl);
            // Meiji
            // Taisho
            // Showa
            // Heisei
    
            pl.accept("[toString]");
            pl.accept(jd.toString());
            // Japanese Heisei 26-04-02
    
            pl.accept("[Japanese Era Name] 和暦(名)");
            pl.accept(jd.getEra());
            // Heisei
    
            pl.accept("[Japanese Era Year] 和暦(年)");
            pl.accept(jd.get(ChronoField.YEAR_OF_ERA));
            // 26
    
            pl.accept("[Era] -1=明治/0=大正/1=昭和/2=平成");
            pl.accept(jd.get(ChronoField.ERA));
            // 2
    
            pl.accept("[InstantSeconds] エポック日(Unix時間/日) 1970-01-01からの経過日");
            pl.accept(jd.getLong(ChronoField.EPOCH_DAY));
            // 16162
    
            pl.accept("[ofPattern]");
            pl.accept(jd.format(DateTimeFormatter.ofPattern("Gyy年 MM月dd日")));
            // 平成26年 04月02日
    
    




    日時操作サンプル



    java.time.ZonedDateTime の日時操作

            Consumer<Object> pl = System.out::println;
    
           //----------------------------------------------------------------------------------------------
           // 日時操作
           //----------------------------------------------------------------------------------------------
            pl.accept("[original]");
            pl.accept(datetime.format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Years] +1年");
            pl.accept(datetime.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2015-04-02T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Months] +1ヶ月");
            pl.accept(datetime.plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-05-02T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Days] +1日");
            pl.accept(datetime.plusDays(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-03T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Hours] +1時間");
            pl.accept(datetime.plusHours(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T22:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Minutes] +1分");
            pl.accept(datetime.plusMinutes(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:21:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Seconds] +1秒");
            pl.accept(datetime.plusSeconds(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:20:59.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Millis] +1ミリ秒");
            pl.accept(datetime.plus(1, ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:20:58.095+09:00[Asia/Tokyo]
    
            pl.accept("[+1Micros] +1マイクロ秒");
            pl.accept(datetime.plus(1, ChronoUnit.MICROS).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:20:58.094001+09:00[Asia/Tokyo]
    
            pl.accept("[+1Nanos] +1ナノ秒");
            pl.accept(datetime.plusNanos(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-02T21:20:58.094000001+09:00[Asia/Tokyo]
    
            pl.accept("[+1Weeks] +1週");
            pl.accept(datetime.plusWeeks(1).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-09T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1HalfDays] +12時間");
            pl.accept(datetime.plus(1, ChronoUnit.HALF_DAYS).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2014-04-03T09:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Decades] +10年");
            pl.accept(datetime.plus(1, ChronoUnit.DECADES).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2024-04-02T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Centuries] +100年");
            pl.accept(datetime.plus(1, ChronoUnit.CENTURIES).format(DateTimeFormatter.ISO_DATE_TIME));
            // 2114-04-02T21:20:58.094+09:00[Asia/Tokyo]
    
            pl.accept("[+1Millennium] +1000年");
            pl.accept(datetime.plus(1, ChronoUnit.MILLENNIA).format(DateTimeFormatter.ISO_DATE_TIME));
            // 3014-04-02T21:20:58.094+09:00[Asia/Tokyo]
    
    

    他に有用なものがあり次第追加していきます。

    2014年4月1日火曜日

    MyBatisで遊んでみる(3) 第三章 MyBatis実装サンプル

    どうも、トムです。
    前準備は終わりましたので第三章ではMyBatisを実装してみます。


    第三章 MyBatis実装サンプル

    必要なライブラリは第二章を参照してください。
    本稿では第二章までの構築が完了している前提で進みます。

    前回はmybatis-generatorで以下のファイルが自動生成されました。


    mybatis-config.xmlの設定

    まずはMyBatisを利用するために設定ファイルを記述します。
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <settings>
        <!-- mybatisログ出力用 -->
        <setting name="logImpl" value="LOG4J"/>
      </settings>
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <!-- データソース -->
          <dataSource type="POOLED">
            <property name="driver" value="org.postgresql.Driver"/>
            <property name="url" value="jdbc:postgresql://192.168.100.100:5432/test"/>
            <property name="username" value="mybatisuser"/>
            <property name="password" value="password"/>
          </dataSource>
        </environment>
      </environments>
      <mappers>
        <!-- Mapperファイルのパスを記述 -->
        <mapper resource="jp/hit/sample/mybatis/mapper/EmpMapper.xml"/>
        <mapper resource="jp/hit/sample/mybatis/mapper/DeptMapper.xml"/>
      </mappers>
    </configuration>
    

    今回コンソールでMyBatisが発行したSQLを確認したいのでsetting要素でLog4jでログ出力出来るように設定しています。
    ロギング自体は指定しだいでLog4jの他にSLF4JやLog4j2なども使用可能です。
    詳細は公式のリファレンスを参照してください。

    dataSource要素ではJDBCパラメータ、mappers要素では実際のMapperのパスを記載します。



    log4j.xmlの設定

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" >
    
      <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
         <param name="Target" value="System.out" />
         <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%c{1} - %m%n" />
         </layout>
      </appender>
    
      <!-- SQLログを出力 -->
      <logger name="jp.hit.sample.mybatis.client" additivity="false">
        <level value="debug"/>
        <appender-ref ref="stdout"/>
      </logger>
      <!-- MyBatisSampleのデバッグログを出力 -->
      <logger name="jp.hit.sample.mybatis.logic" additivity="false">
        <level value="debug"/>
        <appender-ref ref="stdout"/>
      </logger>
      <root>
        <level value="info"/>
        <appender-ref ref="stdout"/>
      </root>
    </log4j:configuration>
    
    MyBatisが発行するSQLを確認するためにJava Client(jp.hit.sample.mybatis.client)以下のログレベルをDEBUGに設定してあります。




    セッションの生成

    MyBatisのセッション生成は次の実装で実現できます。
        package jp.hit.sample.mybatis.util;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.apache.log4j.Logger;
    
    public class SessionUtil {
        private static Logger LOG = Logger.getLogger(SessionUtil.class);
        private static final String RESOURCE = "mybatis-config.xml";
    
        private SessionUtil() {
        }
    
        public static SqlSession createSession() {
            SqlSession session = null;
            try {
                InputStream is = Resources.getResourceAsStream(RESOURCE);
                SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
                session = ssf.openSession(false);
            } catch (IOException e) {
                LOG.error(e.getMessage(), e);
            }
            return session;
        }
    }
    
    また、SqlSessionFactoryには以下6つのopenSessionメソッドが用意されており、用途に応じてセッションの生成を行う事が可能です。
    SqlSession openSession()
    SqlSession openSession(boolean autoCommit)
    SqlSession openSession(Connection connection)
    SqlSession openSession(TransactionIsolationLevel level)
    SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
    SqlSession openSession(ExecutorType execType)
    SqlSession openSession(ExecutorType execType, boolean autoCommit)
    SqlSession openSession(ExecutorType execType, Connection connection)
    
    今回のように引数を指定せずにセッションを生成した場合はautoCommit=false(自動コミット無効)の状態でセッションが生成されます。
    JDBCのトランザクション分離レベルを指定してセッションを生成したい場合は以下のように記述します。
    openSession(TransactionIsolationLevel.NONE); // トランザクション無効
    openSession(TransactionIsolationLevel.READ_UNCOMMITTED);  // READ UNCOMMITTED (ダーティリード有、非再現リード有、ファントムリード有)
    openSession(TransactionIsolationLevel.READ_COMMITTED); // READ COMMITTED (ダーティリード無、非再現リード有、ファントムリード有)
    openSession(TransactionIsolationLevel.REPEATABLE_READ); // REPEATABLE READ (ダーティリード無、非再現リード無、ファントムリード有)
    openSession(TransactionIsolationLevel.SERIALIZABLE); // SERIALIZABLE (ダーティリード無、非再現リード無、ファントムリード無)
    
    SQLステートメントの実行モードを指定したい場合は以下のように記述します。
    openSession(ExecutorType.SIMPLE); // デフォルト ステートメント実行のたびにPreparedStatementを作成します。
    openSession(ExecutorType.REUSE); // PreparedStatementを再利用します。
    openSession(ExecutorType.BATCH); // 全ての更新ステートメントをバッチ処理します。
    
    TransactionIsolationLevelやExecutorTypeはDBやMyBatisのバージョンにより挙動が変わるので使用する際は注意が必要です。
    例えば、postgreSQLの場合はREAD_UNCOMMITTEDを指定してもダーティリードは行われません。




    SQLの発行

    次に生成したセッションを利用して実際にMapperファイルに記載されたSQLを発行してみます。
    MyBatisを利用したSQL発行は非常にシンプルに実装できます。
        private static Logger LOG = Logger.getLogger(MyBatisSample.class);
    
        private static EmpMapper empMapper;
        private static DeptMapper deptMapper;
    
        public static void main(String[] args) {
            // セッション作成
            SqlSession session = SessionUtil.createSession();
            // Mapperを取得
            empMapper = session.getMapper(EmpMapper.class);
            deptMapper = session.getMapper(DeptMapper.class);
    
            try {
                // EmpMapperに自動生成されたselectByPrimaryKeyクエリを利用したPK指定でのEmpテーブルへのselect発行
                EmpKey key = new EmpKey();
                key.setId(1); // id = 1
                key.setDeptId(1); // dept_id = 1
                LOG.debug("【SQL発行】jp.hit.sample.mybatis.client.EmpMapper.selectByPrimaryKey id = 1 and dept_id = 1");
                Emp emp = empMapper.selectByPrimaryKey(key);
                printEmp(emp); // 取得したEmpデータ出力
    
    
                // EmpMapperに自動生成されたExampleMapperを使用したselect発行
                EmpExample param = new EmpExample();
                List<string> names = Arrays.asList("Bravo", "Charlie", "HIT tom");
                param.createCriteria().andNameIn(names); // IN ('Bravo','Charlie','HIT tom')
                param.setOrderByClause("dept_id desc"); // Order by dept_id desc
                LOG.debug("【SQL発行】selectByExample in ('Bravo','Charlie','HIT tom') order by dept_id desc");
                List<emp> empList = empMapper.selectByExample(param);
                printEmp(emp); // 取得したEmpデータ出力
    
    
                // Insert
                Emp insertEntity = new Emp();
                insertEntity.setId(5);
                insertEntity.setName("HIT tom");
                insertEntity.setDeptId(2);
                insertEntity.setTel("03-5225-0530");
                LOG.debug("【SQL発行】Insert Emp");
                empMapper.insert(insertEntity);
    
    
                // Insert結果取得
                empList = empMapper.selectByExample(param);
                LOG.debug("【SQL発行】selectByExample in ('Bravo','Charlie','HIT tom') order by dept_id desc");
                printEmp(emp); // 取得したEmpデータ出力
    
            } finally {
                // 実行前の状態にロールバックする
                session.rollback();
                // セッションを閉じる
                session.close();
            }
        }
    
    
        /**
         * Empの中身をダンプする
         * @param empList
         */
        private static void printEmp(List<emp> empList) {
            for (Emp emp : empList) {
                printEmp(emp);
            }
        }
    
        private static void printEmp(Emp emp) {
            if (emp != null) {
                LOG.debug("【Result】 " + "id:" + emp.getId() + ", Name:" + emp.getName() + ", Dept:"
                        + String.valueOf(emp.getDeptId()) + ", Tel:" + emp.getTel());
            }
        }
    

    サンプル実行前のテーブルデータは以下の通り
    select * from my.dept;
     id |    name
    ----+------------
      1 | Foundation
      2 | Sales
      3 | System
    
    select * from my.emp;
     id | dept_id |  name   |     tel
    ----+---------+---------+--------------
      1 |       1 | Alfa    | 111-111-1111
      2 |       1 | Aileen  | 123-123-1234
      3 |       3 | Bravo   | 222-222-2222
      4 |       2 | Charlie | 333-333-3333
    

    サンプルを実行すると以下の結果が得られます。
    MyBatisSample - 【SQL発行】jp.hit.sample.mybatis.client.EmpMapper.selectByPrimaryKey id = 1 and dept_id = 1
    selectByPrimaryKey - ooo Using Connection [org.postgresql.jdbc4.Jdbc4Connection@7bfb4d34]
    selectByPrimaryKey - ==>  Preparing: select id, dept_id, name, tel from my.emp where id = ? and dept_id = ? 
    selectByPrimaryKey - ==> Parameters: 1(Integer), 1(Integer)
    selectByPrimaryKey - <==      Total: 1
    MyBatisSample - 【Result】 id:1, Name:Alfa, Dept:1, Tel:111-111-1111
    MyBatisSample - 【SQL発行】selectByExample in ('Bravo','Charlie','HIT tom') order by dept_id desc
    selectByExample - ooo Using Connection [org.postgresql.jdbc4.Jdbc4Connection@7bfb4d34]
    selectByExample - ==>  Preparing: select 'false' as QUERYID, id, dept_id, name, tel from my.emp WHERE ( name in ( ? , ? , ? ) ) order by dept_id desc 
    selectByExample - ==> Parameters: Bravo(String), Charlie(String), HIT tom(String)
    selectByExample - <==      Total: 2
    MyBatisSample - 【Result】 id:3, Name:Bravo, Dept:3, Tel:222-222-2222
    MyBatisSample - 【Result】 id:4, Name:Charlie, Dept:2, Tel:333-333-3333
    MyBatisSample - 【SQL発行】Insert Emp
    insert - ooo Using Connection [org.postgresql.jdbc4.Jdbc4Connection@7bfb4d34]
    insert - ==>  Preparing: insert into my.emp (id, dept_id, name, tel) values (?, ?, ?, ?) 
    insert - ==> Parameters: 5(Integer), 2(Integer), HIT tom(String), 03-5225-0530(String)
    insert - <==    Updates: 1
    MyBatisSample - 【SQL発行】selectByExample in ('Bravo','Charlie','HIT tom') order by dept_id desc
    selectByExample - ooo Using Connection [org.postgresql.jdbc4.Jdbc4Connection@7bfb4d34]
    selectByExample - ==>  Preparing: select 'false' as QUERYID, id, dept_id, name, tel from my.emp WHERE ( name in ( ? , ? , ? ) ) order by dept_id desc 
    selectByExample - ==> Parameters: Bravo(String), Charlie(String), HIT tom(String)
    selectByExample - <==      Total: 3
    MyBatisSample - 【Result】 id:3, Name:Bravo, Dept:3, Tel:222-222-2222
    MyBatisSample - 【Result】 id:4, Name:Charlie, Dept:2, Tel:333-333-3333
    MyBatisSample - 【Result】 id:5, Name:HIT tom, Dept:2, Tel:03-5225-0530
    

    サンプル実行後もロールバックが正常に働きテーブルデータが変化しない事が確認出来ます。
    select * from my.dept;
     id |    name
    ----+------------
      1 | Foundation
      2 | Sales
      3 | System
    
    select * from my.emp;
     id | dept_id |  name   |     tel
    ----+---------+---------+--------------
      1 |       1 | Alfa    | 111-111-1111
      2 |       1 | Aileen  | 123-123-1234
      3 |       3 | Bravo   | 222-222-2222
      4 |       2 | Charlie | 333-333-3333
    





    任意のMapper作成

    自動生成されたMapperの利用を確認出来たら次に新規にMapperファイルを作成して任意のSQLを発行させてみましょう。

    まず、Mapperファイルと同一のパッケージ内に任意のMapper(今回はEmpDeptSqlMap.xml)を作成します。

    MyBatisのマッピングは以下の基準で紐付けが行われているのですが、今回は分かりやすいように敢えてMapperの名前を「~Mapper」ではなく「SqlMap」に変えています。
    • Mapperのinterfaceクラス名 = Mapperファイルのnamespace
    • Mapperのinterfaceクラスのメソッド名 = MapperファイルのSQLID
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="jp.hit.sample.mybatis.client.EmpDeptSqlMap" >
      <resultMap id="EmpDeptResultMap" type="jp.hit.sample.mybatis.entity.EmpDept" >
        <id column="emp_id" property="empId" jdbcType="INTEGER" />
        <id column="dept_id" property="deptId" jdbcType="INTEGER" />
        <id column="dept_name" property="deptName" jdbcType="VARCHAR" />
        <result column="emp_name" property="empName" jdbcType="VARCHAR" />
        <result column="tel" property="tel" jdbcType="VARCHAR" />
      </resultMap>
    
      <!--
        resultMapを定義せずにresultTypeを指定し
        <select id="selectEmpJoinDept" resultType="jp.hit.sample.mybatis.entity.EmpDept" parameterType="jp.hit.sample.mybatis.entity.EmpDept">
        のように直接Entityクラスを戻り値に指定することも可能ですが
        内部的に余計なマッピング処理が発生する為、resultMapでjdbcTypeを定義することが推奨されています。
       -->
      <select id="selectEmpJoinDept" resultMap="EmpDeptResultMap" parameterType="jp.hit.sample.mybatis.entity.EmpDept">
        SELECT
             emp.id      AS emp_id
            ,emp.dept_id AS dept_id
            ,dept.name   AS dept_name
            ,emp.name    AS emp_name
            ,emp.tel     AS tel
        FROM my.emp emp
        LEFT JOIN my.dept dept ON emp.dept_id = dept.id
    
        <!--
             <where> 要素は、内包するタグのどれかが結果を返すときだけ "WHERE" を挿入します。
             更に、内包するタグから返された結果が "AND" または "OR" で始まっていた場合はこれを削除します。
             以下の例の場合 empId、deptId、empName、deptName、tel 全てがnullの場合 where要素が削除されます。
         -->
        <where>
          <if test="empId != null">
            AND emp.id = #{empId,jdbcType=INTEGER}
          </if>
          <if test="deptId != null">
            AND emp.dept_id = #{deptId,jdbcType=INTEGER}
          </if>
          <if test="empName != null">
            AND emp.name LIKE #{empName,jdbcType=VARCHAR}
          </if>
          <if test="deptName != null">
            AND dept.name LIKE #{deptName,jdbcType=VARCHAR}
          </if>
          <if test="tel != null">
            AND emp.tel = #{tel,jdbcType=VARCHAR}
          </if>
        </where>
        ORDER BY emp.id
      </select>
    
    </mapper>
    
    MyBatisによる動的SQLの記法については公式リファレンスを参照してください。
    Mapperファイルには静的SQLをそのまま記述することも可能です。


    次にmybatis-config.xmlにMapperファイルのパスを追記します。
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <settings>
        <!-- mybatisログ出力用 -->
        <setting name="logImpl" value="LOG4J"/>
      </settings>
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <!-- データソース -->
          <dataSource type="POOLED">
            <property name="driver" value="org.postgresql.Driver"/>
            <property name="url" value="jdbc:postgresql://192.168.245.131:5432/test"/>
            <property name="username" value="mybatisuser"/>
            <property name="password" value="password"/>
          </dataSource>
        </environment>
      </environments>
      <mappers>
        <!-- Mapperファイルのパスを記述 -->
        <mapper resource="jp/hit/sample/mybatis/mapper/EmpMapper.xml"/>
        <mapper resource="jp/hit/sample/mybatis/mapper/DeptMapper.xml"/>
        <mapper resource="jp/hit/sample/mybatis/mapper/EmpDeptSqlMap.xml"/>
      </mappers>
    </configuration>
    

    Entityを作成します。
    package jp.hit.sample.mybatis.entity;
    
    public class EmpDeptKey {
        private Integer empId;
        private Integer deptId;
    
        public Integer getEmpId() {
            return empId;
        }
        public void setEmpId(Integer empId) {
            this.empId = empId;
        }
        public Integer getDeptId() {
            return deptId;
        }
        public void setDeptId(Integer deptId) {
            this.deptId = deptId;
        }
    }
    
    package jp.hit.sample.mybatis.entity;
    
    public class EmpDept extends EmpDeptKey {
        private String empName;
        private String deptName;
        private String tel;
    
        public String getEmpName() {
            return empName;
        }
        public void setEmpName(String empName) {
            this.empName = empName;
        }
        public String getDeptName() {
            return deptName;
        }
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
        public String getTel() {
            return tel;
        }
        public void setTel(String tel) {
            this.tel = tel;
        }
    }
    

    Mapperのinterfaceクラスを作成します。
    package jp.hit.sample.mybatis.client;
    
    import java.util.List;
    
    import jp.hit.sample.mybatis.entity.EmpDept;
    
    public interface EmpDeptSqlMap {
        /**
         * SELCT * FROM Emp LEFT JOIN Dept
         */
        List<EmpDept> selectEmpJoinDept(EmpDept empDeptParam);
    }
    



    任意のMapperを利用したSQL発行

    上記で作成したMapperは自動生成したMapperと同様の実装で利用できます。
        private static EmpDeptSqlMap empDeptSqlMap;
    
        public static void main(String[] args)  {
            // セッション作成
            SqlSession session = SessionUtil.createSession();
            // Mapperを取得
            empMapper = session.getMapper(EmpMapper.class);
            deptMapper = session.getMapper(DeptMapper.class);
            empDeptSqlMap = session.getMapper(EmpDeptSqlMap.class);
    
            try {
                // 手動作成したMapperを利用したSQLの発行
                EmpDept empDeptParam = new EmpDept();
                empDeptParam.setDeptName("%S%"); // dept.name Like '%S%'
                LOG.debug("【SQL発行】selectEmpJoinDept dept.name like '%S%' 】");
                List<EmpDept> empDeptList = empDeptSqlMap.selectEmpJoinDept(empDeptParam);
                printEmpDept(empDeptList);
    
            } finally {
                // 実行前の状態に戻す
                session.rollback();
                // セッションを閉じる
                session.close();
            }
        }
    
        /**
         * EmpDeptの中身をダンプする
         * @param empList
         */
        private static void printEmpDept(List<EmpDept> empDeptList) {
            for (EmpDept ed : empDeptList) {
                printEmpDept(ed);
            }
        }
    
        private static void printEmpDept(EmpDept empDept) {
            if (empDept != null) {
                LOG.debug("【Result】 " + "empId:" + String.valueOf(empDept.getEmpId())
                        + ", deptId:" + String.valueOf(empDept.getDeptId())
                        + ", empName:" + empDept.getEmpName()
                        + ", deptName:" + empDept.getDeptName()
                        + ", Tel:" + empDept.getTel());
            }
        }
    

    実行すると以下の結果が得られます。
    MyBatisSample - 【SQL発行】selectJoinDept dept.name like '%S%' 】
    selectEmpJoinDept - ooo Using Connection [org.postgresql.jdbc4.Jdbc4Connection@2b4d13ef]
    selectEmpJoinDept - ==>  Preparing: SELECT emp.id AS emp_id ,emp.dept_id AS dept_id ,dept.name AS dept_name ,emp.name AS emp_name ,emp.tel AS tel FROM my.emp emp LEFT JOIN my.dept dept ON emp.dept_id = dept.id WHERE dept.name LIKE ? ORDER BY emp.id 
    selectEmpJoinDept - ==> Parameters: %S%(String)
    selectEmpJoinDept - <==      Total: 2
    MyBatisSample - 【Result】 empId:3, deptId:3, empName:Bravo, deptName:System, Tel:222-222-2222
    MyBatisSample - 【Result】 empId:4, deptId:2, empName:Charlie, deptName:Sales, Tel:333-333-3333
    

    Mapperファイル内には複数のSQLが記述可能なため、例えば親テーブル毎にMapperを分けるなどすれば管理もしやすくなると思います。


    今回でひとまずMyBatisサンプルの動作まで確認できました
    ・・・が、Mapperを追加するたびに毎度設定ファイルを書き換えたり、セッション管理ロジックを書いたり、Mapperの取得したりするのはめんどくさいと思います。
    MyBatisにはSpringFrameworkとシームレスに連携出来してセッションの生成、Mapperの注入などを自動で行うMyBatis-Springという拡張フレームワークが存在します。

    この拡張フレームワークのおかげでMyBatisとSpringの連携は簡単に実装できるのでそちらの方がメインの使い方になると思いますが、今回はとりあえずMyBatisだけに絞りました。
    MyBatis-Springの方の記事も時間があればいずれ書きたいと思います。