環境: Java8
##説明
単純なSELECT文による値の繰り返しがあるソートされた二次元表をもとにキーごとにZipEntryを追加し、エントリの内容はテキストファイルとして、テキストファイルの内容もインデントでグループ化してあるというようなZipファイルを作る。
出力
- キー部分の変化ごとにZipファイルのエントリを作成する。
- 各エントリはテキストファイルを内容とする。
- テキストファイルの内容もグループ化する。インデントでグループ化する。
入力
- SELECT文は同一値の繰り返しがあるフラットな二次元表。
要点
- ByteArrayOutputStreamを可変長のテキスト編集の一時領域として使用する。
- Formatter(OutputStream, String)のコンストラクタを使い文字コード変換と整形処理をまとめて対応する。
- 読みだした1レコード分の整形が終わったらFormatter#flush()を呼び出して編集したテキスト内容をbufferに転送する。
- bufferの内容をZipOutputStreamに移し終わったらByteArrayOutputStream#reset()を呼び出してバッファの先頭に戻す。
- 閉じない。
おまけ
- 簡易的なRowクラスを作成し行を表現し, forステートメントでループできるようにし, カラム値には名前でアクセスできるようにする。これはわかりやすさのため。
- 現在値(cur)と前回値(prev)の比較が常に成立するようにfor(Row prev = new Row(null)として前回値が存在しない1件目の処理でも eq(cur.a, prev.a) が有効になるようにする。
- 文字列比較にはnullを許容してnullと文字列を比較できるようなコンビニエンスメソッドを用意する。これはnull.equals(s)やs.equals(null)でNullPointerExceptionやIllegalArgumentExceptionが発生しないようにするため。
- prevの初期値は実際のデータと整合性をとる。今回はテーブルのデータとしてはnullがない前提である。大概これでいいはず。グループ化するためのカラム値がnullなんてことはないはずだ。
ソースコード
Charset cs = Charset.defaultCharset();
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file), cs)) {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024)) {
try (Formatter formatter = new Formatter(buffer, cs.name())) {
try (PreparedStatement select = con.prepareStatement("SELECT DISTINCT a, b, c, d, e, f FROM foo ORDER BY a, b, c desc, d, e, f")) {
try (ResultSet rs = select.executeQuery()) {
for (Row prev = new Row(null), cur = Row.next(rs); cur != null; prev = cur, cur = Row.next(rs)) {
if (!eq(cur.a, prev.a) || !eq(cur.b, prev.b)) {
zos.putNextEntry(new ZipEntry(String.format("%s-%s.txt", cur.a, cur.b)));
formatter.format("%s%n", cur.c);
formatter.format(" %s%n", cur.d);
} else if (!eq(cur.c, prev.c)) {
formatter.format("%s%n", cur.c);
formatter.format(" %s%n", cur.d);
} else if (!eq(cur.d, prev.d)) {
formatter.format(" %s%n", cur.d);
}
formatter.format(" %s: %s%n", cur.e, cur.f);
formatter.flush();
zos.write(buffer.toByteArray());
buffer.reset();
}
}
}
}
}
}
null との比較を許すコンビニエンスメソッド
public static boolean eq(String l, String r) {
if (l == r) {
return true;
} else if (l != null && r == null) {
return false;
} else if (l == null && r != null) {
return false;
} else {
return l.equals(r);
}
}
ResultSet をループするための簡易的な仕組み
public class Row {
public static Row next(ResultSet rs) throws SQLException {
return (rs.next()) ? new Row(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5), rs.getString(6)) : null;
}
String a;
String b;
String c;
String d;
String e;
String f;
public Row(String v) {
this(v, v, v, v, v, v);
}
public Row(String a, String b, String c, String d, String e, String f) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
}
}
注意点
bufferとformatterはそれらより内側のコードからともに見えている必要がある。下記のように書いてしまうとZipOutputStream#write(byte[])のために素直にデータを取り出せずに困ることになる。やりようはあるけどそれならこんな書き方しなくていい。
Formatter formatter = new Formatter(new ByteArrayOutputStream(1024), cs.name());
...
zos.write(???);//どうするの?