読者です 読者をやめる 読者になる 読者になる

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

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

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

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

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 DokuWiki

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)

Mybatis DB2/400 Java

概要

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とうまく使い分けしていきたいところです!!!

【Thunderbird】 POP3受信済みメールが再受信されてしまう

Mail

メールクライアントで「Mozilla Thunderbird」は、非常に便利で大変重宝させてもらってます!

ただ先日、POP3の受信済みメールが再受信されてしまう現状が発生したので、その仕組みを学んでおきたいと思います。

受信済みメールを管理するファイル

ググってみると、どうやら下記のファイルで一元管理されているようです!

C:\Users\USER_NAME\AppData\Roaming\Thunderbird\Profiles\PROFILE_ID.default\Mail\MAIL_DOMAIN_NAME\popstate.dat

気になる中身を見てみると・・・

# POP3 State File
# This is a generated file!  Do not edit.

*MAIL_DOMAIN_NAME USER_NAME
k 55daa24b@12413 1465888909
k 55daa24b@12428 1465902328
k 55daa24b@12390 1465867308
k 55daa24b@12435 1465943505
k 55daa24b@12420 1465892509
k 55daa24b@12416 1465889508
k 55daa24b@12397 1465873908
:

なんじゃこれは??

どうやら、3桁目から16桁目の合計14桁の文字列がメール1件ごとのキーになっているようです。

直近で届いたメールのソースを確認してみると、キーらしき文字列が3行目にありました!

From - Wed Jun 15 07:31:45 2016
X-Account-Key: account2
X-UIDL: 55daa24b@12435   <<<<<<<<<<<<<<<<<<
X-Mozilla-Status: 0001
X-Mozilla-Status2: 00000000
X-Mozilla-Keys: 

このキーでメールを受信済みかどうかを認識させているのですね。

下記のサイトに、そのキー「X-UIDL」の意味が書かれていました。

@IT - メールヘッダ一覧

X-UIDL : POP利用時のためのユニーク文字列。POPサーバが付加する

なるほど。POPサーバ側で割り振ってくれているというわけですか。

動作検証

おまけで、下記の2点の操作で検証してみました。

(1) popstate.datをオープン状態で、メール手動受信してみる

  • 結果:popstate.datに書き込みできなかったため、同一のメールが再度受信された。

(2) popstate.datから任意の1行を削除して、メール手動受信

  • 結果:削除した行と同一のIDのメールのみ受信された。

今回の問題の結論

ということで、結論です。ソフトのバグ、もしくはその発生時のPCの問題なのでしょう。

  • 何かの影響で、「popstate.dat」の中身がすべてクリアされたか、「popstate.dat」が削除されてしまったのかもしれない。(直接的な原因は不明)
  • POPサーバ側は、メールを貯めいているだけなので、障害の原因であることはゼロといえる。

以上です。

asciiflow:Webサービスで簡易ネットワーク図を描きたい

Network

ネットワーク図をガッチリ描く場合、MS製Visioあたりを使うのでしょうが、簡単な構成や図を描きたい!という場合に、専用のアプリケーションを立ち上げるというのが少々手間だったりもします。

そこで、調べていたら、asciiflow というWebサービスを発見しました!

ASCIIFlow Infinity

以前なら、EXCELを活用したり、様々なフリーソフトで目的を実現することはできましたが、最近はブラウザ1つで済ませたいのが本音。

このツールは、本当に重宝しています!

f:id:no14141:20160606082825j:plain

ポート指定によるPing試験

Network Windows

先日、職場の同僚から教えてもらったツールについて、記録しておきたいと思います。
こういう時に、情報共有の大切さを痛感しますね。ナレッジってやつですか・・・(今更ですが)

ツールの名称は「PsPing」。
プロセスへのpingという意味なのでしょう。

technet.microsoft.com

上記サイトより、ツールを入手! 私が行ったときのVersionは、「PsPing v2.01」でした。

PSToolsフォルダを「C:\tools」などへ複写し、Windows環境変数のPathへそのフォルダを通します。
DOSコマンドで、下記コマンドでポートが通るかの試験が可能になりました。

psping host名(or IP address):port番号

これまでは、TELNETコマンド「telnet host名(or IP address) port番号」で行っていましたが、TELNETコマンドを使えるようにするためには、Windows7の場合、Windowsの機能の有効化または無効化の設定で、「Telnetクライアント」を有効にする必要がありました。

f:id:no14141:20160530185756j:plain

PsPingにしても、プログラムの設置とパスを通す作業が発生しますが、ping試験的な手法でポートの空き状況を確認できるのは、非常にやりやすいと感じました。

PsPing、こちらを積極的に活用していこうと思います!