2016-05-12 14 views
1

私はユーザーがファイルをダウンロードできるSpring-MVCアプリケーションに取り組んでいます。ユーザーは、ダウンロードメカニズムをトリガーする添付ファイルをクリックすることができます。Spring、Java:メモリ不足を避けるためのストリーミングファイルのダウンロード

昨日、複数のダウンロードと2つのファイルに約2 GBのファイルがあると、メモリ不足エラー(以下のログ)が発生しました。

この問題を回避するには、ダウンロードデータをチャンクでストリーミングし、ファイル全体ではなくサービスレイヤでのみチャンクを処理するような方法が考えられます。

残念ながら、私はこれに先立って移動する方法がわかりません。どんな助けもいいと思います。このオプションが飛べない場合、この問題の解決方法に関する推奨事項。

エラーログ:

HTTP Status 500 - Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory 

type Exception report 

message Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory 

description The server encountered an internal error that prevented it from fulfilling this request. 

exception 

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory 
    org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1303) 
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:977) 
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) 
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) 
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) 
    javax.servlet.http.HttpServlet.service(HttpServlet.java:620) 
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) 

コントローラーコード:

@RequestMapping(value = "/download/attachment/{attachid}", method = RequestMethod.GET) 
     public void getAttachmentFromDatabase(@PathVariable("attachid") int attachid, 
    , HttpServletResponse response,) { 

response.setContentType("application/octet-stream"); 
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmenById(attachid); 
response.setHeader("Content-Disposition", "attachment; filename=\"" + groupAttachments.getFileName() + "\""); 
          response.setContentLength(groupAttachments.getSendAttachment().length); 
          FileCopyUtils.copy(groupAttachments.getSendAttachment(), response.getOutputStream()); 
    response.flushBuffer(); 

    } 

サービス層:

@Override 
    public GroupAttachments getAttachmenById(int attachId) { 
     Person person = this.personService.getCurrentlyAuthenticatedUser(); 
     GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId); 

     GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(), 
       groupAttachments.getGroupId()); 
     if (!(groupMembers == null)) { 
      if (person.getUsername().equals(groupMembers.getMemberUsername())) { 
       try { 
        Path path = Paths.get(msg + groupAttachments.getGroupId() + "/" + 
          groupAttachments.getFileIdentifier()); 
        groupAttachments.setSendAttachment(Files.readAllBytes(path)); 
        return groupAttachments; 
       } catch (IOException ignored) { 
        this.groupAttachmentsDAO.removeAttachment(attachId); 
        return null; 
       } 
      } 
      return null; 
     } else { 
      return null; 
     } 
    } 

ありがとうございます。 :-)

更新

新しいダウンロードメカニズム:

コントローラー:

public ResponseEntity<byte[]> getAttachmentFromDatabase(@PathVariable("attachid") int attachid, 
                @PathVariable("groupaccountid") Long groupAccountId, @PathVariable("api") String api, 
                HttpServletResponse response, 
                @PathVariable("type") boolean type) { 

Path path = this.groupAttachmentsService.getAttachmentPathById(attachid); 

     GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmentObjectOnlyById(attachid); 
         response.setContentType("application/octet-stream"); 
         response.setHeader("Content-Disposition", "attachment; filename=\""+groupAttachments.getFileName()+"\""); 
    try { 
OutputStream outputStream = response.getOutputStream(); 

Files.copy(path,outputStream); 
outputStream.flush(); 
outputStream.close(); 
response.flushBuffer(); 
} 

サービス層:あなたのサービスで全体のコンテンツをロードする

@Override 
    public Path getAttachmentPathById(int attachId){ 
     Person person = this.personService.getCurrentlyAuthenticatedUser(); 
     GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId); 

     GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(), 
       groupAttachments.getGroupId()); 
     if (!(groupMembers == null)) { 
      if (person.getUsername().equals(groupMembers.getMemberUsername())) { 
       try { 
        return Paths.get(msg + groupAttachments.getGroupId() + "/" + 
          groupAttachments.getFileIdentifier()); 
       } catch (Exception ignored) { 
        return null; 
       } 
      } 
      return null; 
     } else { 
      return null; 
     } 
    } 
+1

すべてをメモリに読み込んでいますが、しないでください。 'Files.readAllBytes'は' 2Gb'全体をメモリに読み込みます。単にしないでください。読み込みを遅らせ、最初にロードするのではなく、読み込んだ部分を直接読み込みます。代わりに 'Files.copy(Path、OutputStream)'を使用してください。 –

+0

コピーは、それをロードする代わりにストリーミングします。コメントをお読みください... –

+0

@ M.Deinum:誤読にごめんね。私はコードを更新し、またFiles.copy(path、outputStream)を試してみました。うまく動作します。私はメインポストに更新としてコードを掲載しました。可能であれば、最適化が可能かどうかチェックしてください。ありがとうございました。 –

答えて

2

最初のピットストップ、そこにあなたがwhをロードしているように多くのファイル内容をメモリに保存します。

GroupAttachmentsPathを作成する方法を作成します。GroupAttachmentsで作成します。

public class GroupAttachments { 

    public Path getPath() { 
     return Paths.get(msg + getGroupId() + "/" + getFileIdentifier()); 
    } 
} 

は、その後、あなたのコントローラで、単にそれより複雑な私見にする必要はありません

@RequestMapping(value = "/download/attachment/{attachid}", method = RequestMethod.GET) 
public void getAttachmentFromDatabase(@PathVariable("attachid") int attachid, HttpServletResponse response) { 

    response.setContentType("application/octet-stream"); 
    GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmenById(attachid); 
    Path path = groupAttachmetns.getPath(); // calculates the java.nio.file.Path 
    response.setHeader("Content-Disposition", "attachment; filename=\"" + path.getFileName() + "\""); 
    response.setContentLength(Files.size(path); 
    Files.copy(path, response.getOutputStream()); 
    response.flushBuffer(); 

} 

を行います。

+0

コンテンツの長さ以外はすべて同じです。どうもありがとう。 :-) –

+0

一部のブラウザはコンテンツの長さを必要としますが、それ以外のブラウザはうまく動作しません。 –

+0

すでに追加されていますが、キャストする必要がありました。 'response.setContentLength((int)Files.size(path));' –

関連する問題