タイガー!タイガー!じれったいぞー!(SE編)

AS400, Java, JavaEE, JSF等の開発、習慣など。日々の気づきをまとめたブログ(備忘録)

【JSF2.2】セッションごとに保持できるView画面件数が25画面までに制限されている

1年半ほど前にブログで、ViewExpiredExceptionに苦しんだ記録をまとめましたが、再度、似たようなトラブルに直面してしまいました。
タイトルがその原因だったわけですが、この問題についてもまとめておこうと思います。

STATE_SAVING_METHODについて

JavaEE7のWebアプリケーションでは、STATE_SAVING_METHODの設定で、クライアントごとのView情報をどこで保管するかを管理します。
デフォルトでは、serverになっており、Viewの数も15と少な目に設定されています。(下記の設定は、定義しなくてもデフォルトで反映されます)

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>sever</param-value>
    </context-param>

    <context-param>
        <param-name>com.sun.faces.numberOfViewsInSession</param-name>
        <param-value>15</param-value>
    </context-param>

    <context-param>
        <param-name>com.sun.faces.numberOfLogicalViews</param-name>
        <param-value>15</param-value>
    </context-param>

前回、JavaEEアプリの設定(web.xml)で、「javax.faces.STATE_SAVING_METHOD」を「server」から「client」に変更することで、view情報保持数の制限がなくなりました。(ただし、セキュリティ的に危険な状態の設定ではあります。)

今回のトラブル内容

今回起きたトラブルは、STATE_SAVING_METHODを「client」にしているのも関わらず、数ヵ月に1~2回、view情報が消失してしまうというもの。
テスト機で再現させようとトライするも、なかなか発生せず・・・。いったい何故???
ユーザー側にも、きちんとした原因を説明することができず、もどかしい状態が続いておりました。

ちなみに、利用しているソフトウェア構成は下記の通りです。

ソフトウェア構成

  • JavaEE7(Payara 4.1.1.154)
    • JSF2.2
    • Primefaces5.3

原因発見

かなり、困惑させられましたが、PrimeFacesの「Dialog Framework」で作成した検索画面を25回連続で呼び出して、呼び出し元画面に戻ってきたときに、呼び出し元画面のView Scope情報が消失されました!

stackoverflowでも、下記の記事を発見することができ、25画面情報だけが保持されているという仮説が正しいことを確認しました。

stackoverflow.com

では、なぜ「Dialog Framework」が1画面扱いでカウントされてしまうのか?
PrimeFacesのDialog Frameworkでは、基本的にiframe(HTMLのタグの一つで、Webページ内に矩形の領域を設け別のページなどを読み込んで表示する)タグの中で他のビューを見せているようで、それ自身のviewスコープを持っています。
Dialog Framework画面で、特定のモデルを選択しても、Close扱いでDialogを終わらせても、結果的にはDialog Frameworkのview情報が残ってしまいます。
セッションタイムが終了したタイミングで、viewも合わせて消失されます。ということは、セッションが生きている間、view情報が生きていることになります。どんだけ~♪

「Dialog Framework」を初めて知ったときには、これで疎結合にシンプルな開発が実現できるぞ!と思っておったのですが、こういう問題が潜伏していたとは!(ただし、こういう部分をきちんと事前検証・調査しておくことが、自分のミッションではありました。。。反省!)

改善策

stackoverflow.com

この辺のサイトを確認しますと、デフォルトで「p:dialog」を推奨するとありました。 「p:dialog」では呼び出し元ページと同じview scope内に存在するので、view数を食いつぶしてしまうことはなくなりそうです。

ただし、現時点で大量の「Dialog Framework」のviewを作成しており、ひとつずつ、複合コンポーネント化するなどして、各種検索画面を「p:dialog」へ切り替えていく必要がありそうです。

また、view情報の保持する設定を拡張できないかと調査したのですが、こちらの情報はJSF2.2では設定不可っぽいのです。
次回のJSF2.3では反映されるでしょうか。

[JAVASERVERFACES-4015] Provide documentation and tuning options for activeViewMaps - Java.net JIRA

いずれにしましても、1人が25画面も同時に別タブで使うことなどありえませんので、やはりコツコツと「p:dialog」へ移行していくほうが最善策かと思っております。

今回は、かなり骨が折れましたが、原因がわかって、かなりスッキリできました!

【EXCEL】 データの出現数を集計する

完全に個人用の備忘録です。

Excelでとあるデータの出現数をカウントして表にまとめたいシーンを想定し、作業手順に落としました。

ピボットテーブルを使って集計できました。

作業手順

サンプルデータを準備しました。A2からA51まで(計50件)のデータとなっています。

f:id:no14141:20160913200651j:plain

(1)ピボットテーブルの作成

挿入メニューから「ピボットテーブル」を選択。

f:id:no14141:20160913200701j:plain

レポートの出力先はお好みですが、今回は新規シート向けで設定!

f:id:no14141:20160913200708j:plain

(2)ピボットテーブルの設計

フィールドを行のゾーンにドラッグ&ドロップしますと・・・

f:id:no14141:20160913200714j:plain

データの実際の値がグループ化されて、ラベルとして出力されました。

f:id:no14141:20160913200721j:plain

次に、フィールドより右クリックで、「値に追加」を選択します。

f:id:no14141:20160913200729j:plain

すると、初期値の値は「合計」として集計されます。

f:id:no14141:20160913200735j:plain

最後に、値フィールドの設定で、「合計」→「データの個数」へ変更します。

f:id:no14141:20160913200740j:plain

f:id:no14141:20160913200746j:plain

(3)完成

無事に、データ出現数をカウントした表が完成しました!!

f:id:no14141:20160913202242j:plain

競馬の人気別単勝率、連対率などで使えそうですね。

Docker for Windows(Ver1.12)を試してみました

今年初め、10年ぶりに資格取得に挑戦しようと、LPIC1へ挑戦しました。

何とか合格することができ、「よっしゃー!」と当時は気持ちが高ぶったのですが、半年近くも経つと、あの時の気持ちの高ぶりはすっかり冷めてしまっておりました。

これではいかん!!

内的モチベーションを働かせ、約5年ぶりにPCも購入!(初Let's Note。MacからまたWindowsに逆戻りです)

次は、LPIC2に挑戦だ!と息巻き、PCにVirtualBox(5.1.4)とVagrant(1.8.5)をインストールして実機でLinuxの勉強に取り掛かろうとしたその瞬間・・・

f:id:no14141:20160827080658j:plain

何度やっても、PCがクラッシュ(ログはKernel-Power41のみ)。
VirtualBox単体で試しても、同様の結果だったので、どうやら、VirtualBoxの仮想OS起動時に発生しているようです。

これは、困った。。。俺はただLinuxコマンドを勉強したいだけなのに。

Windows10 Anniversary Update適用済みだったので、新機能の「Bash on Ubuntu on Windows」を使う手も考えたのですが、慣れ親しんだCentOSでやりたいのもあります。

いや待てよ。そういえば「Docker for Windows」を先月末にリリースされたんだっけ!?
というわけで、「Docker for Windows(Ver1.12)」をPCにインストールすることになりました。(長い前置きでしたね。。。)

Docker for Windowsインストール

まずは事前作業として、Hyper-Vを有効化せよとのこと。正直、Hyper-Vを試したことはありません。
「プログラムと機能」→「Windowsの機能の有効化または無効化」を確認してみると、すでにHyper-Vは有効化になっていました。Windows10ではデフォルトで有効になっているのでしょうか?

次にDockerのインストーラーをダウンロードしました。下記のリンクから取得できます。

https://www.docker.com/products/docker#/windows

「InstallDocker.msi」をクリックで簡単にインストールできました!

「Docker for Windows」のアイコンをクリックしてみます。

f:id:no14141:20160827084105j:plain

すると、タスクトレイにくじらが泳ぎ始めます。

f:id:no14141:20160827084313j:plain

これで準備完了!?

コマンドプロンプトPowerShellで、確認してみると・・・

> docker -v
Docker version 1.12.0, build 8eab29e

オッケー、オッケー!!! 無事にdockerの起動に成功しました。

CentOSコンテナ作成

まずは、CentOSの最新版のDockerイメージを取得。

> docker pull centos:latest

> docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
centos               latest              970633036444        3 weeks ago         196.7 MB

次に、docker runでコンテナ作成・実行します。

> docker run -it --name mycentos centos /bin/bash
[root@f84680d43af6 /]# 

これでcentos7のコンテナの中に入ることができました!
試しに、vimを導入してみる。

[root@f84680d43af6 /]# vim
bash: vim: command not found
[root@f84680d43af6 /]# yum -y install vim
:
[root@f84680d43af6 /]# exit

再接続は、docker attachで。初回接続時に導入したvimもきちんと残っています。

> docker attach mycentos
[root@f84680d43af6 /]# yum list installed | grep vim
vim-common.x86_64                      2:7.4.160-1.el7                  @base
vim-enhanced.x86_64                    2:7.4.160-1.el7                  @base
vim-filesystem.x86_64                  2:7.4.160-1.el7                  @base
vim-minimal.x86_64                     2:7.4.160-1.el7                  @CentOS

これでDocker上のCentOSコンテナでLinuxの勉強をする準備が整いました。
このやり方以外で便利なものもたくさんあるのでしょうが、Dockerにしろ、Linuxにしろ、ひとつずつやりながらマスターしていく必要がありそうです。

おまけ:Docker起動でのエラー

別のスペックの低いPCでもDockerを入れてみましたが、そのときに発生したエラーをまとめておきます。

Not Enough memory to start Docker

f:id:no14141:20160825122118j:plain

PCのメモリが4GBと少ないから発生したエラーでしょうか。「Settings」の「Advanced」でメモリサイズを1536MB(デフォルトは2048MB)へ変更したら、無事に起動できました。ベストなサイズがどの程度かはわかりません。。。

f:id:no14141:20160825122334j:plain

The VM couldn't get on IP address

f:id:no14141:20160826080736j:plain

VMIPアドレスが設定できないというエラー。よくわかりません。dockerをexitして、リトライしたら、普通に起動できました。原因は正直、わかりません。

【AS400】RPGLE-数値→文字列変換

RPGLEでのキャスト処理(数値→文字列)についてです。

数値を文字列化して出力したいケースは、多くあると思います。
自分がわかる範囲で、そのバリエーションをまとめてみました。

(1) %CHAR関数を使う

  • %CHARを使うことで、数値を簡単に文字列変換してくれます。
  • しかし、ゼロパディングされ、カンマ区切りで整形されるわけではありません。
D W@DEC1          S              9S 0 INZ(*ZERO)
D*  
 /FREE 
     W@DEC1 = 12345;                                          
     W@MSSG = %CHAR(W@DEC1) + ' 円です。 ';                        
     DSPLY W@MSSG;   // 12345 円です。
 /END-FREE 

(2) %EDITW関数を使う

  • 下記のソースは、D仕様書で宣言タイプをC(固定情報定義)し、%EDITWで文字列変換した例です。
  • 書式でカンマ区切りや符号を定義すれば、その通り出力されますが、フル桁の数値でない場合には空白になってしまいます。
D FORMAT1         C                   '   ,   ,   - 円です。 ' 
D*                                                             
 /FREE 
     W@DEC1 = 123456789;             
     W@MSSG = %EDITW(W@DEC1:FORMAT1);
     DSPLY W@MSSG;   // 123,456,789  円です。   
                                
     W@DEC1 = 12345;                 
     W@MSSG = %EDITW(W@DEC1:FORMAT1);
     DSPLY W@MSSG;   //      12,345  円です。   
                                
     W@DEC1 = -500;                  
     W@MSSG = %EDITW(W@DEC1:FORMAT1);
     DSPLY W@MSSG;   //         500- 円です。
 /END-FREE                

(3) %EDITC関数を使う

  • 下記は、%EDITCで文字列変換した例です。
  • %EDITCではオプションを指定できます。
    • オプション'X'では、ゼロ・パディングしてキャストしてくれます。
    • オプション'N'では、カンマ区切り。フル桁でない場合には、その桁をブランクへ変換。
    • オプション'Y'では、日付変換してくれます。便利!!
  • 個人的には、 %TRIM(%EDITC(W@DEC1:'N')) でブランクを詰めてやるやり方をよく使います(メッセージなどで)。
 /FREE
    W@DEC1 = 777;                                     
    W@MSSG = %EDITC(W@DEC1:'X') + ' 円です。 ';       
    DSPLY W@MSSG;   // 000000777 円です。

    W@DEC1 = 123456789;                               
    W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
    DSPLY W@MSSG;   // 123,456,789 円です。  
                                                      
    W@DEC1 = 12345;                                   
    W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
    DSPLY W@MSSG;   //      12,345 円です。 
                                                      
    W@DEC1 = 12345;                                   
    W@MSSG = %TRIM(%EDITC(W@DEC1:'N')) + ' 円です。 ';
    DSPLY W@MSSG;   // 12,345 円です。
                                                      
    W@DEC1 = -123456789;                              
    W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
    DSPLY W@MSSG;   // -123,456,789 円です。

    W@DEC2 = 20160824;            
    W@MSSG = %EDITC(W@DEC2:'Y');  
    DSPLY W@MSSG;   // 2016/08/24   
 /END-FREE 

(4) データ構造を使う(番外)

  • こちらは番外です。キャストではありませんが、RPGのDS定義を使って表現する方法です。
  • 数値を文字列変換しているわけではなく、その数値とミックスしたDSで文字列を形成しています。
  • よって、カンマ区切り等に変換されるわけではありません。
D FORMAT2                 1     19
D TEXT1                   1      9S 0 
D TEXT2                  10     19    INZ(' 円です。 ')
D*                       
 /FREE
    TEXT1 = 123456789;
    DSPLY FORMAT2;    // 123456789 円です。 
 /END-FREE

最終的なサンプル・コードは、こちらです。

サンプルPGM

H DATEDIT(*YMD/)                                              
D MAIN            PR                  EXTPGM('SAMPLE38')      
D MAIN            PI                                          
D*                                                            
D W@DEC1          S              9S 0 INZ(*ZERO)              
D W@DEC2          S              8S 0 INZ(*ZERO)              
D W@MSSG          S             50    INZ(*BLANK)             
D*                                                            
D FORMAT1         C                   '   ,   ,   - 円です。 '
D*                                                            
D                 DS                                          
D FORMAT2                 1     19                            
D TEXT1                   1      9S 0                         
D TEXT2                  10     19    INZ(' 円です。 ')       
D*                                                            
 /FREE                                                        
    //+++++++++++++++++++++++++++++++                         
    // %CHAR 関数を使う                                       
    //+++++++++++++++++++++++++++++++                         
     W@DEC1 = 12345;                       
     W@MSSG = %CHAR(W@DEC1) + ' 円です。 ';
     DSPLY W@MSSG;                         
                                           
    //+++++++++++++++++++++++++++++++      
    // %EDITW 関数を使う                   
    //+++++++++++++++++++++++++++++++      
     W@DEC1 = 123456789;                   
     W@MSSG = %EDITW(W@DEC1:FORMAT1);      
     DSPLY W@MSSG;                         
                                           
     W@DEC1 = 12345;                       
     W@MSSG = %EDITW(W@DEC1:FORMAT1);      
     DSPLY W@MSSG;                         
                                           
     W@DEC1 = -500;                        
     W@MSSG = %EDITW(W@DEC1:FORMAT1);      
     DSPLY W@MSSG;                         
                                           
    //+++++++++++++++++++++++++++++++      
    // %EDITC 関数を使う                               
    //+++++++++++++++++++++++++++++++                  
     W@DEC1 = 777;                                     
     W@MSSG = %EDITC(W@DEC1:'X') + ' 円です。 ';       
     DSPLY W@MSSG;                                     
                                                       
     W@DEC1 = 123456789;                               
     W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
     DSPLY W@MSSG;                                     
                                                       
     W@DEC1 = 12345;                                   
     W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
     DSPLY W@MSSG;                                     
                                                       
     W@DEC1 = 12345;                                   
     W@MSSG = %TRIM(%EDITC(W@DEC1:'N')) + ' 円です。 ';
     DSPLY W@MSSG;                                     
                                                       
     W@DEC1 = -123456789;                              
     W@MSSG = %EDITC(W@DEC1:'N') + ' 円です。 ';       
     DSPLY W@MSSG;                   
                                     
     W@DEC2 = 20160824;              
     W@MSSG = %EDITC(W@DEC2:'Y');    
     DSPLY W@MSSG;                   
                                     
    //+++++++++++++++++++++++++++++++
    //  データ構造を使う             
    //+++++++++++++++++++++++++++++++
     TEXT1 = 123456789;              
     DSPLY FORMAT2;                  
                                     
     *INLR = *ON;                    
     RETURN;                         
 /END-FREE                                                

実行結果

> CALL PGM(TIGEROBJ/SAMPLE38)

DSPLY  12345 円です。       
DSPLY  123,456,789  円です。
DSPLY       12,345  円です。
DSPLY          500- 円です。
DSPLY  000000777 円です。   
DSPLY   123,456,789 円です。
DSPLY        12,345 円です。
DSPLY  12,345 円です。      
DSPLY  -123,456,789 円です。
DSPLY  2016/08/24           
DSPLY  123456789 円です。   

【Docker】Bridge IPアドレスの変更でハマる

DockerネットワークのホストOS側のIPアドレスは、デフォルトでは「172.17.42.1/16」のIPアドレス(bridge IPアドレス)が割り当てられます。
(※追記: Docker1.11.2で試したところ、172.17.42.1ではなく、172.17.0.1/16のIPアドレスとなりましたでした。)


この物語は、そのIPアドレスを変更したく、Dockerトーシローとして悪戦苦闘し、何度も何度も困難に立ち向かった(ただ無知なだけという話もあるが)戦いの記録である・・・(TVドラマ『スクール☆ウォーズ』のイントロ風に読んでください)。

検証1

(1) Dockerネットワーク現状確認

まずは、Docker起動中のネットワークを確認。

$ ifconfig
docker0   Link encap:Ethernet  HWaddr xx:xx:xx:xx:xx:xx
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0 
          inet6 addr: xxxx::xxxx:xxxx:xxxx:xxxx/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:164998 errors:0 dropped:0 overruns:0 frame:0
          TX packets:200582 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:219239813 (209.0 MiB)  TX bytes:153663854 (146.5 MiB)
:

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
link-local      *               255.255.0.0     U     1002   0        0 eth0
192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
172.17.0.0      *               255.255.0.0     U     0      0        0 docker0 <<<<<<<<<<<<

確かに、172.17.0.0/16のネットワークが構成されています。

(2) Docker起動時のbipアドレスの設定

  • 初期 bipアドレスを「192.168.100.1/24」へ変更する設定例は、下記の通りです。

Docker停止

# service docker stop
docker を停止中:                                           [  OK  ]

「/etc/sysconfig/docker」を編集

# vim /etc/sysconfig/docker

#
# Other arguments to pass to the docker daemon process
# These will be parsed by the sysv initscript and appended
# to the arguments list passed to docker -d

other_args="--bip=192.168.100.1/24"   <-- 編集

(3) Docker起動

# service docker start
Starting docker:                                           [  OK  ]

# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.100.0    *              255.255.255.0   U     0      0        0 docker0 <<<<<<
link-local       *              255.255.255.0   U     1002   0        0 eth0
192.168.1.0      *              255.255.255.0   U     0      0        0 eth0
default         192.168.1.1     0.0.0.0         UG    0      0        0 eth0

Serverを再起動しても、bipは指定した通りに起動してくれました!


検証2

(1) Dockerネットワーク現状確認

# ip addr show
:
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:3aff:fef8:662/64 scope link
       valid_lft forever preferred_lft forever

(2) Docker起動時のbipアドレスの設定

  • 検証1と同じく、初期 bipアドレスを「192.168.100.1/24」へ変更します。

Docker停止

# systemctl stop docker

「/usr/lib/systemd/system/docker.service」を編集

# vim /usr/lib/systemd/system/docker.service
:
ExecStart=/usr/bin/dockerd                            // before
ExecStart=/usr/bin/dockerd --bip=192.168.100.1/24     // after

(3) Docker起動

# systemctl daemon-reload

# systemctl start docker

# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.100.1  netmask 255.255.255.0  broadcast 0.0.0.0
        ether xx:xx:xx:xx:xx:xx  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

こちらも、Serverを再起動しても、bipは指定した通りに起動してくれました!


今回は、かなりハマってしまいました。。。
ブリッジIPアドレスの認識も甘く、自分でbipのインタフェースの設定ファイルを作ったり、「brctl addbr」コマンドでブリッジを作成してしまったりと、うまく制御させることができませんでした・・・。

結果、サービスの起動ファイルにOPTIONを付加するというシンプルな形に落ち着きました。

今回もまた、「基本を知れ!」という教訓を得ることとなりましたとさ。

【Docker】DokuWikiバックアップコンテンツを復元できなくなる(tar: can't open '/backup/dw-backup.tgz': Permission denied)

Dockerをオンプレミスで利用するようになって、しばらく経ちますが、自分でDockerfileを書いて「infrastructure as a code」も実現できますし、「Docker Hub」から必要なものを取り込むことも容易にできて、もう、便利過ぎてDocker無しでは考えられません!

その中で、自分なりに、はまった内容をまとめておきたいと思います。

wikiサイトを調べていて、「DokuWiki」というオープンソースを試そうと思い、Docker Hubから、vimagick/dokuwikiをpullして、テストした際に発生したものです。
こちらのDockerイメージは、Alpine Linuxを使っていて、容量がとても小さい!
さらに、コンテンツのバックアップもリストア方法のコマンドも「Repo info」にまとめていただいており、大変ありがたい公開イメージです。

ところが、復元コマンドを実施した際に、「tar: can't open '/backup/dw-backup.tgz': Permission denied」というエラーメッセージになり、コンテンツの復元ができない事態になってしまいました。

発生Version

  • CentOS7.2
  • docker1.10.3 (yum docker installでインストールしたもの)
# docker -v
Docker version 1.10.3, build 9419b24-unsupported

※ちなみに、Docker1.7.0では、エラーにならず、うまくいきました!

エラー内容

# systemctl start docker

# docker run -d -p 8000:80 --restart always --name dokuwiki vimagick/dokuwiki

# docker run --rm --volumes-from dokuwiki -v `pwd`:/backup alpine tar czf /backup/dw-backup.tgz /var/www/html
Unable to find image 'alpine:latest' locally
Trying to pull repository docker.io/library/alpine ...
latest: Pulling from docker.io/library/alpine
:
Status: Downloaded newer image for docker.io/alpine:latest
tar: can't open '/backup/dw-backup.tgz': Permission denied

問題解決

  • Dockerを最新版(2016-07-27 1.11.2, build b9f10c9)を適用し、解決できました!

(1) 現VersionのDockerの削除

# yum list installed|grep docker
docker.x86_64                         1.10.3-44.el7.centos             @extras
docker-common.x86_64                  1.10.3-44.el7.centos             @extras
docker-forward-journald.x86_64        1.10.3-44.el7.centos             @extras
docker-selinux.x86_64                 1.10.3-44.el7.centos             @extras

# yum remove -y docker.x86_64 
# yum remove -y docker-selinux.x86_64
# yum remove -y docker-forward-journald.x86_64
# yum remove -y docker-selinux.x86_64

(2) 最新版のDockerインストール

curl -sSL https://get.docker.com/ | sh

# curl -sSL https://get.docker.com/ | sh
+ sh -c 'sleep 3; yum -y -q install docker-engine'
warning: /var/cache/yum/x86_64/7/docker-main-repo/packages/docker-engine-selinux-1.11.2-1.el7.centos.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 2c52609d: NOKEY
Public key for docker-engine-selinux-1.11.2-1.el7.centos.noarch.rpm is not installed
Importing GPG key 0x2C52609D:
 Userid     : "Docker Release Tool (releasedocker) <docker@docker.com>"
 Fingerprint: 5811 8e89 f3a9 1289 7c07 0adb f762 2157 2c52 609d
 From       : https://yum.dockerproject.org/gpg
restorecon:  lstat(/var/lib/docker) failed:  No such file or directory
warning: %post(docker-engine-selinux-1.11.2-1.el7.centos.noarch) scriptlet failed, exit status 255
Non-fatal POSTIN scriptlet failure in rpm package docker-engine-selinux-1.11.2-1.el7.centos.noarch

If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker your-user

Remember that you will have to log out and back in for this to take effect!


# docker -v
Docker version 1.11.2, build b9f10c9

(3) Dokuwiki復元テスト

# systemctl start docker

# docker run -d -p 8000:80 --restart always --name dokuwiki vimagick/dokuwiki

# docker run --rm --volumes-from dokuwiki -v `pwd`:/backup alpine tar czf /backup/dw-backup.tgz /var/www/html
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
:
Status: Downloaded newer image for alpine:latest
tar: removing leading '/' from member names

(4) サイト確認

無事、リストアされました!

ちなみに、2016-08-03時点では、Docker 1.12.0がリリースされています。

新しいVersionを、積極的に試していく前向きさが必要であることを痛感した出来事でした。

それと、Linuxコマンドを深く理解することも重要であることも・・・!

はじめてのMybatis(with AS400)

概要

O/Rマッパーではなく、SQL文とオブジェクトのマッピングを行ってくれるというMyBatis(旧:iBatis)を試してみました。
DBはDB2 for i(AS400)となります。

結論から申しますと非常に便利!! しかしながら、DBがAS400だからなのかもしれませんが、何点か注意すべきことがあったので、学んだことをこちらにまとめておきたいと思います。

実行環境

  • Java
    • 1.8.0.45
  • Mybatis
    • 3.4.1
  • Driver : AS/400 Toolbox for Java JDBC Driver
    • Version : 8.11 (JT400 6.7)
  • Database : DB2 UDB for AS/400
    • Version : 07.01.0000 V7R1m0

はまったこと

(1) ライブラリーリストが機能しない

AS400の魅力のひとつに「ライブラリーリスト」という概念があります。
簡単にいうと、複数のスキーマを参照できるパスのようなもので、SQL文でスキーマ名を指定しなくても、そのライブラリーリストの中から順にテーブルを探してくれるという優れものなのです。
つまりは、本番環境用とテスト環境用のスキーマ(ライブラリー)を分けて運用している場合、ライブラリーリストの中身を変えるだけでSQL文は変更不要で共通化できるようになります。

ところが、今回の検証では、ライブラリーリストの設定が効きませんでした。

JDBCの設定は、下記の例の通り、DB接続オプション「libraries」で指定可能です。

Connection con = DriverManager.getConnection(
    jdbc:as400://192.168.1.5;libraries=TIGERDB,TIGERDB2, user, password);

MybatisでDB接続したジョブを調査したところ(WRKJOB - 13.ライブラリー・リストの表示)、 きちんと指定した2つのライブラリーが存在していましたが、SQLで指定したTIGERDB2に存在するテーブルを参照できず、エラーとなりました。

Cause: java.sql.SQLException: [SQL0204] TIGERDBのタイプ*FILEのTEST_TABLEが見つからない。
    at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:136)
    :

これはMybatisの仕様なのでしょうか。
ただし、librariesオプションの最初に指定したライブラリーのテーブルは、参照できました!

スキーマは1つだけで、限定で使っていくしかなさそうです。

なお、Mybatisでは、定義ファイルでdataSourceを複数管理できるので、こちらの指定でテストと本番の切り替えはできそうです。 安心しました!

(2) resultMapのcolumn値でピリオド付きフィールドが使えない

下記のようなSQL文をresultMapに紐づけしようとした際に、「テーブル別名.フィールド名」の部分に値が格納されませんでした。(SQLは検証時のもので故意的にJOINしています)

<mapper namespace="tigertaizo.mappers.CompanyMapper">
    <resultMap id="companyResultMap" type="tigertaizo.models.Company">
        <id property="companyCode" column="CP1.COMPANY_CODE"/>
        <result property="companyValue1" column="CP2.COMPANY_VALUE1"/>
        <result property="companyValue2" column="CP2.COMPANY_VALUE2"/>
    </resultMap>
    
    <select id="selectTest" resultMap="companyResultMap">
        SELECT
           CP1.COMPANY_CODE, 
           CP2.COMPANY_VALUE1,
           CP2.COMPANY_VALUE2
         FROM COMPANY AS CP1
         INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
         WHERE CP1.COMPANY_CODE = #{companyCode}
    </select>
</mapper>

これは大した問題ではなく、SQL文のフィールド名を別名にして、resultMapのcolumn値としました。解決です!

<mapper namespace="tigertaizo.mappers.CompanyMapper">
    <resultMap id="companyResultMap" type="tigertaizo.models.Company">
        <id property="companyCode" column="COMPANY_CODE"/>
        <result property="companyValue1" column="COMPANY_VALUE1"/>
        <result property="companyValue2" column="COMPANY_VALUE2"/>
    </resultMap>
    
    <select id="selectTest" resultMap="companyResultMap">
        SELECT
           CP1.COMPANY_CODE AS COMPANY_CODE, 
           CP2.COMPANY_VALUE1 AS COMPANY_VALUE1,
           CP2.COMPANY_VALUE2 AS COMPANY_VALUE2
         FROM COMPANY AS CP1
         INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
         WHERE CP1.COMPANY_CODE = #{companyCode}
    </select>
</mapper>

(3) 最初のN行のみの選択したい場合にN値をパラメータで渡せない

新しいDB2 for iのVersionでは「LIMIT」句が使えるようになったという話ですが、私が参照するVersionではLIMIT句は非サポート。
最初のN行取得する場合のSQL文は「FETCH FIRST N ROWS ONLY」を使うのですが、このN行をパラメータで渡せず。。。

<mapper namespace="tigertaizo.mappers.CompanyMapper">
    <resultMap id="companyResultMap" type="tigertaizo.models.Company">
        <id property="companyCode" column="COMPANY_CODE"/>
        <result property="companyValue1" column="COMPANY_VALUE1"/>
        <result property="companyValue2" column="COMPANY_VALUE2"/>
    </resultMap>
    
    <select id="selectTest" resultMap="companyResultMap">
        SELECT
           CP1.COMPANY_CODE AS COMPANY_CODE, 
           CP2.COMPANY_VALUE1 AS COMPANY_VALUE1,
           CP2.COMPANY_VALUE2 AS COMPANY_VALUE2
         FROM COMPANY AS CP1
         INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
         FETCH FIRST #{count} ROWS ONLY
    </select>
</mapper>

これはパラメータ渡しができないようで、SQL文字列の埋め込みをする動的SQLで対応するしかないようです。

「#{count}」の部分を 「${count}」へ変更したところ、うまく動きました!

(4) CHARフィールドのトリムがされない

JPAなどのO/Rマッパーでは、CHAR文字列のフィールドであっても、自動的にトリムされてJavaのフィールドにセットされるのですが、 Mybatis経由では、トリムされずにそのままセットされてしまいました。
JavaのDAOクラスでは、モデルにセットする際に.trim()をかましていたのですが、これは困りました。(レガシーなCHARが問題のような気もしますが・・・)

仕方なくSQL側でRTRIMすることで対処しました。大量のレコード取り出し時でのSQLパフォーマンスが気になるところです。

<mapper namespace="tigertaizo.mappers.CompanyMapper">
    <resultMap id="companyResultMap" type="tigertaizo.models.Company">
        <id property="companyCode" column="COMPANY_CODE"/>
        <result property="companyValue1" column="COMPANY_VALUE1"/>
        <result property="companyValue2" column="COMPANY_VALUE2"/>
    </resultMap>
    
    <select id="selectTest" resultMap="companyResultMap">
        SELECT
           RTRIM(CP1.COMPANY_CODE) AS COMPANY_CODE, 
           RTRIM(CP2.COMPANY_VALUE1) AS COMPANY_VALUE1,
           RTRIM(CP2.COMPANY_VALUE2) AS COMPANY_VALUE2
         FROM COMPANY AS CP1
         INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
         WHERE CP1.COMPANY_CODE = #{companyCode}
    </select>
</mapper>

数日試したところでは、以上4点が気になりましたが、MyBatisの魅力は十分認識できました!
JPAとうまく使い分けしていきたいところです!!!