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

AS400,WAS,GlassFish,Java,JavaEE,JSF等の開発における日々の気づきをまとめたブログ(備忘録)。

「JJUG CCC 2016 Fall」に行ってきました #jjug_ccc

日付を跨いでしまいましたが(長州力に「跨ぐなよ!、おいコラ!」と恫喝してもらいたい)、昨日、新宿で行われたJJUGに行ってきました。
2015年春、2016年春に続いて3回目。急で強引な参加になってしまいましたが、Javaエンジニア力アップのためにも、チーム開発レベルアップのためにも、何としても次の行動に結びつけるための何かを持ち帰りたい!!! そんな思いで今回も参加しました。

www.java-users.jp

基調講演1 Be a great engineer!〜 フォローすべきトレンド、スルーすべきトレンドをどう見抜くのか

まずは、朝一発目! 去年のJavaDayで初めて講演を聞かせていただいた谷本さんの話。
前回同様、お笑い満載かと思っていたら、哲学的なお話。確かに先を見越して何にウォッチすべきかってことは、7つの習慣でいうところの第2領域の時間の使い方。的外れな技術を選んでいると、結果的に緊急性の高いタスクに振り回されることにもなります。
今自分は、JSF(スルー対象)で開発をしていますが、自分たちのニーズにまさにマッチしているので、これはこれでオッケーだと思われますが、Javaフレームワークに縛られずに、同じコンポーネントベースのAngularなどのJSフレームワークも含めて3歩先を見ていこうと思っています。まあ、その前にJFS2をまだ極めきれていないので、まずはこちらを優先する必要があります。

基調講演2 Java EE - What's Next?

Oracle社のAnil Gaurさんの講演。今後のJavaEEの展望のお話。
時代は、急速にマイクロサービス化に向かっていく(いる)、そこにJavaEEもフォーカスしていくという概要か。。。
今年春から英語の勉強を再開して継続しているのですが、ほぼ聞き取れず。通訳の方、ITにも精通しているのでスラスラ翻訳し、凄すぎです! 当然、JavaEE8,9のリリースも気になりますが、次回以降、自分の英語リスニング力をアップして、本場の方の声を堪能したいところであります。

メンバーのスキルアップ、どうしてる? -Java 100本ノックで新加入メンバーを鍛えてみた-

メンバーのJava力アップに「Java100ノック」を使ってきたお話。メンバーのスキルアップには、レビューが最も大切という。さらに、レビューは、間違いを指摘するだけでなく、「よくできたことを伝える」という部分が目から鱗でした(その前に定期的なレビューができていないのですが・・・)
さらに、アンチパターンに名前を付ければいいよと。これは今日から実践できることで、自分が経験した失敗事例アンチパターンJavaに限らず)を命名しようと思います。さて、どんな風に名前を付けようか? どうせなら、覚えやすいセンスあふれる名前がいいですよね。

GitBucketを支えるJava技術とグローバルで使われるOSSの作り方

社内で、バリバリ活用させてもらっているGitBucketの歴史を聞くことができました。今回、自分が一番楽しみにしていたセッション。
GitBucketの産みの苦労話で、「こういうプロダクトを作ってみようと思う」という著者に対し、仲間から「そんなの無理だろう」というストーリー。ここで実際に優れたプロトタイプを制作し、メンバーにみてもらって「これはいける!」となったそうです。反対などにも目をくれず、行動していく姿勢があったからこそ、このOSSがあるということですね。
増井雄一郎さんの「wri.pe」同様に、まずは自分が欲しいものを創る! その精神が素晴らしいです。
自分が今一番欲しい道具っていったい何だろうか? そういう自分への問いが大切なのかもしれません。「今までこうやってきたから!」などという思考停止発言はしたくないですしね。

JVMのトラブル解決のためにやったこと~メモリー/スレッド

こちらもずっと悩ませているヒープについて。JVM、メモリ、GC、スレッドの基本の話の後、実際にやったトラブルシューティングの話がとても参考になりました。大事なのは、問題が起きてからではなく、正常時の定期観測というもの。そのツールとして、Zipkin(分散トレーシングシステム)、cactiLinuxリソース監視Tool)、Sentry(エラートラッカー)なども教えていただきましたが、まずは、はやりログ監視をしっかり実現していく必要がありそうです(できていないので)。そのツールは定番の「kibana+fluentd+elasticsearch」を習得していきたいですね。

JPA と DDD の関係で僕が思っていること

キータの「JavaEE使い方メモ」を何度も拝見させていただいています。そのページを書いている方がスピーカーということで、こちらも楽しみにしていたセッション。
自分が抱いているJPA像って、「複雑で、いまいち」というもの。その悪印象を払拭していただくことができました。JPAどうのこうのの前に、やはり大切なのはデータ構造であるということを改めて思い知る良い機会となりました。
RDB上の再設計はまだ先になりそうですが、最重要課題であることは間違いないので、今からJPAと合わせて準備します。

Payara Micro の設計と実装

組み込み系Payaraについてのお話。正直、フルPayaraを使うようになって、もうすぐ1年か。。。
Glassfishと比べてかなり安定しており、定期的にアップデートしてくれるPayara。近々、最新版にアップする計画もあるので、Payaraの中身について話を聞けてとても良かったです。Bootstrapの部分や停止する仕組みなど、そういう内部的な動きについて詳しくなることで、トラブル解決力も上がっていくでしょう。


2016年春は、JavaEE系の話がほとんどなかったのですが、今回はJavaEE系のセッションは多くあり、とても参考になりました。ただし、EEよりもSpringの方が人気と熱気があるように思えました。Springについては、まったく試していないので、こちらも自習から初めてみます。 合わせて、JPA活用、Javaスキルアップ(教育)、ヒープ管理、ログ監視とまだまだできていないことがたくさんあることを認識することができた良い機会となりました。

できる範囲で、いや無理をしてでも、ひとつずつ取り入れてやっていこうと思っています。

何のためにそれらをやるのか? 答えは簡単です。
そのシステムを使うユーザーのため。さらに継続的に管理・改善していく自分たちチームのため。その2つしかありませんね!

【AS400】レコードロック検証

改めて、DB2 for iでのレコードロックを検証していきたいと思います。

(1) レコードロックとは?

レコードロックとは、ローリングストーンズ、ビートルズ、ジミヘンなどのLPのことで・・・違いました。それはロックレコードですね。。。
テーブルのとある行をこれから書き込みを行う作業前に、その対象レコードを「今から俺が使うよ!」という宣言のようなもので、レコード(行)をロック(占有)することです。
これにより、ロックされたレコードは、他のユーザーやバッチプログラムなどからの更新処理を一時的にブロックすることができます。
書き込み直前にロックをし、レコードの値を更新して対象レコードを更新する。そんな流れになります。
ただし、ロック事態が排他制御をしてくれるわけではなく、排他制御はプログラムできちんと実装する必要があります。(JavaJPAでは、悲観的ロックや楽観的ロックという手法もあります)

(2) RPGプログラムでレコードロックを発生させてみる

それでは、実際にレコードロックを発生させてみましょう。

サンプルで下記のようなテーブルを準備しました。従業員マスタは、レコードロック検証全体で使います。部門マスタはデッドロック検証で利用します。

■ 従業員マスタ (TIGERDB/EMPLOYEES)

社員CD 苗字 名前 電話番号 部門CD
0001 山田 太郎 012-3456-7890 10
0002 山田 花子 012-3456-7891 20

■ 部門マスタ (TIGERDB/SECTIONS)

部門CD 部門名
10 営業部
20 管理部

次のような対話型RPGを作成しました。1つの対話型JOBで「社員CD=0001」をロックし、もう1つの対話型JOBで同じ「社員CD=0001」をロックしてみます(検証1の図参照)。
ちなみに、ロック呼び出しの条件は、対象テーブル(EMPLOYEES)をUモードで定義し、READ、CHAIN文などでオプション(N)を付けずに実行すれば、ロック読み取りが実施されます!

f:id:no14141:20161121170325j:plain

検証1用ソースPGM RPGLESRC/SAMPLE39

H DATEDIT(*YMD/)                                                 
F****************************************************************
F* FILE 定義                                                     
F****************************************************************
F* 従業員マスタ                                                  
FEMPLOYEES UF   E           K DISK                               
F*                                                               
D****************************************************************
D* PROTOTYPE/INTERFACE                                           
D****************************************************************
D MAIN            PR                  EXTPGM('SAMPLE39')         
D MAIN            PI                                             
D*                                                               
D****************************************************************
D* VARIABLE 定義                                                 
D****************************************************************
D LOCK_ID         S              4A   INZ(*BLANK)                
D UNLOCK_FLG      S              1A                              
D W@MSSG          S             50    INZ(*BLANK)                
D*                                                               
C****************************************************************
C* MAIN                                                          
C****************************************************************
C                   EXSR      #MAIN                              
C                   EXSR      #FINAL                             
 /FREE                                                           
  //*************************************************************
  //*  MAIN                                                      
  //*************************************************************
  BEGSR #MAIN;                                                   
     DSPLY ' レコードIDを指定して下さい  ?' '' LOCK_ID;
     EMP_CD = LOCK_ID;
     CHAIN EMP_CD RECEMP; //←読み取り成功で、レコードロック!!
     IF %FOUND();
         W@MSSG = ' レコード読み取り中 :' + LOCK_ID;
         DSPLY W@MSSG;
     ELSE;
         W@MSSG = ' レコードが存在しません :' + LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDIF;
      
   // ロック開放
      DSPLY '  ロックを開放しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK EMPLOYEES;
      ENDIF;
  ENDSR;
  //*************************************************************
  //* FINALIZE 
  //*************************************************************
  BEGSR #FINAL; 
      *INLR = *ON;
      RETURN; 
  ENDSR; 
 /END-FREE 

検証1 実行結果

従業員マスタレコード0001をUSER01のJOBがロックしている状態で、USER02がロックさせます。
すると、USER02では、初期値60秒を経過したのち、アベンド(プログラム異常終了)が発生してしまいました。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39)   
[USER01] DSPLY   レコードIDを指定して下さい  ?
[USER01] ? 0001
[USER01] DSPLY   レコード読み取り中 :0001
[USER01] DSPLY    ロックを開放しますか?  (Y/N)   ※USER01は応答しないでおく。
[USER02] CALL PGM(TIGEROBJ/SAMPLE39)
[USER02] DSPLY   レコードIDを指定して下さい  ?
[USER02] ? 0001
[USER02] ロック Waiting -  60秒(デフォルト値)
[USER02] アベンド発生!「アプリケーション・エラー。 RNX1218 は, SAMPLE39 によって
         ステートメント 0000000044 命令 X'0000' で監視されていません。」
[USER02] プログラム異常終了
[USER01] ? Y     
[USER01] プログラム正常終了

レコードロックの確認方法

ロックが発生しているかどうかは、「DSPRCDLCKコマンド」で確認が可能です。
下記は、「DSPRCDLCK FILE(TIGERDB/EMPLOYEES)」を実行した結果になります。

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400 
 ファイル . . . . . . :   EMPLOYEES       メンバー . . . . . . :   EMPLOYEES    
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER01      USER01      701737   保留     UPDATE   ←ロックしているJOB      
                  USER02      USER02      701944   待機     UPDATE   ←ロック待機中のJOB

ロックウェイト時のWRKACTJOB

                               活動ジョブの処理 
                                                      
 オプションを入力して,実行キーを押してください。
   2= 変更   3= 保留     4= 終了   5= 処理   6= 解放   7=メッセージ の表示
   8=スプール・ファイル の処理   13= 切断 ...                              
                     現行                 
 OPT  サブシステム/ジョブ  ユーザー       タイプ  CPU %  機能             状況             
      QINTER         QSYS        SBS      .0                   DEQW   
        USER01       USER01      INT      .0  PGM-SAMPLE39     DSPW  
        USER02       USER02      INT      .0  PGM-SAMPLE39     LCKW  <<<<< Lock Waitの意味   

(3) ロックが解放される条件

ロックが解放される条件は、次の通りです。

(4) レコードロックをMONITOR文でキャッチする

レコードロックのついては、例外コード1218でキャッチすることが可能です。RPG言語では、MONITORで行います。
MONITOR命令については、以前のブログでまとめていました。

検証1で使ったソースに、例外処理を加えてみます。

f:id:no14141:20161121170349j:plain

検証2用ソースPGM RPGLESRC/SAMPLE39B

 BEGSR #MAIN;                                                
   MONITOR;                                                 
       DSPLY ' レコードIDを指定して下さい  ?' '' LOCK_ID; 
       EMP_CD = LOCK_ID;                                    
       CHAIN EMP_CD RECEMP;                                 
       IF %FOUND();                                         
           W@MSSG = ' レコード読み取り中 :' + LOCK_ID;      
           DSPLY W@MSSG;                                    
       ELSE;                                                
           W@MSSG = ' レコードが存在しません :' + LOCK_ID;  
           DSPLY W@MSSG;                                    
           LEAVESR;                                         
       ENDIF;                                               
   ON-ERROR 1218;                                           
       W@MSSG = ' レコードがロックされています :' + LOCK_ID;
       DSPLY W@MSSG;                                        
       LEAVESR;                                             
   ENDMON;                                                  
                                                            
 // ロック開放                                              
     DSPLY '  ロックを開放しますか?  (Y/N)' '' UNLOCK_FLG;     
     IF UNLOCK_FLG = 'Y';                                       
         UNLOCK EMPLOYEES;                                      
     ENDIF;                                                     
 ENDSR;                                                         
 //*************************************************************
 //* FINALIZE                                                   
 //*************************************************************
 BEGSR #FINAL;                                                  
     *INLR = *ON;                                               
     RETURN;                                                    
 ENDSR;                                                         
/END-FREE                                                       

検証2 実行結果

例外処理を組み込むことで、レコードロックを例外としてキャッチすることができます。
実施に実施してみると、アベンドではなく、ログを吐いて処理を継続してくれていることがわかります。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39B)
[USER01] DSPLY   レコードIDを指定して下さい  ?
[USER01] ? 0001
[USER01] DSPLY   レコード読み取り中 :0001
[USER01] DSPLY    ロックを開放しますか?  (Y/N)   ※USER01は応答しないでおく。
[USER02] CALL PGM(TIGEROBJ/SAMPLE39B)  
[USER02] DSPLY   レコードIDを指定して下さい  ?
[USER02] ? 0001
[USER02] ロック Waiting -  60秒(デフォルト値)
[USER02] ログ出力「レコード 1 がジョブ 701737/USER01/USER01 で使用中。」
[USER02] ログ出力「ファイル EMPLOYEES のレコードを割り振ることができない。」
[USER02] DSPLY   レコードがロックされています :0001
[USER02] プログラム正常終了
[USER01] ? Y     
[USER01] プログラム正常終了
  • 例外をキャッチすれば、例外処理を記述する必要がありますが、正常終了になるため、上位からの呼び出しにも戻りコードをエラー扱いにするなどして、連携することが可能になります。

(5) デッドロックとは?

ロックといえば、「デッドロック」という概念が必ず出てきますね。
デッドロックとは、IT用語辞典を引用させていただきましょう。

デッドロックとは、複数のプロセスが互いに相手の占有している資源の解放を待ってしまい、処理が停止してしまうこと。データベースの排他制御の不備などが原因で起こる。(IT用語辞典

(6) RPGプログラムでデッドロックを発生させてみる

処理の流れは、下記の図のようになります。

20161121181628

検証PGMでは、パラメータを1で渡すと「従業員マスタ→部門マスタ」という順で呼び出してくれるのに対して、1以外の値をパラメータに渡すと、「部門マスタ→従業員マスタ」の順で呼び出されます。
すなわち、JOB別にそれぞれ別のテーブルのレコードをロックさせた状態で、次にすでにロックしているレコードを呼び合うことで、デッドロックが発生します。

検証3用ソースPGM RPGLESRC/SAMPLE39C

H DATEDIT(*YMD/) 
F****************************************************************
F* FILE 定義
F****************************************************************
F* 従業員マスタ
FEMPLOYEES UF   E           K DISK
F* 部門マスタ
FSECTIONS  UF   E           K DISK
F*
D****************************************************************
D* PROTOTYPE/INTERFACE 
D****************************************************************
D MAIN            PR                  EXTPGM('SAMPLE39C')
D                                1A   CONST
D MAIN            PI
DP@OPTION                        1A   CONST
D*
D****************************************************************
D* VARIABLE 定義
D****************************************************************
D EMP_LOCK_ID     S              4A   INZ(*BLANK)
D SEC_LOCK_ID     S              2A   INZ(*BLANK)
D UNLOCK_FLG      S              1A
D W@MSSG          S             50    INZ(*BLANK)
D*
 /FREE
  //*************************************************************
  //*  MAIN
  //*************************************************************
      IF P@OPTION = '1';
           EXSR  #SUB_EMP;
           EXSR  #SUB_SEC;
      ELSE;
           EXSR  #SUB_SEC;
           EXSR  #SUB_EMP;
      ENDIF;
      EXSR  #FINAL;
  
  //*************************************************************
  //*  従業員マスタ操作
  //*************************************************************
  BEGSR #SUB_EMP;
     DSPLY ' 従業員マスタIDを指定して下さい  ?' '' EMP_LOCK_ID;
     MONITOR;
         EMP_CD = EMP_LOCK_ID;
         CHAIN EMP_CD RECEMP;
         IF %FOUND();
             W@MSSG = ' 従業員レコード読み取り中 :' + EMP_LOCK_ID;
             DSPLY W@MSSG;
         ELSE;
             W@MSSG = ' 従業員レコードが存在しません :' + EMP_LOCK_ID;
             DSPLY W@MSSG;
             LEAVESR;
         ENDIF;
     ON-ERROR 1218;
         W@MSSG = ' 従業員レコードがロックされています :' + EMP_LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDMON;
   // ロック開放
      DSPLY '  ロックを解除しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK EMPLOYEES;
      ENDIF;
  ENDSR;
  //*************************************************************
  //*  部門マスタ操作 
  //************************************************************* 
  BEGSR #SUB_SEC;
     DSPLY ' 部門マスタIDを指定して下さい  ?' '' SEC_LOCK_ID;
     MONITOR;
         SEC_CD = SEC_LOCK_ID;
         CHAIN SEC_CD RECSEC;
         IF %FOUND();
             W@MSSG = ' 部門レコード読み取り中 :' + SEC_LOCK_ID;
             DSPLY W@MSSG;
         ELSE;
             W@MSSG = ' 部門レコードが存在しません :' + SEC_LOCK_ID;
             DSPLY W@MSSG;
             LEAVESR;
         ENDIF;
     ON-ERROR 1218;
         W@MSSG = ' 部門レコードがロックされています :' + SEC_LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDMON;

   // ロック開放
      DSPLY '  ロックを解除しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK SECTIONS;
      ENDIF;
  ENDSR;
  //*************************************************************
  //* FINALIZE
  //*************************************************************
  BEGSR #FINAL;
      *INLR = *ON;
      RETURN;
  ENDSR; 
 /END-FREE

検証3 実行結果

実際にデッドロックが発生させることができました。
先に例外をキャッチしたJOBが例外処理となり、後のJOBがレコードを読み取りできるようになります。 ということは、ロックをキャッチして、再度読み取りをトライ!などという処理をループ扱いにしてしまうと、永久に待ち合うことになってしまいます(おそろしや~)。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39C) PARM('1') 
[USER01] DSPLY   従業員マスタIDを指定して下さい  ?
[USER01] ? 0001 
[USER01] DSPLY   従業員レコード読み取り中 :0001
[USER01] DSPLY    ロックを解除しますか?  (Y/N) 
[USER01] ? N 
[USER02] CALL PGM(TIGEROBJ/SAMPLE39C) PARM('2')
[USER02] DSPLY   部門マスタIDを指定して下さい  ? 
[USER02] ? 10
[USER02] DSPLY   部門レコード読み取り中 :01   
[USER02] DSPLY    ロックを解除しますか?  (Y/N) 
[USER02] ? N

[USER01] DSPLY   部門マスタIDを指定して下さい  ? 
[USER01] ? 10

[USER02] DSPLY   従業員マスタIDを指定して下さい  ?
[USER02] ? 0001 

<<デッドロック発生中!!>>
↓
※先に例外をキャッチしたJOBが例外処理となり、
  後のJOBがレコードを読み取りできるようになる。
(以下省略)

DSPRCDLCK

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400
 ファイル . . . . . . :   EMPLOYEES       メンバー . . . . . . :   EMPLOYEES    
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER01      USER01      701737   保留     UPDATE              
                  USER02      USER02      701944   待機     UPDATE              
                                                                          
-----------------------------------------------------------------------------------

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400
 ファイル . . . . . . :   SECTIONS        メンバー . . . . . . :   SECTIONS     
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER02      USER02      701944   保留     UPDATE              
                  USER01      USER01      701737   待機     UPDATE         

WRKACTJOB

                               活動ジョブの処理 
                                                      
 オプションを入力して,実行キーを押してください。
   2= 変更   3= 保留     4= 終了   5= 処理   6= 解放   7=メッセージ の表示
   8=スプール・ファイル の処理   13= 切断 ...                              
                     現行                 
 OPT  サブシステム/ジョブ  ユーザー       タイプ  CPU %  機能             状況             
      QINTER         QSYS        SBS      .0                   DEQW    
        USER01       USER01      INT      .0  PGM-SAMPLE39C    LCKW             
        USER02       USER02      INT      .0  PGM-SAMPLE39C    LCKW

デッドロックを発生させない定石として、トランザクション処理は、一定の順序でテーブルアクセスを行う」という点と、「読み取り成功するまでループ扱いにしない(例外処理を組み込む)」という2点でしょうか。

(7) 最大レコード待機時間の変更

DB2 for iでは、テーブル構築時に、最大レコード待機時間を指定できます。
デフォルト値が、60秒となっておりますので、よほどの事情が無ければこのままでいいように思います。

すでに運用中のテーブルの場合には、CHGPFコマンドで変更は可能です(取扱い注意! 私の環境では、CHGPF実行後にRPGのリコンパイルは不要でした)。
下記のコマンドでは、最大レコード待機時間を10秒に設定するケースです。

CHGPF FILE(TIGERDB/EMPLOYEES) WAITRCD(10)

(8) SQLでのロック制御

では、SQL文でのロックはどうするのか? ということで、DB2 for iでは「FOR UPDATE WITH 分離レベル」でロック処理ができました。

SELECT * FROM TIGERDB/EMPLOYEES 
 WHERE EMP_CD = '0001'  
   FOR UPDATE WITH RS    

分離レベルについては、データがアクセスされている間、 データが他のプロセスからどのようにロックされ分離されるかを決めるもので、複数指定ができます。きつめのロック、軽めのロックという具合でしょうか・・・。
こちらはJavaEEの場合、APサーバー側で設定し、EJBで自動トランザクション処理を行っているため、個別での操作は経験不足です。
いずれにしましても気をつけたい点は、COMMIT or ROLLBACKを実行するまで、ロックされ続けている点と、WHERE文によっては大量のレコードをロックしてしまう点でしょうか。

以上で、レコードロック検証についてのまとめ終了です。

【AS400】SQLRPGLE-挿入、更新、削除件数を取得する

久しぶりにAS400のSQLRPGLEネタです。

SQLのINSERT文、UPDATE文、DELETE文実行後に、どうやって結果レコード数を取得するのか? という疑問が湧きました。

Javaであれば、下記のようにSQL実行結果の戻り値で結果レコード件数を取得できるのですが・・・。

int updatedCount = stmt.executeUpdate(sql);

SQLCA(SQL Communications area)

SQLRPGLEでは、SQLCA(SQL Communications area)と呼ばれる領域の中から、更新レコード件数を取得できるようです。
SQLCAとは、プログラムとRDMSの間で、統計情報とエラー情報を共有するメモリ領域で、SQLRPGLEは変数定義せずに、SQLCAの変数を参照することができました。

具体的な変数は、SQLERRD配列の3要素目。

SQLにより更新、挿入、削除されたレコード数を「SQLERRD(3)」で取り出します。

その結果を戻り値などで返すパターンが実用的でありましょう。

サンプルSQLRPGLE

EXEC SQL                           
    DELETE TABLE_NAME                  
     WHERE COMPLETED = '1'            
       AND CRT_DATE <= :W@YYMD;      
                                   
CNT = %DEC(SQLERRD(3):7:0);     

【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 円です。