【问题标题】:Process REST request to retrieve data via Hibernate in a separate Thread处理 REST 请求以在单独的线程中通过 Hibernate 检索数据
【发布时间】:2015-07-26 17:49:37
【问题描述】:

我们有一个带有 jersey REST 接口的 java、spring、osgi 应用程序。

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

为了解决这个问题,想法是拆分请求。

  1. 查询数据的初始请求,将其拆分为片段,将它们编码为 JSON,将它们存储在 HDD 上并返回有多少片段以及参考
  2. 后续请求将一次检索一个片段,其中片段大小仅为 100 KiB

即使连接非常糟糕,这也应该可以工作,并允许 MDE 尽可能慢地请求片段,并且尽可能频繁地请求片段,同时仍然获得相同的数据快照。

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

整个休眠过程应该发生在一个后台线程中,该线程通知 REST-request-thread 一次以提供有关结果的数据,但不提供实际结果数据。

现在我已经读到休眠和多线程是一个非常危险的组合。但在我看来,如果 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;
  }
}

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

我想知道如何在子线程中获得休眠会话,以及整个设置是否有机会工作。有没有我还没有考虑过的陷阱,我应该注意这些? 如果您对如何处理所有问题(拆分数据)有更好的了解,我也会对此持开放态度。

编辑 1:

我得到一个:

org.springframework.orm.hibernate3.HibernateSystemException: No Hibernate Session bound to thread,并且配置不允许在这里创建非事务性会话; 嵌套异常是 org.hibernate.HibernateException: No Hibernate Session bound to thread,并且配置不允许在此处创建非事务性会话

在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())
{
  ...

【问题讨论】:

  • “我没有获得休眠会话”是什么意思?你尝试了什么,发生了什么。例外?在哪里?
  • 另外:不要为了计算元素而滚动结果集。改为执行带有计数的选择。不要将结果存储在后端,而是按需执行分页查询。你真的需要第一个请求,还是可以从一个请求开始,比如:给我前 100 个“随便”,如果还有更多,请告诉我,这样可以节省一次往返,并且可能会限制存储在客户端上所需的信息量
  • @Jens:感谢您的想法,但我认为在这种情况下他们不会给我我需要的东西 - 请参阅编辑 1

标签: java multithreading spring hibernate transactions


【解决方案1】:

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

与设计相关,应关闭ScrollableResults,释放数据库相关资源(连接、游标)。

因此我会这样设计它:

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

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

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

【讨论】:

  • 感谢您提醒您关闭 ScrollableResults、会话和数据库资源 - 尽管这不是当前问题,但我仍然忽略了这一点
  • 在我看来,你提出的建议和我到目前为止的设计方式大致相同 - 我想我的问题是我仍然需要以某种方式明确地打开一个新会话
  • 几乎是的。但我认为您应该将结果保存在内存中而不是磁盘上。
  • 我们有一个项目(目前有自己的代码),其中多达 1000000 个对象同步到 MDE。我当然不希望 RAM 中有 100 万个数据对象,这很可能不适合任何方式。此外,数据将以 JSON 格式发送,移动设备需要一些时间来处理每个请求。所以从磁盘读取碎片不是(时间)问题。
  • 有道理。那时可能像 Redis 这样的键值存储非常适合。
猜你喜欢
  • 2015-08-09
  • 1970-01-01
  • 1970-01-01
  • 2019-11-01
  • 1970-01-01
  • 2019-09-16
  • 1970-01-01
  • 2018-09-30
  • 1970-01-01
相关资源
最近更新 更多