【问题标题】:Could not open JPA EntityManager for transaction; EntityManager is closed无法打开 JPA EntityManager 进行事务处理; EntityManager 已关闭
【发布时间】:2018-04-19 12:43:26
【问题描述】:

最近几天我在互联网上上下搜索,并尝试了所有我能想到的。我认为自己现在别无选择。

在 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 is closed

我知道,这是因为 JPA 存储库在不同的线程中运行,在该线程中创建存储库的线程已经完成(我在单个线程中尝试了读取循环并且有效)。

我的问题是: 如何防止 JPA 连接(我应该怎么称呼它)在不同的线程中消失?

有人请给我另一个提示。

问候

S.

【问题讨论】:

    标签: java spring multithreading hibernate jpa


    【解决方案1】:

    异常明显是EntityManager is closed

    你可以做以下事情

    1. getResponse (...) 方法移动到某个服务类并使用@transactional 注释该方法。从您的控制器调用此方法。从 PageRepository 接口中删除 @Transactional。它真的没关系,但它是推荐的。想了解更多请找link

    2. 重要pageString = pageRepository.findContentByListIdAndPageNumber(listId, i); 在这里,您为每个 pageNumber 访问数据库,因此会发生许多事务。修改您的代码只是通过传递list of page numbers 来执行一个in 查询。这样一来,数据库就只有一个事务。

    只需尝试这些更改,如果可行,请告诉我们。

    【讨论】:

      【解决方案2】:

      这就是我可以解决的方法:

      将此函数添加到 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

      到目前为止,解决方案效果很好。延迟加载的速度令人惊叹。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-09
        • 1970-01-01
        • 2017-07-25
        • 2015-11-19
        • 2018-03-13
        • 2020-05-27
        • 2019-10-12
        相关资源
        最近更新 更多