I searched the internet up and down for this the last few days and tried all I could think of. I consider myself out of options now.
In a java 8 spring boot rest application I have a
JPA repository:
@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);
}
The controller:
@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);
}
}
When I call the controller everything works as expected. I see in the browser the pages coming in as chunks of 1k. That's what I wanted,-lazy load of large content.
But out of a sudden (and not always at the same page) it crashes with: org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: EntityManager is closed
I understand, that this happens because the JPA repository operates in a different thread, where the thread the repository was created in, has finished (I tried that reading loop in a single thread and that worked).
Question for me is: How can I prevent the JPA connection (whatever I should call it) from vanishing in the different thread?
Someone please give me another hint.
greetings
S.