我在最近几天上下搜索了互联网,并尝试了所有我能想到的。我现在认为自己没有选择。
在java 8 spring boot rest应用程序中,我有一个
JPA存储库:
@Transactional
public interface PageRepository extends JpaRepository<ListPage, Long> {
static final String CLAUSE_AND_PAGE_NR = " and lp.pageNumber = :pageNumber";
static final String BY_LIST_ID = "from ListPage lp inner join lp.listInstance li where li.objectId = :listId";
static final String FIND_CONTENT = "select lp.content ";
@Query(value = FIND_CONTENT + BY_LIST_ID + CLAUSE_AND_PAGE_NR)
public String findContentByListIdAndPageNumber(@Param("listId") final Long listId, @Param("pageNumber") final Integer pageNumber);
}
控制器:
@RestController
@RequestMapping("/download")
public class DownloadController {
private final Logger logger = LoggerFactory.getLogger(DownloadController.class);
@Resource
ListRepository listRepository;
@Resource
PageRepository pageRepository;
@RequestMapping(value = "/list/{id}/browse-txt", method = RequestMethod.GET, produces= MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<StreamingResponseBody> getListAsSingleFileBrowseTxt(@PathVariable(value="id") Long listId,
HttpServletResponse response,
HttpServletRequest request) {
ListInstance list = listRepository.findOne(listId);
response.setHeader("Content-Disposition", "inline; filename=" + renameFileExtension(list.getFileName(),".TXT"));
response.setHeader("content-type", "text/plain");
return getResponse(auth.getCorporateKey(), list);
}
private ResponseEntity<StreamingResponseBody> getResponse (String corporateKey, ListInstance list) {
Long listId = list.getObjectId();
StreamingResponseBody responseBody = out -> {
Integer pageCount = pageRepository.countByListId(listId);
long bytesBuffered = 0;
ByteArrayOutputStream buff = new ByteArrayOutputStream();
String pageString;
byte[] pageBytes;
for (Integer i = 1; i <= pageCount; i++) {
try {
pageString = pageRepository.findContentByListIdAndPageNumber(listId, i);
buff.write(pageBytes);
bytesBuffered += buff.size();
if (bytesBuffered > 1024) { //flush after 1KB
bytesBuffered = 0;
out.write(buff.toByteArray());
out.flush();
buff.reset();
}
}
catch (Exception e) {
logger.error("error writing lazy buffer: page " + i + " of list " + listId, e);
}
}
out.write(buff.toByteArray());
out.flush();
};
return new ResponseEntity(responseBody, HttpStatus.OK);
}
}
当我呼叫控制器时,一切都按预期工作。我在浏览器中看到以1k块的形式出现的页面。这就是我想要的, - 大量内容的加载。
但是突然(并不总是在同一页面)它崩溃了: org.springframework.transaction.CannotCreateTransactionException:无法打开JPA EntityManager进行事务处理;嵌套异常是java.lang.IllegalStateException:EntityManager已关闭
我理解,这是因为JPA存储库在不同的线程中运行,其中创建存储库的线程已经完成(我在单个线程中尝试了这个读取循环并且这样做了。)
问题是: 如何防止JPA连接(无论我应该怎么称呼)在不同的线程中消失?
有人请再给我一个提示。
问候
S上。
答案 0 :(得分:0)
例外情况显然是EntityManager is closed
。
您可以做以下事情
将getResponse (...)
方法移动到某个服务类,并使用@transactional注释该方法。从您的控制器调用此方法。从PageRepository界面中删除@Transactional。它真的没关系,但建议。要了解更多信息,请查看link
重要:pageString = pageRepository.findContentByListIdAndPageNumber(listId, i);
您在这里为每个pageNumber点击数据库,因此会发生很多交易。修改代码只是为了通过传递in
来执行一次list of page numbers
查询。这样,DB只会有一笔交易。
只要尝试这些更改,请告诉我们是否有效。
答案 1 :(得分:0)
这就是我能解决的问题:
将此功能添加到 JPA存储库
@Query(value = FIND_CONTENT + BY_LIST_ID + CLAUSE_ORDER_BY_PAGE_NR)
@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "0"))
public Stream<String> streamAllPageContentByListID(@Param("listId") final Long listId);
这将是控制器
@RestController
@RequestMapping("/download")
public class DownloadController {
private final Logger logger = LoggerFactory.getLogger(DownloadController.class);
@Resource
ListRepository listRepository;
@Resource
PageRepository pageRepository;
@Transactional(readOnly = true)
@RequestMapping(value = "/list/{id}/browse-txt", method = RequestMethod.GET, produces= MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<StreamingResponseBody> getListAsSingleFileBrowseTxt(@PathVariable(value="id") Long listId,
HttpServletResponse response,
HttpServletRequest request) {
ListInstance list = listRepository.findOne(listId);
response.setHeader("Content-Disposition", "inline; filename=" + renameFileExtension(list.getFileName(),".TXT"));
response.setHeader("content-type", "text/plain");
return getResponse(response, auth.getCorporateKey(), list);
}
protected ResponseEntity getResponse (HttpServletResponse response, String corporateKey, ListInstance list) {
Long listId = list.getObjectId();
try {
try (PrintWriter out = response.getWriter()) {
try (Stream<String> pageStream = pageRepository.streamAllPageContentByListID(listId)) {
pageStream.forEach(page -> {
out.write(page);
});
out.flush();
}
}
} catch (Exception e) {
logger.error("error writing lazy buffer of list " + listId, e);
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(HttpStatus.OK);
}
}
JPA存储库永远不会在不同的线程中工作。最后的提示来自这一方http://knes1.github.io/blog/2015/2015-10-19-streaming-mysql-results-using-java8-streams-and-spring-data.html
到目前为止,解决方案非常完美。懒惰负载的速度令人惊叹。