lsコマンドの使い方。覚えておくと便利なオプションも解説。

Linux

Linuxで作業をおこなう上では、lsコマンドは必須のコマンドになります。
使わない人はいないでしょう。


しかし、「-l」とか「-a」といった超メジャーなオプションのみを使っており、便利なオプションを使いこなしていない人が多いように思います。
今回は、lsコマンドのオプションについて、知っていればちょっと便利なオプションについて紹介していきます。


そもそもですが、lsは「ListSegment」の略です。
日本語では、”セグメントをリスト表示する”でしょうか。


lsコマンドだけ実行すると、カレントディレクトリに存在するファイルやフォルダの一覧を表示します。

$ ls
aaa.txt  folder

q


カレントディレクトリではなく、一覧を表示したいフォルダを指定すれば、指定したフォルダ配下の一覧を表示します。

$ ls  ./folder/
folder2  xyz.txt

以下から各オプションについて説明していくのですが、オプションを複数指定したい場合は、ハイフン(-)の後に使用したいオプションを連続して指定すればよいです。
「-l」と「-a」オプションを同時に使用したい場合は、「ls -la」と記述する、といった感じです。


全部表示する(-a,-A)


全てのファイルを表示する場合は「-a」オプションです。
もしくは「-all」。
通常は表示されない、先頭にピリオド「.」が付くファイルも表示します。
カレントを表現する「.」と、一つ上階層を表現する「..」も表示します。


$ ls -a
.  ..  .env.sh  aaa.txt  folder

小文字の「a」ではなく大文字の「A」を使用すると、「.」「..」は除外された形で表示されます。
もしくは「–almost-all」。


$ ls -A
.env.sh  aaa.txt  folder

バックアップファイルを表示しない(-B)


linuxが生成するバックアップファイルには、末尾に”~”(チルダ)が付きます。
このバックアップをファイルを表示したくない場合は「-B」オプションを使用します。
もしくは「–ignore-backups」。


1.「-B」を付けない場合

$ ls
aaa.txt  bbb.txt  ccc.txt  ccc.txt~  folder

2.「-B」を付けた場合

$ ls -B
aaa.txt  bbb.txt  ccc.txt  folder

出力時の色を指定する(–color)


出力時の表示色を指定する場合は「–color[=WHEN]」を指定します。
WHENには、’auto”always”never’の3パターンが指定できます。
デフォルトは’auto’です。


1.’auto’を使用。

$ ls -l --color=auto

環境設定に定義されている色に従って、表示結果の色付けをおこないます。
環境設定に定義されている表示色は、「$ dircolors -p」で確認できます。


$ dircolors -p
DIR 01;34 # directory
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a

2.’always’を使用

$ ls -l --color=always

常に色表示を有効にします。
例えば、ls の結果をパイプで繋いで絞り込んだ場合、色指定が消えてしまうときがあります。
これを防ぎます。
常に環境変数に定義されている色指定を有効にします。


3.’never’を使用

$ ls -l --color=never

色表示を行いません。


ディレクトリのみを表示する(-d)


ディレクトリのみを表示する場合は「-d」オプションです。
もしくは「–directory」。


正確には、ディレクトリのみを表示する場合は「-d」オプションを付けるだけではなく、もう一工夫が必要です。
以下のようにします。
カレントディレクト配下のディレクトリのみを表示する場合です。


$ ls -d */
folder1/  folder2/

様々なソート方法


ファイル・ディレクトリのソートについて、様々なソートをおこなうことができます。


1.ディレクトリ昇順でソート(-f)

「ディレクトリ昇順」とは、Linuxのファイルシステムが設定する順番です。
なので、ソートせずに表示していることと同等となります。


$ ls -f
.  folder1  xyz.txt  ..  folder2

2.逆ソート(-r)

ソート順を逆にしてソートをおこないます。
「-r」もしくは「–reverse」になります。


$ ls
aaa.txt  bbb.txt  ccc.txt  ccc.txt~  folder  link
$ ls --reverse
link  folder  ccc.txt~  ccc.txt  bbb.txt  aaa.txt

3.ファイルサイズでソート(-S)

ファイルサイズの降順でソートをおこないます。


$ ls -lS
total 20
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx yyy   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt

4.更新日時でソート(-t)

更新日時の降順でソートをおこないます。


$ ls -lt
total 20
lrwxrwxrwx 1 oops.jp-sakusaku LolipopUser   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt
drwxr-xr-x 4 oops.jp-sakusaku LolipopUser 4096 2020-01-05 01:26 folder
-rw-r--r-- 1 oops.jp-sakusaku LolipopUser   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 oops.jp-sakusaku LolipopUser   20 2020-01-05 00:27 ccc.txt~
-rw-r--r-- 1 oops.jp-sakusaku LolipopUser   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 oops.jp-sakusaku LolipopUser   12 2020-01-05 00:26 aaa.txt

5.アクセス日時でソート(-u)

アクセス日時の降順でソートをおこないます。


$ ls -lu
total 20
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 17:06 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 17:06 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-12 12:02 folder
lrwxrwxrwx 1 xxx yyy   41 2020-01-12 12:02 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

6.ファイルの拡張子でソート(-X)

ファイルの拡張子でソートをおこないます。
拡張子のアルファベット順になります。


$ ls -lX
total 20
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx yyy   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt
-rw-r--r-- 1 xxx yyy    0 2020-01-12 14:19 aaa.exe
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~

ファイルタイムスタンプを秒まで表示(–full-time)


通常、ファイルのタイムスタンプは分までしか表示されないですが、秒まで表示することもできます。
「–full-time」を指定します。


$ ls --full-time
total 20
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26:52.42195666 +0900 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27:28.51452186 +0900 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28:28.96779361 +0900 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27:47.82928918 +0900 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26:07.22816214 +0900 folder

オーナーを表示しない(-g)、グループを表示しない(-G)


通常は、オーナーとグループが表示されますが、それぞれを表示しない出力も可能です。

オーナーを表示したくない場合は「-g」オプションを指定します。


$ ls -la
total 20
-rw-r--r-- 1 yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 yyy 4096 2020-01-05 01:26 folder

グループを表示したくない場合は「-G」オプションを指定します。
もしくは「–no-group」。


$ ls -lG
total 20
-rw-r--r-- 1 xxx   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx 4096 2020-01-05 01:26 folder

また、オーナーのみを表示したい場合は「-o」オプションを指定でもOKです。


$ ls -o
total 20
-rw-r--r-- 1 xxx   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

1024の累乗ではなく100の累乗でサイズ表示する(–si)


通常、1024の累乗でファイル表示をおこないますが、1000の累乗でサイズ表示。


1.1024の累乗

$ ls -la
total 28
drwxr-xr-x 3 xxx yyy 4096 2020-01-05 00:28 .
drwx-----x 6 xxx yyy 4096 2020-01-05 00:27 ..
-rw-r--r-- 1 xxx yyy    0 2019-11-19 01:28 .env.sh
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder

2.1000の累乗

$ ls -la --si
total 29k
drwxr-xr-x 3 xxx yyy 4.1k 2020-01-05 00:28 .
drwx-----x 6 xxx yyy 4.1k 2020-01-05 00:27 ..
-rw-r--r-- 1 xxx yyy    0 2019-11-19 01:28 .env.sh
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4.1k 2020-01-05 01:26 folder

ファイルのインデックス番号を表示する(-i)


Linuxにファイルに付与しているインデックス番号を表示することができます。
「-i」もしくは「–inode」になります。


$ ls -li
total 20
25566879 -rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
25566875 -rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
25566878 -rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
25566868 -rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
25566958 drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
25559629 lrwxrwxrwx 1 xxx yyy   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

リスト形式で表示する(-l)


ファイル名のみの表示ではなく、オーナー・グループ・サイズといったファイルの属性情報を含めてリスト形式で表示します。


$ ls -l
total 20
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx yyy   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

キロバイトでファイルサイズを表示する(-k)


ファイルサイズをバイト表示ではなく、キロバイト表示します。


$ ls -lk
total 20
-rw-r--r-- 1 xxx yyy 1 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy 1 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy 1 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy 1 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx yyy 1 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

シンボリックリンクはリンク先パスを表示しない(-L)


シンボリックリンクのリンク先パスを表示したくない場合は「-L」オプションを使用します。


$ ls -lL
total 24
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 link

ファイルリストをカンマ区切りで表示する(-m)


ファイルリストをカンマ区切りで表示することができます。
「-m」オプションです。
出力結果を加工する際に便利です。


$ ls -m
aaa.txt, bbb.txt, ccc.txt, ccc.txt~, folder, link

オーナーとグループを文字列ではなくID表示(-n)


オーナーとグループはID(数値)をもっています。
「-n」オプションを使用すると、ID表示をおこなうことができます。


$ ls -n
total 20
-rw-r--r-- 1 1421071 1000   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 1421071 1000   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 1421071 1000   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 1421071 1000   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 1421071 1000 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 1421071 1000   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

ダブルクォーテーションでファイル名を囲む(-Q)


ファイル名をダブルクォーテーションで囲むことができます。
「-Q」もしくは「–quote-name」になります。


$ ls -Q
"aaa.txt"  "bbb.txt"  "ccc.txt"  "ccc.txt~"  "folder"  "link"

再帰的にlsコマンドを実施(-R)


lsコマンドを実施するフォルダの中に存在するフォルダに対してもlsコマンドを実行し、再帰的に結果を表示します。
「-R」もしくは「–recursive」になります。


「再帰的」に結果を表示するので、フォルダの中にフォルダ(フォルダがネスト)が存在していて、全てのファイルを一覧表示する時に便利です。


$ ls -lR
.:
total 20
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:26 aaa.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 bbb.txt
-rw-r--r-- 1 xxx yyy   12 2020-01-05 00:28 ccc.txt
-rw-r--r-- 1 xxx yyy   20 2020-01-05 00:27 ccc.txt~
drwxr-xr-x 4 xxx yyy 4096 2020-01-05 01:26 folder
lrwxrwxrwx 1 xxx yyy   41 2020-01-05 20:41 link -> /home/users/0/oops.jp-sakusaku/ls/aaa.txt

./folder:
total 8
drwxr-xr-x 2 xxx yyy 4096 2020-01-05 01:26 folder1
drwxr-xr-x 2 xxx yyy 4096 2019-11-19 01:13 folder2
-rw-r--r-- 1 xxx yyy    0 2019-11-19 01:13 xyz.txt

./folder/folder1:
total 0

./folder/folder2:
total 0

ファイル名だけを行表示(-1)


あんまり知っている人が少ないかと思いますが、ファイル名だけを表示することができます。
「-1」オプションになります。


$ ls -1
aaa.exe
aaa.txt
bbb.txt
ccc.txt
ccc.txt~
folder
link


XSLファイルを使って、SpringMVCでCSV出力をおこなう方法

SpringFrameWork

WEBアプリケーションにおいて、サーバでCSVを作成して、クライアント(ブラウザ)にダウンロードする、といった要件は頻繁に発生します。


SpringMVCでXSLファイルを上手に使えば、簡単にCSVファイルの作成とダウンロードの機能を作成することができます。


今回は、SpringMVCでCSV出力をおこなう方法を紹介します。
Javaで自力でゴリゴリ作成する方法ではなく、XSLファイルを使ってCSV出力おこなう方法となります。


XSLファイルとは、XMLファイルのスタイルを定義しているファイルになります。
出力するCSVファイルのレイアウトをXSLファイルに定義してあらかじめサーバ上に格納しておき、CSVファイルを出力する際に使用します。


XSLファイルとは何か?


XSL(eXtensible Style Language)ファイルは、XML 文書を他の文書タイプに変換したり、出力をフォーマットする際に使用できるスタイルシートです。
Styleという言葉が使われていることからもわかるように、XMLのスタイルシートがXSLになります。


このXSLファイルで便利なのが、テンプレート関数というちょっとしたロジックをXSLファイル内で使用することができる点です。
例えば、以下のような関数です。


  • テンプレート関数の定義の仕方(xsl:template、xsl:param)
  • 引数の値の参照の仕方($引数名)
  • 条件分けの仕方(xsl:if、xsl:choose)
  • XSLT関数(not、contains、substring-before、substring-after)
  • XSLT関数の使い方(<xsl:value-of select=”XSLT関数 (引数…)” />)
  • 定義されたテンプレート関数の呼び出し方(xsl:call-template、xsl:with-param:再帰呼び出しの箇所)

上記のような関数もあらかじめXSLファイル内に定義することができます。


以下に、よく使う関数を紹介していきます。


文字列の連結


concat(str1,str2,str3,・・・)
str1、str2、str3、を連結


文字列の調査


contains(str, substr)
strの中のsubstrを検索。
存在する場合はtrueを通知


数字のフォーマッティング


format-number(number, format)
format-number(number, format, formatType)
numberの数字を、formatで指定されたフォーマットに変換して出力します。


空白の除去


normalize-space(str)
strから空白を除外した文字列を返却します


先頭文字のチェック


starts-with(str, substr)
strがsubstrで始まっているかをチェックします。
始まっている場合はtrueを通知。


文字列への変換


string(val)
valを文字列に変換します。


文字列の長さを取得


string-length(str)
strの長友を取得します。


文字列の抜き出し


substring(str, start)
substring(str, start, length)
strに対して、start位置からlengthの長さの文字列を取得します。


文字列の抜き出し(前文字列の取得)


substring-before(str, substr)
strからsubstrの文字列を検索し、見つかったらその前の文字列を取得します。


substring-after(後文字列の取得)


substring-after(str, substr)
strからsubstrの文字列を検索し、見つかったらその後の文字列を取得します。


文字列の置換


translate(str, src, dest)
strに含まれるsrcという文字列をdestに変換します。


切り上げ


ceiling(val)
valの切り上げをおこないます。


ノード数の取得


count(node)
XML内に含まれるnodeで指定されたノードの数を取得します。

<a>
  <b></b>
  <b></b>
</a>

この例で、’b’のノード数は2です。


切り下げ


floor(val)
valの切り下げをおこないます。


数値に変換


number(any)
anyを数値に変換します。


四捨五入


round(val)
valを四捨五入します。


加算


sum(node)
nodeで指定されている値を加算し、数値で返却します。


<a>
    <b>10</b>
    <b>10</b>
</a>

sum(‘b’)の返却値は20です。


現在のノード取得


current()
現在位置のノードを取得します。


現在のノードの最下層ノードを取得


last()


nodeのローカル名(名前空間を外したもの)を取得


local-name(node)


現在のノード名を取得


name()


現在のノードの名前空間URIを取得


namespace-uri()


現在のノード位置を数値で取得


position()


boolean型への変換


boolean(any)
anyをboolean(true or false)に変換します。


falseの出力


false()
boolean型のfalseを出力します。


trueの出力


true()
boolean型のtrueを出力します。


否定


not(boolean)
booleanの否定値をbooleanで出力します。


ドキュメントルートの取得


document(uri)
uriのドキュメントルートを出力します。


xsltで解釈できるxlstノードかを判定


element-available(str)
返り値はbooleanです。


xsltで解釈できる関数かを判定


function-available(func)


ノードに固有の値を割り当て


generate-id(node)


該当するidのノードを取得


id(any)


キー値を返却


key(str any)
str内のハッシュに、anyで指定される表現を返却します。


XSLT固有値の取得


system-property(str)
str内のXSLT固有の値を取得します。


<!entity>タグ値を取得


unparsed-entity-uri()
<!entity>タグで指定されている値を取得します。


CSV出力のサンプルソース


CSV出力のサンプルソースを紹介します。


SpringMVCのコントローラクラスに実装することを想定しています。


@RequestMapping(params = "csvOut", produces = "text/csv")
public ModelAndView makeCsvOut(HttpServletResponse response, Form form)
        throws JAXBException, IOException {

    // DBから出力する情報を取得してDTOに格納
    CsvOutDTO dto = this.service.geCsvInfo(form);

    // DTOをXMLの形式に変換
    JAXBContext context = JAXBContext.newInstance(CsvOutDTO.class);
    Writer writer = new StringWriter();
    context.createMarshaller().marshal(dto, writer);

    // モデルに変換したXMLを格納
    String xslName = "CsxXsl";
    ModelAndView modelAndView = new ModelAndView(xslName);
    modelAndView.addObject(
        "xmlSource", new StringReader(writer.toString()));

    // HTTPレスポンスヘッダを作成
    response.setHeader("Content-Disposition", 
        "attachment; filename=csvFile.csv, "UTF-8"));

    // BOMを先頭に付加
    response.getOutputStream().write(CxFix.BOM);

    return modelAndView;
}

データベースからCSV出力する内容を取得することを想定しています。
「geCsvInfo」の中身は消略していますが、このメソッドの中身がDBアクセスしてDTOのインスタンスを返却するイメージです。


その後はサンプルの通りで、DTOを「JAXBContext」に渡せば、CSVファイルの出来上がり


次にXSLファイルのサンプルです。

上記のJavaソースで”CsvXsl”という名前で定義されたXSLファイルのサンプルになります。


<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output method="text" omit-xml-declaration="yes" 
        byte-order-mark="yes" encoding="UTF-8" />
  <xsl:template match="/csvList">
    <xsl:call-template name="template_csvList" />
  </xsl:template>
    <xsl:template name="template_csvList">
        <!-- ヘッダ情報 -->
        <xsl:text>カラム1,</xsl:text>
        <xsl:text>カラム2,</xsl:text>
        <xsl:text>カラム3,</xsl:text>
        <xsl:text>
    </xsl:text>
        <!-- データ情報 -->
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col1, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col2, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(
                translate(translate(./entity/col3, '"', ' '), 
                ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
    </xsl:template>
</xsl:stylesheet>

「entity」とは、前のJavaコードに記載していた、「CsvOutDTO」で使用しているエンティティクラスです。
1行データ毎に1エンティティのレコードを保持します。


CSVファイルは1行目は各項目名の説明で、実際のデータ出力は2行目からです。
XSLファイルで2行目以降を動的にすることで、「CsvOutDTO」に格納されているエンティティの数だけCSVデータ出力がおこなわれます


関数は「translate」しか使っていません。
ダブルクオテーションと改行コードを半角スペースに変換しています。


まとめ


いかがでしたでしょうか?


SpringMVCでCSV出力をおこなう場合に、参考にしてもらえればと思います。
特に、XSLの関数は助かります。
ちょっとした処理をJavaでおこなう必要がないので。


それではまた!


Javaで動画変換をおこなう。JAVE(Java Audio Video Encoder)でMP4に!

Javeでの画像変換

今回は、Javaで動画変換をおこなう方法を紹介します。
使用するライブラリは『JAVE』です。



あんまりメジャーではないライブラリのようですね。
でも、使い方がシンプルで、かつ、世の中に出回っているほとんどの動画ファイル形式に対応しているので、筆者は気に入っています。


具体的には、『mp4』に変換することを前提にサンプルプログラムを紹介していきます。


mp4に変更するのは特に理由はないですが、あえて理由をあげるとすれば、”圧縮率が一番よさそうだから”です。


検証環境


筆者が動作検証した環境は以下になります。


  • Windows7
  • Java1.8.06
  • JAVE-1.0.2

JAVE(Java Audio Video Encoder)について


JAVE(Java Audio Video Encoder)とは何か?ということになるのですが、公式サイトの説明を日本語化すると以下になります。


JAVE(Java Audio Video Encoder)ライブラリは、ffmpegプロジェクトのJavaラッパーです。
開発者は、JAVEを利用して、オーディオファイルとビデオファイルをある形式から別の形式にトランスコードできます。
例では、AVIファイルをMPEGファイルにトランスコードでき、DivXビデオストリームを(YouTubeのような)Flash FLVファイルに変更でき、WAVオーディオファイルをMP3またはOgg Vorbisに変換できます。
オーディオトラックとビデオトラックをトランスコードする場合、ビデオのサイズを変更したり、サイズや比率を変更したりできます。
JAVEでは、他の多くの形式、コンテナ、および操作がサポートされています。


動画操作で有名なライブラリは「ffmpeg」ですが、JAVEはffmpegをラッピングしているライブラリみたいです。


説明文をみるとように、様々なファイル形式に対応しています。
ベースはffmpegなので、当然っちゃ当然ですね。


JAVEでMP4に変換する


一通りの動画について変換を試してみました。
一覧でまとめました。
動画は基本的に圧縮されます。


拡張子

mp4変換時の圧縮率

.avi

70%

.mp4

70%

.m2ts

99%

.ts

27%

.mpeg

27%

.mpg

29%

.mkv

29%

.wmv

41%

.webm

42%

.ogm

180%

.mov

96%


JAVEを使用した動画変換処理


動画変換処理のサンプルコードを紹介します。
使い方は簡単で、変換処理の条件(コーデック、ビットレート、など)を属性情報として設定し、変換処理を実行します。
様々な形式に変換が可能(っぽい)ですが、MP4形式への変換サンプルコードになります。


import it.sauronsoftware.jave.AudioAttributes;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncoderException;
import it.sauronsoftware.jave.EncodingAttributes;
import it.sauronsoftware.jave.InputFormatException;
import it.sauronsoftware.jave.VideoAttributes;
import it.sauronsoftware.jave.VideoSize;

import java.io.File;

public class JaveManage {

    private JaveManage() {
    }

    public static void main(String[] args) {
        System.out.println("start.");

        try {
            // 動画フォーマットを設定
            EncodingAttributes attrs = 
                new EncodingAttributes();

			// 動画圧縮変換時フォーマット
            attrs.setFormat("mp4");

            // 音声の変換情報を設定
            AudioAttributes audio = new AudioAttributes();
			// 動画圧縮変換時オーディオコーデック
            audio.setCodec("aac");
			// 動画圧縮変換時オーディオビットレート
            audio.setBitRate(256000);
			// 動画圧縮変換時オーディオチャンネル
            audio.setChannels(1);
            audio.setSamplingRate(88200);
			// 動画圧縮変換時オーディオサンプリングレート
            attrs.setAudioAttributes(audio);

            // 動画の変換情報を設定
            VideoAttributes video = new VideoAttributes();
			// 動画圧縮変換時ビデオコーデック
            video.setCodec("libx264");
			// 動画圧縮変換時ビデオビットレート
            video.setBitRate(2700000);
			// 動画圧縮変換時ビデオフレームレート
            video.setFrameRate(30);
			// 動画圧縮変換時ビデオ幅,動画圧縮変換時ビデオ高さ
            video.setSize(new VideoSize(1296, 768));
            attrs.setVideoAttributes(video);

            // 変換を実施
            File source = new File("source.wav");
            File dest = new File("source.mp4");
            Encoder encoder = new Encoder();
            encoder.encode(source, dest, attrs);

            // 変換結果を検証
            if (!dest.exists() || dest.length() == 0) {
                System.out.println("encode failer.");
            }
        } catch (EncoderException e) {
            System.out.println("Occured EncoderException.");
        } catch (Exception e) {
            System.out.println("Occured Exception.");
        }
        System.out.println("end.");
    }
}

設定値はソースコードに直接記述していますが、以下のパラメータを設定します。


  • オーディオビットレート:256000
  • オーディオチャンネル:1
  • サンプリングレート:88200
  • ビデオビットレート:2700000
  • フレームレート:30
  • サイズ:height:1296 width:768

まとめ


いかがでしたでしょうか?


JAVEはあんまりメジャーなライブラリではないようですね。
ググってもあんまり情報がないです。


使ってみた直観としては、使いやすい印象です。
「パラメータを設定して変換」っていうだけですからね。


皆さんが使う際に、参考にして頂ければと思います。


それではまた!



SpringMVCでトランザクション管理をおこなう。アノテーションとXMLの設定

SpringFrameWork

SpringMVCでは、アノテーションの設定をおこなうことだけでDBトランザクションの管理をおこなうことができます。


今回は、SpringMVCでトランザクション管理をおこなう方法について説明していきます


SpringMVCでは、トランザクションのスタート位置をアノテーションで指定することにより、トランザクションの開始を定義することができます
トランザクションのスタート位置を定義することにより、どのDB処理を一つのトランザクションとするかをコントロールすることができるということになります。


しかし、こういった制御をおこなうためには、アノテーションの準備とXMLへの設定が必要になります。


トランザクションとは


トランザクションとは何でしょうか?
ここでのトランザクションとは、“データベースのトランザクション”になります。
直訳すると『取引』ですね。


データベース処理では、1回のSQL発行ではデータの整合性が取れない場合が多々あります。
例えば、あるデータをシステムに登録しようとした場合、複数のテーブルにレコードの登録と更新が必要な場合。
テーブルが3つあり、あるデータをシステムに登録しようとした場合、テーブルAにはレコードの登録、テーブルBにはレコードの更新、テーブルCにもレコードの更新が必要だとします。
この場合、テーブルBのレコード更新が失敗したとすると、テーブルAのレコード登録は無かったことにしたいです。
具体的には”コミットしたくない”。


コミットは、全てのDB処理が完了してから(テーブルCへのレコード更新)おこないたいです。


トランザクションの管理

各テーブルへの処理を終わったタイミングでコミットしてしまうと、途中でエラーが発生した場合に、データの整合が取れていない状態でデータができあがってしまいます。
これを避けるために、3つの処理をまとめて考え、3つの処理が全て完了してからコミットします。
途中で、エラー等が発生した場合はロールバックして元に戻します。


この、複数のDB処理を一つにまとめることをトランザクションと呼びます。


どの処理をまとめて一つのトランザクションにするかを考えるのは、安定したシステムを作るのにきちんと考えておきたいポイントです。


common-database-context.xmlの定義


SpringMVCでトランザクション定義をおこなう場合は、「common-database-context.xml」への定義が必要になります。


まずはトランザクション管理するための基本定義です。
トランザクション管理するためのクラス定義と、DBコネクションのデータソース設定です。


「DataSourceTransactionManager」を使用可能な状態にし、「DataSourceTransactionManager」が使用するデータソースを定義します。


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    <property name="defaultTimeout" value="10" />
</bean>

プロパティ値として、タイムアウト値も定義します。
10秒です。


通常、SpringMVCで開発したアプリケーションは、Tomcat等のWEBアプリケーション上に配置することになります。
WEBアプリケーションが生成したコネクションプール内のDBコネクションを、SpringMVCが取得してアプリケーションが使用するのが通常になります。


次に、トランザクションを有効にするパッケージの定義とアノテーションの定義。
以下のように定義します。


<aop:config proxy-target-class="false">
    <aop:advisor advice-ref="txAdvice" pointcut="execution(public * jp.co.xxx..*Controller.*(..)) && @annotation(jp.co.xxx.annotation.DbTransaction)" />
</aop:config>

「pointcut」に、トランザクション管理対象のパッケージを定義します。
パッケージを「execution」括ります。


かつ、「@annotation」に、トランザクション開始を意味するアノテーション定義を記載します。
「jp.co.xxx.annotation.DbTransaction」が、アノテーションのJavaクラスになります。


トランザクション管理するアノテーション設定


ここまで準備した「common-database-context.xml」への定義とアノテーション定義で、トランザクション管理をおこなうことが可能になりました。
アノテーションを定義したメソッドがトランザクションの開始位置で、そのメソッドが完了したら自動的にコミットされます。
仮に、メソッドの途中で例外が発生してメソッドが中断された場合はロールバックされます。(コミットされません)
定義としては以下になります。


@DbTransaction
    public void xxx() {

通常は上記でトランザクション管理ができるのですが、個別にトランザクションの開始やコミット・ロールバックをおこないたい場合があります。
独自のトランザクション管理についても可能です。
以下のように、自分でイベントをプログラミングすればよいです。


// トランザクション管理を読み込み
@Autowired(required = false)
private PlatformTransactionManager transactionManager;

public void xxx() {

    // 処理開始
    TransactionStatus status = startTransaction();

        ・
        ・
        ・

    if (isDone) {
        // コミット
        transactionManager.commit(status);
    } else {
        // ロールバック
        transactionManager.rollback(status);
    }
}

まとめ


いかがでしたでしょうか?


SpringMVCでは、アノテーションの準備をするだけでトランザクション管理をおこなうことが可能になります。


システムの方針によるかと思うのですが、トランザクションの単位をどれにするかもいろいろです。
1つのコントローラメソッドについて1トランザクションにするか、サービスのメソッドについて1トランザクションにするか、
といった悩みどころもあります。


システムでルールを統一した上で、SpringMVCで開発を進めていくのがいいかと思います。


それではまた!



SpringMVCでアノテーションを使ったり、自分で作ってみたりする

SpringFrameWork

皆さん、普段は普通にアノテーションを使ってコーディングしているかと思います。
筆者は、アノテーションについて、Java開発初期時は特に重要に考えていませんでした。

ただの、JavaDocに記述するためのキーワードぐらい?程度です。


でも、アノテーションにロジックを組み込めることを知ってから、自作アノテーションは重宝しています。


今回は、アノテーションを自分で作成する方法を紹介していきます。


大規模なシステムとなり開発メンバが増えれば増えるほど、処理の統一の意味であらかじめ準備されているアノテーションではなく、システム固有のアノテーションを作りたくなってきます。
自分でアノテーションを作って、使用する処理のヘッダに組み込んでいく、といった形です。


今回は、自前でアノテーションを作る方法を紹介していきます。


アノテーションとは


アノテーションには、大きく分けて以下の3つがあります。


  • 名前だけでデータのないマーカー 例:@Override、@Deprecated
  • データをひとつ持つ単一アノテーション 例:@SuppressWarnings
  • 複数のデータをもつフルアノテーション

アノテーションは、メソッドのコメントに「@(アットマーク)」を付けることで有効になります。
当然ながら、ここに書いたアノテーションはJavaDocに出力されます。


日本語としては『注釈』です。
『注釈』の説明としては以下になります。


  • 語句や文章の意味をわかりやすく解説すること。また、それをした文。 「古典を-する」 「 -を加える」
  • 補足的な説明。

そもそもアノテーションを記載する意味ですが、要は、”複数人でプログラムを開発する時に、コーディングのやり方を統一するため”と言ってよいかと思います。


複数人で開発を同時進行する場合、コーディング規約を定めていたとしても、どうしても人によってそれぞれのプログラムが出来上がってしまうことがあります。


その、人による差をゼロにすることはできないですが、なるべく差を小さくしていくための仕組みの一つがアノテーションです。


例えば、「@Override」。
「@Override」を付ける意味は、”このメソッドは親メソッドのオーバーライドメソッドだよ。”という『注釈』です。
このアノテーションを付けた場合、親メソッドに同メソッドが存在しない場合はエラーになります。


開発チーム内で、こういったルールを設けていれば、勝手にオーバーライドメソッドを作る確率が減ります。
※あくまで、”確率が減る”だけですが。


代表的なアノテーション


SpringMVCであらかじめ準備されているアノテーションについて紹介しておきます。
代表的なやつ、です。


@Autowired

 

自動注入(Dependency Injection)する際に使用する

 

@Autowired

 

自動注入(Dependency Injection)する際に使用する

 

@Component

 

自動注入(Dependency Injection)して使いたいクラスに付与

 

@Service

 

サービスクラスに付与する

 

@Transactional

 

トランザクション境界を定義する際に付与

 

@RestController

 

APIのコントローラに付与

 

@Controller

 

ページのコントローラに付与

 

@Validated

 

バリデーションを自動的に実施

 

@Min

 

最少値チェック

 

@Max

 

最大値チェック

 

@Size

 

桁数チェック

 

@Pattern

 

正規表現チェック

 

 


アノテーションを自分で作る


それではアノテーションを自分で作ってみましょう。
アノテーションを作るには、以下の3つを準備すればよいです。


アノテーションの定義


「@interface」をつけて、アノテーション定義をおこないます。
ここでは、「anotationAccess」というアノテーションを定義しています。


public class anotationControl {
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface anotationAccess {
    }
}

アノテーションのロジック部


アノテーションが実行された際のロジックを記述するクラスを準備します。
ここでは、「anotationLogic」というメソッドを準備しています。


public class anotationLogic {

    private anotationLogic() {
    }

    public void anotationAccessLogic() {

        ※アノテーションの中身のロジック。ここに書く。

    }
}

定義とロジックの紐付け


アノテーションの定義とロジックの紐つけをおこないます。
定義は「servlet-context.xml」におこないます


ここまで作成した定義とロジックであれば、以下のような紐つけになります。


<aop:config>
  <aop:aspect id="ana" ref="anotationAccess">
    <aop:pointcut id="anaPointcut" expression="@annotation(jp.co.xxx.anotationLogic)"/>
      <aop:before pointcut-ref="racPointcut" method="anotationAccessLogic" />
    </aop:aspect>
</aop:config>

「jp.co.xxx.anotationLogic」は、システム独自のパッケージになります。
こうすることで、システム内で自作のアノテーションが使用できるようになります。


アノテーションの使い方は、あらかじめ準備されているアノテーションを使う場合とまったく同じです。


@anotationAccess
public xxx method() {

まとめ


いかがでしたでしょうか?
アノテーションを使ったり、自分で作る際の参考にしてもらえればと思います。


それではまた!



SpringSecurityを使ったセッション制御。同時ログインを許可しない

SpringSecurity

今回は、SpringSecurityを使っての同時ログイン制御について説明します。


SpringSecurityを使った場合、セッションの作成や制御はSpringSecurityが自動でやってくれますが、「applicationSecurity.xml」で然るべき定義をおこなえば、同時ログイン制御も簡単におこなうことができます。



同時ログイン制御は、システムによって仕様が異なるかと思います。
同じユーザIDで複数ユーザでのログインを許可しているシステムもあれば、同じユーザIDで複数ユーザでのログインを許可しないシステムもあります。


こういった制御は、自前で制御を作るのではなく、「applicationSecurity.xml」の定義で制御可能です。


SpringSecurityの基本的な使い方


SpringSecurityの基本的な使い方については、以下の記事を参照してください。



SpringSecurityを使えば、ログイン機能があるシステムで必要な以下の制御が可能になります。


  • ログイン認証
  • セッション管理
  • 不正アクセスに対する制御
  • ワンタイムトークン

同時ログイン制御で必要な「applicationSecurity.xml」の定義


基本的な「applicationSecurity.xml」の定義は以下になります。
同時ログインを許可していないシステムでの定義例になります。


<bean id="sessionStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" >
  <constructor-arg index="0">
    <list>
      <bean class="jp.co.xxx.MyConcurrentSessionControlAuthenticationStrategy">
        <constructor-arg index="0" ref="sessionRegistry" />
        <property name="maximumSessions" value="1" />
        <property name="exceptionIfMaximumExceeded" value="false"/>
      </bean>
    </list>
  </constructor-arg>
</bean>

同時ログインを許可しないために必要なプロパティ設定について解説していきます。


最大セッション数 – maximumSessions


1ユーザが許可する最大セッション数になります。
ここを1にすると、1ユーザが許可する最大セッション数は1になるので、同時ログインは許可しないということになります。


例えば、この値に「100」を定義すると、同じユーザIDでログインを許可する最大セッション数は100になります。
つまり、同時ログインを100セッションまで許可する、ということになります。


セッション数が最大を超えた場合に例外を発生させる – exceptionIfMaximumExceeded


「maximumSessions」の数を超えた場合、例外を発生させるかどうか?の定義になります。
trueだと例外を発生させ、falseだと例外を発生させない。


上記の「applicationSecurity.xml」の定義では、例外は発生させたくないのでfalseにしています。


Javaで制御をおこなう – ConcurrentSessionControlAuthenticationStrategy


XMLに定義されていますが、「MyConcurrentSessionControlAuthenticationStrategy」で個別制御をおこなうことができます。
「MyConcurrentSessionControlAuthenticationStrategy」は「org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy」
を継承したクラスになります。


具体的には、以下のクラスを準備して個別制御をおこないます。


import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;

public class MyConcurrentSessionControlAuthenticationStrategy extends
        ConcurrentSessionControlAuthenticationStrategy {

    private SessionRegistry mySessionRegistry;

    public MyConcurrentSessionControlAuthenticationStrategy(
            SessionRegistry sessionRegistry) {
        super(sessionRegistry);
        this.mySessionRegistry = sessionRegistry;
    }

    @Override
    public void onAuthentication(Authentication authentication,
            HttpServletRequest request, HttpServletResponse response) {

        //-- 個別処理をこの中に書く --//

    }
}

「onAuthentication」は、認証が成功した場合に実行されるメソッドになります。
個別処理が必要な場面とは以下になります。
全て、同時ログインを許可しない「applicationSecurity.xml」の定義をおこなった場合の制御です。


  • ある条件に一致するユーザは、同時ログイン制御を許可させる。
  • 『後勝ち』の制御をおこなう。

『後勝ち』の制御はスペシャルですね。
既にログインしているユーザのセッションを削除する処理が必要になります。


以下のような実装が必要になります。


// 同一ユーザでのセッションリストを取得
List<SessionInformation> sessions = this.mySessionRegistry
    .getAllSessions(authentication.getPrincipal(), false);

 // 許可するセッションの数を取得
int allowedNum = getMaximumSessionsForThisUser(authentication);

// セッションリストのうち、ログインしたユーザだけを有効にし、
// 他セッションを無効にする
allowableSessionsExceeded(sessions, allowedNum, this.mySessionRegistry);

まとめ


いかがでしたでしょうか?


SpringSecurityで同時ログイン制御が簡単におこなうことができることがわかって頂けたかと思います。


「applicationSecurity.xml」の定義だけではなく、SpringSecurityは必ずスペシャルな処理を個別に実装することが可能です。
「applicationSecurity.xml」の定義だけでやり切る(やり切れる)部分と、スペシャルに実装する部分を見極めて実装することが大事かと思います。


それではまた!



JavaでSocket通信をおこなう。サーバとクライアントのサンプルコード。

javaソケット

Javaでソケット通信を、自前で開発しなければいけない事はあるかと思います。
ソケットを待ち受ける側が常駐プロセスとして確立させ、ソケットの送信を常に待ち受けている状態。
ソケットを待ち受ける側がサーバとなり、ソケットを送る側がクライアント側になりますね。


今回は、ソケット通信のサーバ側とクライアント側のサンプルソースコードを紹介いたします。
ソケットの送受信のみのサンプルコードになりますので、システム固有の処理については別途組み込んで使ってください。


環境情報


今回のサンプルコードのビルド環境、実行環境は以下になります。


  • Java12.0.1

Javaの標準ライブラリしか使っていないです。
Apache等の拡張ライブラリ(JAR)は必要なし、になります。


サンプルプログラムの機能


サーバのクラス名は「SocketReceive」
クライアントのクラス名は「SocketSend」


クライアントは、クライアント上に存在するファイルである「client_send.txt」をストリーム上に読み込みます。
クライアントの「SocketSend」は、サーバの「SocketReceive」に接続して「client_send.txt」の内容を送信します。
「SocketReceive」は受信した内容を「server_recv.txt」にファイル出力します。


「SocketReceive」は、ファイル出力が終わった後にまったく逆のことをおこないます。
「SocketReceive」は、「server_send.txt」をストリーム上に読み込み「SocketSend」に送信します。
「SocketSend」は受信した内容を「cient_rect.txt」に出力します。


送受信プログラムの概要

ソケット通信プログラム

クライアントプログラム – ソケット送信


クライアントプログラム(送信側)のサンプルは以下になります。
上の説明での「SocketSend」になります。


import java.sql.SQLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class SocketSend {

    private SocketSend() {
    }

    public static void main(String[] args) {

        Socket socket = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try {
            System.out.println("start.");

            // 出力ソケットを作成
            socket = new Socket("localhost", 8100);

            // 入出力ストリームを準備
            fis = new FileInputStream("client_send.txt");
            fos = new FileOutputStream("client_recv.txt");

            // 入力ストリームの内容を全て送信
            int ch;
            OutputStream output = socket.getOutputStream();
            while ((ch = fis.read()) != -1) {
                output.write(ch);
            }
            output.write(0);

            // サーバからのレスポンスを受信し、ファイルに出力
            InputStream input = socket.getInputStream();
            while ((ch = input.read()) != -1) {
                fos.write(ch);
            }
        } catch (SocketException e) {
            System.out.println("occured SocketException.");
        } catch (Exception e) {
            System.out.println("occured Exception.");
        } finally {
            try {
                socket.close();
                fos.close();
                fis.close();
            } catch (IOException e) {
                System.out.println("occured IOException.");
            }
            System.out.println("end.");
        }
    }
}

サーバプログラム – ソケット受信


次はサーバプログラム(受信側)のサンプルです。
上の説明での「SocketReceive」になります。


import java.sql.SQLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class SocketReceive {

    private SocketReceive() {
    }

    public static void main(String[] args) {

        Socket socket = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try {
            System.out.println("start.");

            // 入出力ストリームを準備する
            fos = new FileOutputStream("server_recv.txt");
            fis = new FileInputStream("server_send.txt");

            // 入力ソケットを作成して、送信を待ち受ける
            ServerSocket server = new ServerSocket(8100);
            socket = server.accept();

            // ソケットを受信する
            int ch;
            InputStream input = socket.getInputStream();
            while ((ch = input.read()) != 0) {
                fos.write(ch);
            }

            // 受信した内容をファイル出力
            OutputStream output = socket.getOutputStream();
            while ((ch = fis.read()) != -1) {
                output.write(ch);
            }
        } catch (SocketException e) {
            System.out.println("occured SocketException.");
        } catch (IOException e) {
            System.out.println("occured IOException.");
        } catch (Exception e) {
            System.out.println("occured Exception.");
        } finally {
            try {
                socket.close();
                fos.close();
                fis.close();
            } catch (IOException e) {
                System.out.println("occured IOException.");
            }
            System.out.println("end.");
        }
    }
}

まとめ


いかがでしたでしょうか?
ソケット通信の仕組みを開発する必要が出てきた場合は参考にしてください。


ちょっとできなかったのは、「SocketReceive」の常駐プロセス化。
現在のサンプルプログラムは1回受信したら終わってしまうので。


ここら辺はアレンジして使ってください。


それではまた!




SpringSecurityで認証機能を実現する!セキュアなWEBサイトへの第1歩

SpringSecurity

認証機能が存在するWEBアプリケーションを開発する場合、認証に関連する機能をゼロから開発するのは大変ですね。
ちょっと考えただけで、以下のような制御が必要になります。


  • ログイン認証
  • セッション管理
  • 不正アクセスに対する制御
  • ワンタイムトークン

これらの、いわゆる認証で必要な機能を提供しているのが「SpringSecurity」です。
簡単な実装と設定ファイルへの設定記載をおこなえば、認証に必要な最低限の機能が使えるようになります。


今回は、認証機能が存在するWEBアプリケーションに対するSpringSecurityの使いかたを説明していきます。


ログイン認証

ログイン認証に関して、SpringSecurityでの実装はお決まりです。


applicationSecurity.xmlの基本的な定義


まずは、「applicationSecurity.xml」にSpringSecurityを使用するための定義が必要です。
以下に、定義例を記述します。


<sec:form-login login-page="/xxx.jsp"
    username-parameter="username"
    password-parameter="password"
    default-target-url="/xxx"
    always-use-default-target="true"
    login-processing-url="/j_spring_security_check"
    authentication-failure-handler-ref="authenticationFailureHandler"
    authentication-success-handler-ref="authenticationSuccessHandler" />

    <bean id="authenticationSuccessHandler" class="jp.co.xxx.AuthenticationSuccessHandler">
        <property name="alwaysUseDefaultTargetUrl" value="true" />
        <property name="defaultTargetUrl" value="/xxx/xxx" />
    </bean>

    <bean id="authenticationFailureHandler" class="jp.co.xxx.ExceptionMappingAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/xxx" />
    </bean>

「xxx」の箇所は、SpringSecurityを組み込むWEBアプリケーション固有の部分になります。
<sec>タグで、SpringSecurityの基本動作を定義しています。
<sec>タグで、認証成功時と認証失敗時の制御クラスを定義しています。
「authenticationSuccessHandler」「authenticationFailureHandler」です。
その2つのクラスについて、<bean>タグでクラスの属性等を定義しています。


認証でのJava側の実装


Java側の実装においてポイントとなるのは、UserDetailsServiceクラスです。

UserDetailsServiceクラスのJavaDocは以下です。



で、UserDetailServiceを使った認証ロジックは以下になります。


import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String loginID) {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) attrs.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        String password = request.getParameter("password");
    }
}

「loadUserByUsername」のパラメータである「loginID」が、ログイン画面で入力されたログインID。
HTTPリクエストから取得している「password」が、ログイン画面で入力されたパスワードです。


この実装をおこなうこで、「loadUserByUsername」でログインIDとパスワードがわかります。
後は、ログインIDとパスワードが格納されているテーブルを検索して、ログインIDとパスワードの一致判定をやればOKです。


ログインIDとパスワードが一致しているユーザ情報の取得でさえも、SprinSecurityが機能を提供をしています。
以下のような実装をおこなえば、DBに登録されているログインID・パスワードと一致するユーザ情報を取得できます。


import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

public class MyUserDetails extends User {
    public MyUserDetails(
        String userName,
        String password,
        String dispName,
        boolean enabled,
        boolean accountNonExpired,
        boolean credentialsNonExpired,
        boolean accountNonLocked,
        Collection<GrantedAuthority> authorities) {
        super(userName, password, enabled, accountNonExpired,
            credentialsNonExpired, accountNonLocked, authorities);
    }
}

※SpringSecurityは、ユーザロール(ユーザ毎の権限制御)についても制御可能ですが、今回のサンプルでは省略します。


上記クラスを作成し、「loadUserByUsername」において「MyUserDetails」のインスタンスを取得します。
以下のような感じです。


user = new MyUserDetails(
     ※ログインID,
     ※パスワード,
     ※ユーザ名,
     true,
     true,
     true,
     true,
     authorities);

インスタンスが取得できたら認証成功、インスタンスが取得できなかったら認証失敗、です


システムによっては認証が成功したとしても、ログイン成功とはみなさないケースもあるかと思います。
例えば、アカウントロック。
認証失敗した数をDBに保存しておいて、その数が規定値を超えたら画面にエラーメッセージを表示してログインさせない。


そういったシステム固有の認証チェックについても、この「loadUserByUsername」に実装するとよいでしょう。


認証に成功したら、ログイン情報をセッションに格納します。


    UserSession userSession = new UserSession();
    userSession.setLoginID(※ログインID);
    userSession.setUserName(※ユーザ名);
    RequestContextHolder.getRequestAttributes().setAttribute("session", userSession, RequestAttributes.SCOPE_SESSION);

userSessionというのは、システム固有でセッションとして保持しておきたい情報を格納するインスタンス。
そのインスタンスをセッションとして格納しています。


上記のサンプルプログラムは、ログインIDとユーザ名をセッションに格納する例です。
ここら辺はシステムによって変わってくるかと思います。
セッションとして保持するべきユーザ情報を見極めて、セッションに格納するようにしましょう。


認証に成功してセッションに情報を格納したら、認証については完了です。


ここまで記述した認証をおこなうためには、前述したSpringSecurityの定義の他に、「applicationSecurity.xml」に以下の定義が必要です。


<sec:authentication-manager>
    <sec:authentication-provider user-service-ref="myUserDetailsService" >
        <sec:password-encoder ref="passwordEncoder" />
    </sec:authentication-provider>
    </sec:authentication-manager>
    <sec:global-method-security pre-post-annotations="enabled"/>
    <bean id="myUserDetailsService" class="jp.co.xxx.MyUserDetailsService" />
    <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />

「jp.co.xxx.MyUserDetailsService」の「xxx」部分は、このクラスがシステム固有のクラスになるために「xxx」にしています。
システム固有のパッケージになります。


「jp.co.xxx.AuthenticationSuccessHandler」「xxx」部分は、このクラスがシステム固有のクラスになるために「xxx」にしています。
システム固有のパッケージになります。
この「AuthenticationSuccessHandler」に、ログイン成功時の制御を記述します。
固有のログを出すとか、ログイン成功回数をカウントするとか、業務に合わせた制御について、このクラスに処理を記述すればよいかと思います。


「value=”/xxx/xxx”」が、ログイン成功時に遷移するURLです。
デフォルトのURLになりますので、例外的に他URLに遷移させたい場合は、「AuthenticationSuccessHandler」で他URLに遷移する制御をおこなえばよいです。


パスワードの暗号化


パスワードはDBに登録することになりますが、一般的に平文(そのままの文字列)で登録することはセキュリティの観点からNGです。
何かしら暗号化して登録することが一般的です。


SpringSecurityは、暗号化されたパスワード文字列を参照しての認証についても自動で行ってくれます。
暗号化の種類は選べるのですが、以下の例は「SHA-1」で暗号化(ハッシュ化)された文字列をパスワードとして扱う場合の例です。
以下の定義を「applicationSecurity.xml」に定義すればよいです。


<bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" >
    <constructor-arg value="1"/>
</bean>

「constructor-arg」がSHA強度です。
「1, 256, 384, 512」といった値を設定するのが一般的。


「applicationSecurity.xml」に定義するのは認証時の制御について必要になるのですが、パスワード文字列を登録する処理は別途開発が必要です。
多分、ほとんどのシステムがユーザ登録画面があり、そこからパスワード文字列を登録することになるかと思うので、その制御にパスワード文字列を暗号化してDBに登録する制御が必要になります。


SpringSecurityとは別に開発が必要ですが、難しい処理ではないです。


public static String getHashString(String str) {

    // ダイジェスト文字列取得
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA1");
    } catch (NoSuchAlgorithmException e) {
        return "";
    }

    // ダイジェスト文字列をハッシュ文字列に変換する
    byte[] dat = str.getBytes();
    md.update(dat);
    byte[] byt = md.digest();
    StringBuffer buff = new StringBuffer(byt.length * 2);
    for (int lc = 0; lc < byt.length; lc++) {
        int d = byt[lc] & 0xFF;
        if (d < 16) {
            buff.append("0");
        }
        buff.append(Integer.toHexString(d));
    }
    return buff.toString();
    }

実際のシステムでは、ユーザ情報の登録編集時に上記のようなロジックを組み込み、DBに暗号化されたパスワード文字列を登録します。


不正アクセスに対する制御(フィルター)


SpringSecurityを使って認証をおこなった場合、ログイン後の画面は認証に成功した状態じゃないとアクセスできません。
しかし、SpringSecurityの対象外にしたい画面があったります。
例えば以下のような画面。


  • ログインしてなくても使える画面(パスワード再発行画面、など)
  • CSSやJSファイル

上記のようにSpringSecutiryの対象外にしたい画面やファイルについては、「applicationSecurity.xml」に定義をおこなうことで設定できます。
フィルター定義ですね。


    <!-- SpringSecurity Filter対象外URL設定 -->
    <sec:http pattern="/css/**" security="none"/>
    <sec:http pattern="/js/**" security="none"/>

上記定義例は、ルートフォルダにある「css」フォルダと「js」フォルダ直下の全てのファイルについて、SpringSecurityの対象外にしています。
この定義をおこなわないと、画面にCSSが反映されず、かつ、JavaScriptも動かないので必須の定義になります。


CSRFトークン


SpringSecurityは、CSRFチェックをおこなうトークン値を自動で発行してくれます。
CSRFはクロスサイトリクエストフォージェリ”のことで、サイバー攻撃の一種です。


CSRFの詳しい説明は、以下のサイトにきれいまとまっています。



要はリクエストの改ざんです。
悪意のある第三者が、リクエストを偽造してWEBサイトにアクセスするサイバー攻撃になります。


これを防ぐ手段の一つがCSRFトークンです。
HTTPリクエスト内でCSRFトークンというシステムが発行した文字列を受け渡します。

このトークン値がシステムが期待している値と違ったら不正なアクセスと判断し、SpringSecurityがアクセスを遮断します。


ちょっと小難しいですが、こういった制御をSpringSecurityは自動でやってくれます。
JSP内にCSRFトークンを埋め込む処理を実装するだけです。


具体的には、以下の記述をおこないます。
これはJSPの場合、ですが。


<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">

基本はログイン画面でのみ埋め込みばよい。
SpringSecurityは、どうやら1回発行したトークン値を使い回すようです。


業務システムだとブラウザの戻るボタンを許可しない場合もありますが、その場合は上記のコードを全画面のJSPの埋め込む必要があります。
全ての画面に埋めこむと、戻るボタンで戻った先の画面のCSRFトークン値が古くなってしまい、トークン値不一致でSpringSecurityが不正アクセスと判断します


仮に不正アクセスが発生した場合の制御については、「applicationSecurity.xml」に定義したクラスで制御可能。


<bean id="requestDataValueProcessor"
    class="jp.co.xxx.CompositeRequestDataValueProcessor">
    <property name="processors">
        <list>
            <bean class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" />
        </list>
    </property>
</bean>

まとめ


いかがでしたでしょうか?


世の中に存在するほぼすべてのWEBシステムが認証機能をもっているかと思いますが、認証機能をゼロから作るのは骨が折れます。。。
SpringSecurityを導入すれば、認証に必要な機能をゼロから作る必要はありません。


XMLの定義で結構必要なところが解り辛い部分なのですが、使わない手はないかと。


それではまた!



Apache Solrを使ったドキュメント検索 – FastVector Highlighter

ApacheSolr

前回、「Standard Highlighter」を使ったドキュメント検索、および、ハイライトを紹介しました。



今回は、ハイライト表示をおこなうことができる別の方法である「FastVector Highlighter」の使用方法を紹介します。


環境情報


環境情報は以下になります。

使用するのは、JavaとApache Solrの2つのみです。

  • Java 12.0.1
  • ApacheSolr 6.4.2

設定変更


「Standard Highlighter」でのハイライト表示をおこなった後に、「FastVector Highlighter」を使えるように設定変更をおこないます。
「Standard Highlighter」のスキーマ定義については、前回の記事を参考にしてください。



「Standard Highlighter」を使っている状態から、「FastVector Highlighter」を使える状態に変更します。


まずはスキーマ定義です。
今回は、文書の中身に対して検索をおこない、検索結果をハイライト表示することを想定します。
文書の中身を格納するフィールドである「content」について、以下のように設定変更をおこないます。
変更するファイルは「managed-schema」です。


<field name="content" type="text_general_content" multiValued="false" indexed="true" stored="true"/>

以下のように変更します。 ↓↓↓

<field name="content" type="text_general_content" multiValued="false" indexed="true" stored="true" termVectors="true" termPositions="true" termOffsets="true"/>

3つの設定値について、「true」を設定しています。


フィールド名

設定値

説明

termVectors

true=設定する false=設定しない

検索キーワードが含まれる数やキーワードの開始、終了位置などを結果に含めるかどうか(termPositionsとtermOffsetsをtrueに設定した場合、termVectorsもtrue扱いとなる)

termPositions

true=設定する false=設定しない

検索キーワードの含まれる位置を返すかどうか

termOffsets

true=設定する false=設定しない

検索キーワードの含まれる位置を返すかどうか


変更したら、Apache Solrのサービスを再起動して、変更した内容がコア定義に反映されているかを確認します。
Apache Solrのダッシュボードをひらき、コアの選択で対象コアを選択します。


コアを選択したら、左ペーンで「schema」を選択し、「content」フィールドを確認します。


「TermVector Stored」「Store Offset With TermVector」「Store Position With TermVector」が表示されチェックされていれば、スキーマの設定変更OKです。


ApacheSolrの設定確認

筆者は、この定義をおこなうことで「FastVector Highlighter」でのハイライト表示について思ったような結果が返却されるようになりました。


今回は、「Standard Highlighter」を使ったハイライト表示とまったく同じ結果にすることを目標にします。



その場合、ハイライト表示する際に検索ワードを囲むHTMLタグを固定させてしまおうと思います。
その場合、以下の定義変更が必要になります。
変更するファイルは「solrconfig.xml」です。


<fragmentsBuilder name="colored" class="solr.highlight.ScoreOrderFragmentsBuilder">
  <lst name="defaults">
    <str name="hl.tag.pre"><![CDATA[
      <b style="background:yellow">,<b style="background:lawgreen">,
      <b style="background:aquamarine">,<b style="background:magenta">,
      <b style="background:palegreen">,<b style="background:coral">,
      <b style="background:wheat">,<b style="background:khaki">,
      <b style="background:lime">,<b style="background:deepskyblue">]]></str>
    <str name="hl.tag.post"><![CDATA[</b>]]></str>
  </lst>
</fragmentsBuilder>

↓↓↓

<fragmentsBuilder name="colored" class="solr.highlight.ScoreOrderFragmentsBuilder">
  <lst name="defaults">
    <str name="hl.tag.pre"><![CDATA[<b>]]></str>
    <str name="hl.tag.post"><![CDATA[</b>]]></str>
  </lst>
</fragmentsBuilder>

「hl.tag.pre」について、<b>タグのみを使用するように変更します。


こうすることにより、検索ワードをbタグで囲みます。
こんな感じです。


  • <b>検索ワード</b>

FastVector Highlighterのサンプルコード


「FastVector Highlighter」を使った検索のサンプルコードは以下になります。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrDocument;

public class SolrSearchFast {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        SolrQuery solrQuery = new SolrQuery();

        // 検索結果として、文書IDを返却するよう設定
        solrQuery.setFields("id");

        // 検索結果の上限は100件
        solrQuery.setRows(100);

        try {
            StringBuilder queryString = new StringBuilder();
            String keyword = args[0];
            if (keyword.equals("")) {
                queryString.append("*");
            } else {
                String queryPhrase = "\"" + ClientUtils.escapeQueryChars(keyword) + "\"";
                queryString.append("(");
                queryString.append("content:");
                queryString.append(queryPhrase);
                queryString.append(")");
            }

            // 検索実行
            System.out.println("q=" + queryString.toString());

            solrQuery.set("hl.useFastVectorHighlighter", "true");
            solrQuery.set("hl.fragmentsBuilder", "colored");
            solrQuery.set("hl", true);
            solrQuery.set("hl.fl", "content");
            solrQuery.set("hl.fragsize", 100);

            QueryResponse response = client.query(solrQuery);

            // 検索結果を表示
            SolrDocumentList list = response.getResults();
            if (list == null) {
                System.out.println("文書は存在しませんでした。");
            } else {
                System.out.println(list.getNumFound() + "件ヒットしました。");

                // ハイライト情報を取得して加工
                Map<String,Map<String,List<String>>> highlighting = response.getHighlighting();
                for (SolrDocument doc : list) {
                    System.out.println(doc.get("id"));
                    String id = (String) doc.getFieldValue("id");
                    Map<String, List<String>> map = highlighting.get(id);
                    List<String> contentList = map.get("content");
                    for(String val : contentList) {
                        val = val.replaceAll("[\r\n\t]", "");
                        System.out.println("val=" + val);
                    }
                }
            }
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

「Standard Highlighter」を使ったハイライト表示をおこなうサンプルコードと、ほぼほぼ同様です。
違う箇所は、クエリパラメータだけ。



検索結果のハイライト文字列についても、「Standard Highlighter」を使用したサンプルコード検索結果と、ほぼ同様となります。


「Standard Highlighter」と「FastVector Highlighter」、どっちを使うべきか?


どちらを使っても同じハイライト表示をおこなうことができるので、機能としては同等です。

では、どちらを使った方がいいのか?ということですが、筆者は「FastVector Highlighter」を推します。
理由はパフォーマンスです。


筆者が検証した限りでは、「Standard Highlighter」と「FastVector Highlighter」では、約5倍の性能差がありそうです。

以下は、500ファイルが登録されたコアに対して、30ファイルがヒットする状態に対してのパフォーマンス比較です。


ApacheSolrの検索比較

縦軸はmsecです。
なので、「Standard Highlighter」が1秒ちょっとかかる検索に対して、「FastVector Highlighter」は0.2秒くらい。
パフォーマンスが圧倒的に違います。
こうなってくると、「FastVector Highlighter」を使った方がよいという話になります。


では、「FastVector Highlighter」を使うデメリットはまったくないかというとそうではないです。


「設定変更」で説明したスキーマ定義をおこなうと、ファイルを登録した際にコアに登録されている内容が変わってきます。
コアのファイルサイズが、変更前のスキーマ定義と比べると1.5倍になりました。


あまり深く調べられてまとめいないのですが、「termVectors」「termPositions」「termOffsets」をオンにすると、「FastVector Highlighter」を使うためのインデックス情報をApacheSolrが大量に作るようです。
そのために、ファイルサイズが膨大になるが、高速な検索が可能になるということなります。


まとめ


いかがでしたでしょうか?


「FastVector Highlighter」について説明してきましたが、前記事の「Standard Highlighter」と比べると違いがわかるかと。


まとめ
  • 「Standard Highlighter」ではなく「FastVectorHighlighter」を使った方が高速
  • でも、「Standard Highlighter」より「FastVectorHighlighter」の方がディスク使用率が高い

結論としては筆者は「FastVector Highlighter」推しです。
やはり、検索パフォーマンスが高いのは魅力的なので。


それではまた!



MySQLデータのインポートとエクスポート!圧縮と解凍する方法を紹介

Linux

こんにちは。
さくさくTECHブロガーのさくです。


今回は、Linux + MySQL の環境において、シェルを使ってエクスポートとインポートをおこなう方法を紹介します。


どういった場面を想定しているかといいますと、一番は定期実行のバッチですね。
例えば、1日1回、特定テーブルのデータをエクスポートする、といった要件があった場合。


その場合、エクスポートするコマンドをシェル化してクーロンに設定すると思います。
そういった時に使える手法です。


インポートについては、例えば実データが壊れてしまった場合の復旧ですね。
エクスポートしたデータをインポートすれば、データは元通りになります。


環境情報


OS:Linux
DB:MySQL


MySQLには「saku」というDBを準備します。
そのDBには「person」というテーブルが存在します。
テーブル構成は以下。


mysql> desc person;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| personID | int(11)      | NO   | PRI | NULL    | auto_increment |
| name     | varchar(32)  | NO   | MUL | NULL    |                |
| kana     | varchar(128) | NO   |     | NULL    |                |
| address  | varchar(512) | YES  |     | NULL    |                |
| gender   | char(1)      | NO   |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

また、あらかじめデータも格納しておきます。

mysql> select * from person;
+----------+--------+--------------+-----------+--------+
| personID | name   | kana         | address   | gender |
+----------+--------+--------------+-----------+--------+
|        4 | 山田   | ヤマダ       | 東京      | 0      |
|        5 | 高橋   | タカハシ     | 神奈川    | 0      |
|        6 | 田中   | タナカ       | 千葉      | 1      |
+----------+--------+--------------+-----------+--------+
3 rows in set (0.00 sec)

このテーブルに対して、エクスポート、および、インポートをおこないます。


エクスポート

まずはエクスポート。
エクスポートする条件も指定します。
「gender」が「1」のレコードのみをエクスポートします。
シェルは以下になります。

#!/bin/sh
# 
# エクスポートシェル
# 
mysqldump -h localhost \--single-transaction --skip-opt --extended-insert --quick --set-charset --no-create-info --no-autocommit \
  -u ※ユーザ名 -p※パスワード -h ※ホスト名 ※DB名 person \
  --where="gender = '0'" \
  | gzip > person.sql.gz 2>&1 | tee -a export.log

使用するコマンドは mysqldump コマンドです。
mysqldump コマンドで、「gender」が「1」のレコードのみをエクスポートします。
かつ、エクスポートするファイルはperson.sql.gzという圧縮ファイルとして出力し、エクスポート時のログをexport.logに出力します。


エクスポート結果を圧縮することで、仮に大量データをエクスポートすることになったとしても、ある程度はディスク使用率おさえることができます。


様々なオプションを指定していますが、各オプションの用途は以下になります。


オプション

説明

–single-transaction

これを設定しておくと、InnoDBについてエクスポートする際、他SQLがロックされないです。運用中システムについてエクスポートする際は、このオプションを指定した方が無難。

–skip-opt

「–opt」の設定をオフにします。

「–opt」の設定の中に「–add-locks」がありますが、これが有効だとエクスポート中にテーブルがロックされてしまう可能性があります。

それを防ぐために、–skip-optを設定して、「–opt」の設定をオフにします。

–extended-insert

出力されるダンプファイルについて、バルクインサートの形でSQLを作成します。

–quick

MySQLのマニュアルをみる限りでは、大量データをエクスポートする際はつけた方がいいみたいです。意味を理解しきれませんでしたが、たぶん、メモリ上にエクスポートデータを展開するタイミングの話のようです。このオプションを指定した方が、より高速なやり方を実施するようです。

–set-charset

出力するエクスポートするファイルに SET NAMES default_character_set を出力します。

–no-create-info

エクスポートするファイルに CREATE文を出力しません。

–no-autocommit

エクスポートするファイルに AutoCommitをオフにします。

具体的には、エクスポートするファイルのSQL先頭に「set autocommit=0;」を出力します。

かつ、最後に「commit;」を出力します。


エクスポートはこんな感じです。
エクスポートが完了するとperson.sql.gzというファイルが作成されます。


インポート


次にインポート。
インポートは、エクスポートシェルで作成したperson.sql.gzを解凍して、解凍したファイルを参照してインポートします。


シェルは以下になります。

#!/bin/sh
# 
# インポートシェル
# 
gunzip -d person.sql.gz >> insert.sql
mysql -u ※ユーザ名 -h ※ホスト名 -D ※DB名 -p※パスワード < insert.sql 2>&1 \ | tee -a import.log

まずは guzip コマンドで解凍して、「insert.sql」を出力。
gunzipに-d オプションをつけているので、元ファイルの「person.sql.gz」を削除して「insert.sql」に解凍結果を出力。


インポート自体はmysqlコマンドで実行しています。
リダイレクト「<」を指定して、解凍したSQLファイルをそのままインポートします。


まとめ


いかがでしたでしょうか?


今回はたった3行しか入っていないテーブルに対してのエクスポートなので性能は意識する必要はないのですが、数億件のエクスポートする場合は性能が大事になります。
紹介したオプションを指定すれば、極力、サーバ性能に沿ったパフォーマンスを出せると思っています
参考にしてください。


それではまた!