LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-07-21

環境: 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(???);//どうするの?
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0