各 BLOB をデータベースからクライアントのファイル システムに直接ストリーミングすることによって作成された、完全に動的な ZIP ファイルのキックオフ例。
次のパフォーマンスを持つ巨大なアーカイブでテスト済み:
- サーバーのディスク容量のコスト: 0 メガバイト
- サーバーのRAMコスト:
~ xx メガバイト。Runtime.getRuntime().freeMemory()
ループの前、最中、後に同じルーチンを複数回 ( を使用して) 実行すると、異なる明らかにランダムな結果が得られたため、メモリ消費はテストできません (または、少なくとも適切に行う方法がわかりません)。ただし、byte[]を使用するよりもメモリ消費量が少なく、十分です。
FileStreamDto.javaInputStream
の代わりに使用byte[]
public class FileStreamDto implements Serializable {
@Getter @Setter private String filename;
@Getter @Setter private InputStream inputStream;
}
Java サーブレット(または Struts2 アクション)
/* Read the amount of data to be streamed from Database to File System,
summing the size of all Oracle's BLOB, PostgreSQL's ABYTE etc:
SELECT sum(length(my_blob_field)) FROM my_table WHERE my_conditions
*/
Long overallSize = getMyService().precalculateZipSize();
// Tell the browser is a ZIP
response.setContentType("application/zip");
// Tell the browser the filename, and that it needs to be downloaded instead of opened
response.addHeader("Content-Disposition", "attachment; filename=\"myArchive.zip\"");
// Tell the browser the overall size, so it can show a realistic progressbar
response.setHeader("Content-Length", String.valueOf(overallSize));
ServletOutputStream sos = response.getOutputStream();
ZipOutputStream zos = new ZipOutputStream(sos);
// Set-up a list of filenames to prevent duplicate entries
HashSet<String> entries = new HashSet<String>();
/* Read all the ID from the interested records in the database,
to query them later for the streams:
SELECT my_id FROM my_table WHERE my_conditions */
List<Long> allId = getMyService().loadAllId();
for (Long currentId : allId){
/* Load the record relative to the current ID:
SELECT my_filename, my_blob_field FROM my_table WHERE my_id = :currentId
Use resultset.getBinaryStream("my_blob_field") while mapping the BLOB column */
FileStreamDto fileStream = getMyService().loadFileStream(currentId);
// Create a zipEntry with a non-duplicate filename, and add it to the ZipOutputStream
ZipEntry zipEntry = new ZipEntry(getUniqueFileName(entries,fileStream.getFilename()));
zos.putNextEntry(zipEntry);
// Use Apache Commons to transfer the InputStream from the DB to the OutputStream
// on the File System; at this moment, your file is ALREADY being downloaded and growing
IOUtils.copy(fileStream.getInputStream(), zos);
zos.flush();
zos.closeEntry();
fileStream.getInputStream().close();
}
zos.close();
sos.close();
重複エントリを処理するためのヘルパー メソッド
private String getUniqueFileName(HashSet<String> entries, String completeFileName){
if (entries.contains(completeFileName)){
int extPos = completeFileName.lastIndexOf('.');
String extension = extPos>0 ? completeFileName.substring(extPos) : "";
String partialFileName = extension.length()==0 ? completeFileName : completeFileName.substring(0,extPos);
int x=1;
while (entries.contains(completeFileName = partialFileName + "(" + x + ")" + extension))
x++;
}
entries.add(completeFileName);
return completeFileName;
}
ダイレクト ストリーミングのアイデアをくれた@prunge に感謝します。