处理REST请求以通过Hibernate在单独的线程

时间:2015-05-14 22:48:38

标签: java multithreading spring hibernate transactions

我们有一个带有泽西REST接口的java,spring,osgi应用程序。

在该设置中,我需要将数据发送到运行winCE CF 3.5的移动设备,因此每个进程只有4个MiB RAM。这意味着即使是一千个数据对象也让MDE内存过流。

要解决问题,我们的想法是拆分请求。

  1. 查询数据的初始请求,将其拆分为片段,将它们编码为JSON,将它们存储在HDD上并返回有多少片段以及参考
  2. 后续请求将一次检索一个片段,其中片段大小仅为几百KiB
  3. 即使是非常糟糕的连接也应该可以工作,并允许MDE以尽可能慢的速度请求片段,同时仍然获得相同的数据快照。

    我对第一个请求的想法是,将具有休眠标准的数据查询为可滚动结果,滚动到结尾,以这种方式从行号中获取总结果并创建对初始请求的答案,而在后台滚动回到开始并将结果处理到硬盘上的json文件中。

    整个hibernate的东西应该发生在一个后台线程中,它通知REST请求线程一次提供有关结果但没有实际结果数据的数据。

    现在我已经读过Hibernate和Multithreading是一个非常危险的组合。然而,在我看来,如果Hibernate相关的一切都在后台线程中肆虐,我可以避免麻烦。

    @Path("/1/mde/articles/")
    @Component
    @Scope("prototype")
    @Transactional
    public class RestMdeArticleController
    {
      private static final Logger              LOG = LoggerFactory.getLogger(RestMdeArticleController.class);
    
      @Context
      private HttpServletRequest               request;
      @Autowired
      private IResultFragmentationController   resultFragmentationController;
      @Autowired
      private IFindMasterDataStrategy          masterDataFinder;
      @Autowired
      private SessionFactory                   sessionFactory;
    
      @Path("fragment")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public FragmentedResultInfo getAllMdeArticlesFragmentedThreaded()
      {
        final FragmentedResultInfo info = new FragmentedResultInfo();
    
        Thread worker = new Thread((new Runnable() {
          private FragmentedResultInfo info = null;
    
          public Runnable setInfo(FragmentedResultInfo info)
          {
            this.info = info;
            return this;
          }
    
          @Override
          public void run()
          {
            try
            {
              HibernateTemplate template = new HibernateTemplate(sessionFactory, true);
              template.execute((new HibernateCallback<Object>()
              {
                private FragmentedResultInfo info = null; ;
    
                public HibernateCallback<Object> setInfo(FragmentedResultInfo info)
                {
                  this.info = info;
                  return this;
                }
    
                @Override
                public Object doInHibernate(Session session) throws HibernateException, SQLException
                {
                  ScrollableResults results = masterDataFinder.findArticles(null, new ArticleFilterOptions<Article>(), ScrollMode.SCROLL_INSENSITIVE);
                  resultFragmentationController.createFragments(this.info, results, Article.class, MdeTransferArticle.class);
    
                  return null;
                }
    
              }).setInfo(info));
    
            }
            catch (Exception e)
            {
              LogUtil.error(LOG, e, "Thread failed with {1}", e.getClass().getSimpleName());
            }
            finally
            {
              synchronized (info)
              {
                info.notify();
              }
            }
          }
    
        }).setInfo(info));
    
        worker.start();
    
        synchronized (info)
        {
          try
          {
            // wait for the worker to notify (that it updated the values in info)
            info.wait();
          }
          catch (InterruptedException e)
          {
            LogUtil.warn(LOG, e, "waiting for thread '{0}' failed with InterruptedException", worker.getName());
          }
        }
    
        return info;
      }
    }
    

    问题是我仍然没有在新线程中获得Hibernate会话。

    我想知道如何在子线程中获得Hibernate会话,如果整个设置有任何机会可以工作。我还有什么陷阱尚未考虑过,我应该注意哪些? 如果您更好地了解如何处理所有问题(将数据拆分),我也会对此持开放态度。

    编辑1:

    我得到了:

      

    org.springframework.orm.hibernate3.HibernateSystemException:没有Hibernate Session绑定到线程,配置不允许在这里创建非事务性的;     嵌套异常是org.hibernate.HibernateException:没有Hibernate会话绑定到线程,并且配置不允许在这里创建非事务性的

    在masterDatafinder深度的这一行:

    getSessionFactory()
        .getCurrentSession()
        .createCriteria(getEntityName(entityClass, storageMode))
        .setComment(StringUtil.normalize(comment))
        .setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE);
    

    在我看来,我遗漏了一些重要信息。这用于在离线/批处理模式下将主数据同步到移动设备。移动设备大多数时间都没有连接。

    虽然移动设备上的数据在大多数情况下都会过时/同步,但至少应始终如此。独立检索页面将为我提供属于每个请求的不同时间点的数据。 如果数据在几毫秒内发生变化,那么在检索实际数据之前执行SELECT COUNT(*)也可能会给我不同的结果。

    我从一个据说从Hibernate书中得到它的帖子中得到了结果滚动的想法。实际上并未检索数据。这是ResultFragmentationController的开头:

    results.last();
    totalResults = results.getRowNumber() + 1;
    totalFragments = BigDecimal.valueOf(totalResults).divide(BigDecimal.valueOf(fragmentSize), 0, RoundingMode.UP).intValue();
    
    info.setTotalFragments(totalFragments);
    info.setTotalResults(totalResults);
    info.setFragmentSize(fragmentSize);
    info.setStatus(EState.IN_PROGRESS);
    
    synchronized (info)
    {
      info.notify();
    }
    
    // reset the results pointer to the initial position
    results.beforeFirst();
    
    while (results.next())
    {
      ...
    

1 个答案:

答案 0 :(得分:1)

HibernateTemplate应该允许您创建新的Hibernate Session,因为当前SpringSessionContext ThreadLocal存储没有会话限制。

与设计相关,您应关闭ScrollableResults并释放与数据库相关的资源(连接,光标)。

因此我会这样设计:

  1. 初始请求构建一个分配了UUID的Command,并将Command传递给ExecutorService以进行异步处理。执行结果被缓存。

  2. 任何后续请求都使用相同的UUID从Cache中获取计算结果。您可以使用Future,以便客户端代码阻止Computation结束。

  3. 计算Result对象的异步块必须始终关闭Hibernate会话并释放数据库连接资源。