Help us understand the problem. What is going on with this article?

Javaでデータベースの検索結果をグルーピングしながらZipファイルを作成するには

More than 1 year has passed since last update.

環境: Java8

説明

単純なSELECT文による値の繰り返しがあるソートされた二次元表をもとにキーごとにZipEntryを追加し、エントリの内容はテキストファイルとして、テキストファイルの内容もインデントでグループ化してあるというようなZipファイルを作る。

出力

  1. キー部分の変化ごとにZipファイルのエントリを作成する。
  2. 各エントリはテキストファイルを内容とする。
  3. テキストファイルの内容もグループ化する。インデントでグループ化する。

入力

  1. SELECT文は同一値の繰り返しがあるフラットな二次元表。

要点

  1. ByteArrayOutputStreamを可変長のテキスト編集の一時領域として使用する。
  2. Formatter(OutputStream, String)のコンストラクタを使い文字コード変換と整形処理をまとめて対応する。
  3. 読みだした1レコード分の整形が終わったらFormatter#flush()を呼び出して編集したテキスト内容をbufferに転送する。
  4. bufferの内容をZipOutputStreamに移し終わったらByteArrayOutputStream#reset()を呼び出してバッファの先頭に戻す。
  5. 閉じない。
おまけ
  1. 簡易的なRowクラスを作成し行を表現し, forステートメントでループできるようにし, カラム値には名前でアクセスできるようにする。これはわかりやすさのため。
  2. 現在値(cur)と前回値(prev)の比較が常に成立するようにfor(Row prev = new Row(null)として前回値が存在しない1件目の処理でも eq(cur.a, prev.a) が有効になるようにする。
  3. 文字列比較にはnullを許容してnullと文字列を比較できるようなコンビニエンスメソッドを用意する。これはnull.equals(s)やs.equals(null)でNullPointerExceptionやIllegalArgumentExceptionが発生しないようにするため。
  4. 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(???);//どうするの?
fgtrjhyu
ソフトウェアエンジニアです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away