【问题标题】:How to handle large dataset with JPA (or at least with Hibernate)?如何使用 JPA(或至少使用 Hibernate)处理大型数据集?
【发布时间】:2011-02-15 05:37:57
【问题描述】:

我需要让我的网络应用程序能够处理非常庞大的数据集。目前,我得到 OutOfMemoryException 或正在生成 1-2 分钟的输出。

让我们简单地说,假设我们在 DB 中有 2 个表:Worker 和 WorkLog 第一个大约有 1000 行,第二个有 10 000 000 行。后一个表有几个字段,包括“workerId”和“hoursWorked”字段等。我们需要的是:

  1. 计算每个用户工作的总小时数;

  2. 每个用户的工作时段列表。

在纯 SQL 中对每个任务最直接的方法 (IMO) 是:

1)

select Worker.name, sum(hoursWorked) from Worker, WorkLog 
   where Worker.id = WorkLog.workerId 
   group by Worker.name;

//results of this query should be transformed to Multimap<Worker, Long>

2)

select Worker.name, WorkLog.start, WorkLog.hoursWorked from Worker, WorkLog
   where Worker.id = WorkLog.workerId;

//results of this query should be transformed to Multimap<Worker, Period>
//if it was JDBC then it would be vitally 
//to set resultSet.setFetchSize (someSmallNumber), ~100

所以,我有两个问题:

  1. 如何使用 JPA(或至少使用 Hibernate)实现我的每种方法;
  2. 您将如何处理这个问题(当然是使用 JPA 或 Hibernate)?

【问题讨论】:

  • 您是要创建报告,还是要加载一堆对象?如果您只是想创建一个报告,那么就像您说的那样在 SQL 中执行它并完成它。
  • @Zak:我在 jpa+spring+jsf 中有一个可以运行的 Web 应用程序。但它的性能应该更好。而且,更重要的是,它应该能够处理比目前更大的数据集。 1) 第一个查询存在问题,我不知道如何在hql 或jpa query language 中编写它。我不想使用普通的 sql,恕我直言,这是最后的手段。 2)第二个查询的问题是我不知道如何在JPA 中设置提取大小,我也不知道如何使用JPA 处理这种情况:结果集没有循环,我不知道不知道如何加载“下一个”提取。

标签: java performance hibernate jpa jakarta-ee


【解决方案1】:

假设我们在 DB 中有 2 个表:Worker 和 WorkLog,第一个大约有 1000 行,第二个大约有 10 000 000 行

对于这样的大容量,我的建议是使用来自 Hibernate 的 The StatelessSession interface:

另外,Hibernate 提供了一个 可以使用的面向命令的 API 用于流入和流出的数据流 分离形式的数据库 对象。 StatelessSession 没有 与之关联的持久性上下文 并且不提供许多 更高层次的生命周期语义。在 特别是,无状态会话确实 不实现一级缓存也不 与任何二级或 查询缓存。它没有实现 事务性后写或 自动脏检查。运营 使用无状态会话执行 从不级联到关联的实例。 集合被无状态者忽略 会议。通过执行的操作 无状态会话绕过 Hibernate 的 事件模型和拦截器。由于 缺少一级缓存, 无状态会话容易受到 数据混叠效应。一个无国籍的 session 是一个较低级别的抽象 这更接近底层 JDBC。

StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();

ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}

tx.commit();
session.close();

在此代码示例中,Customer 查询返回的实例是 立即分离。他们从来不是 与任何持久性相关联 上下文。

insert(), update() 和 delete() 定义的操作 StatelessSession接口是 被认为是直接数据库 行级操作。他们导致 SQL 的立即执行 INSERT, UPDATE 或 DELETE 分别。他们有不同的 save(), saveOrUpdate() 和 delete() 的语义 由Session 定义的操作 界面。

【讨论】:

  • @Pascal Thivent:感谢您的回答!关于卷:我不知道实际的卷,我只是指定了最大值(我认为这是基于对该领域的一些知识)。也许实际体积要少 10-100 倍,恕我直言,这些体积的解决方案也可以。
  • 您知道“无状态会话易受数据别名影响”的确切含义吗?谢谢。
  • 这绝不会更快。事实上,它非常比通常使用的 EntityManager 慢而且性能也差很多。
【解决方案2】:

看来您也可以使用 EclipseLink 来做到这一点。 检查这个:http://wiki.eclipse.org/EclipseLink/Examples/JPA/Pagination:

Query query = em.createQuery...
query.setHint(QueryHints.CURSOR, true)
     .setHint(QueryHints.SCROLLABLE_CURSOR, true)
ScrollableCursor scrl = (ScrollableCursor)q.getSingleResult();
Object o = null;
while ((o = scrl.next()) != null) { ... }

【讨论】:

  • setHint 方法显示未定义。
【解决方案3】:

对于内存有限的大型数据集,可能需要结合使用多种技术来创建和操作查询:

  1. 使用 setFetchSize(一些值,可能是 100+),因为默认值(通过 JDBC)是 10。 这更多地与性能有关,并且是其最大的相关因素。 可以使用提供者(Hibernate 等)提供的 queryHint 在 JPA 中完成。 似乎没有(无论出于何种原因)JPA Query.setFetchSize(int) 方法。
  2. 不要尝试将整个结果集编组为 10K+ 记录。 有几种策略适用: 对于 GUI,使用分页或执行分页的框架。考虑 Lucene 或商业搜索/索引引擎(如果公司有钱,则为 Endeca)。为了在某​​处发送数据,将其流式传输并每 N 条记录刷新一次缓冲区以限制使用多少内存。流可能会刷新到文件、网络等。请记住,在下面,JPA 使用 JDBC,而 JDBC 将结果集保存在服务器上,一次仅获取行集组中的 N 行。可以操纵这种细分以促进分组刷新数据。
  3. 考虑用例是什么。通常,应用程序试图回答问题。如果答案是清除 10K+ 行,则应审查设计。同样,考虑使用像 Lucene 这样的索引引擎,优化查询,考虑使用 BloomFilters 作为 contains 检查缓存以在大海捞针中寻找针,而无需访问数据库等。

【讨论】:

    【解决方案4】:

    我同意在您提到的特定情况下,在数据库服务器上进行计算是您的最佳选择。 HQL 和 JPAQL 可以处理这两个查询:

    1)

    select w, sum(wl.hoursWorked) 
    from Worker w, WorkLog wl
    where w.id = wl.workerId 
    group by w
    

    或者,如果关联已映射:

    select w, sum(wl.hoursWorked) 
    from Worker w join w.workLogs wl
    group by w
    

    both 或 which 返回 List,其中 Object[]s 是 Worker 和 Long。或者您也可以使用“动态实例化”查询来包装它,例如:

    select new WorkerTotal( select w, sum(wl.hoursWorked) )
    from Worker w join w.workLogs wl
    group by w
    

    或(取决于需要)甚至可能只是:

    select new WorkerTotal( select w.id, w.name, sum(wl.hoursWorked) )
    from Worker w join w.workLogs wl
    group by w.id, w.name
    

    WorkerTotal 只是一个普通的类。它必须有匹配的构造函数。

    2)

    select w, new Period( wl.start, wl.hoursWorked )
    from Worker w join w.workLogs wl
    

    这将为 WorkLog 表中的每一行返回一个结果...new Period(...) 位称为“动态实例化”,用于将结果中的元组包装到对象中(更易于使用)。

    对于操作和一般用法,我推荐 Pascal 指出的 StatelessSession。

    【讨论】:

      【解决方案5】:

      我正在使用类似的东西,它的工作速度非常快。我也讨厌使用原生 SQL,因为我们的应用程序应该适用于任何数据库。

      将结果放入一个非常优化的 sql 并返回记录列表,这些记录是映射。

      String hql = "select distinct " +
                  "t.uuid as uuid, t.title as title, t.code as code, t.date as date, t.dueDate as dueDate, " +
                  "t.startDate as startDate, t.endDate as endDate, t.constraintDate as constraintDate, t.closureDate as closureDate, t.creationDate as creationDate, " +
                  "sc.category as category, sp.priority as priority, sd.difficulty as difficulty, t.progress as progress, st.type as type, " +
                  "ss.status as status, ss.color as rowColor, (p.rKey || ' ' || p.name) as project, ps.status as projectstatus, (r.code || ' ' || r.title) as requirement, " +
                  "t.estimate as estimate, w.title as workgroup, o.name || ' ' || o.surname as owner, " +
                  "ROUND(sum(COALESCE(a.duration, 0)) * 100 / case when ((COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) = 0) then 1 else (COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) end, 2) as factor " +
                  "from " + Task.class.getName() + " t " +
                  "left join t.category sc " +
                  "left join t.priority sp " +
                  "left join t.difficulty sd " +
                  "left join t.taskType st " +
                  "left join t.status ss " +
                  "left join t.project p " +
                  "left join t.owner o " +
                  "left join t.workgroup w " +
                  "left join p.status ps " +
                  "left join t.requirement r " +
                  "left join p.status sps " +
                  "left join t.iterationTasks it " +
                  "left join t.taskActivities a " +
                  "left join it.iteration i " +
                  "where sps.active = true and " +
                  "ss.done = false and " +
                  "(i.uuid <> :iterationUuid or it.uuid is null) " + filterHql +
                  "group by t.uuid, t.title, t.code, t.date, t.dueDate, " +
                  "t.startDate, t.endDate, t.constraintDate, t.closureDate, t.creationDate, " +
                  "sc.category, sp.priority, sd.difficulty, t.progress, st.type, " +
                  "ss.status, ss.color, p.rKey, p.name, ps.status, r.code, r.title, " +
                  "t.estimate, w.title, o.name, o.surname " + sortHql;
      
          if (logger.isDebugEnabled()) {
              logger.debug("Executing hql: " + hql );
          }
      
          Query query =  hibernateTemplate.getSessionFactory().getCurrentSession().getSession(EntityMode.MAP).createQuery(hql);
          for(String key: filterValues.keySet()) {
              Object valueSet = filterValues.get(key);
      
              if (logger.isDebugEnabled()) {
                  logger.debug("Setting query parameter for " + key );
              }
      
              if (valueSet instanceof java.util.Collection<?>) {
                  query.setParameterList(key, (Collection)filterValues.get(key));
              } else {
                  query.setParameter(key, filterValues.get(key));
              }
          }       
          query.setString("iterationUuid", iteration.getUuid());
          query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
      
          if (logger.isDebugEnabled()) {
              logger.debug("Query building complete.");
              logger.debug("SQL: " + query.getQueryString());
          }
      
          return query.list();
      

      【讨论】:

      • 优化了吗?你能解释一下吗?
      • 根据结果列表的大小,这可能会在数据集较大时崩溃和烧毁。已经发现将 RI 数据存储在数据库中的一般模式,然后在此之上使用搜索引擎(Lucene 等人)是优越的。提供全文搜索模式支持、卓越的性能、内置分页,而不会使客户端内存需求负担过重等。简而言之,几乎从不只返回(一些)集合以响应(任意)用户查询,因为它可能是数千个(或数百万?)的记录。
      【解决方案6】:

      不应将原始 SQL 视为最后的手段。如果您想在 JPA 层上保持“标准”,而不是在数据库层上,它仍然应该被视为一个选项。 JPA 还支持原生查询,它仍然会为您映射到标准实体。

      但是,如果您有一个无法在数据库中处理的大型结果集,那么您真的应该使用普通 JDBC,因为 JPA(标准)不支持大型数据集的流式传输。

      如果您使用 JPA 实现特定的结构,则将您的应用程序移植到不同的应用程序服务器会更加困难,因为 JPA 引擎嵌入在应用程序服务器中,您可能无法控制使用哪个 JPA 提供程序。

      【讨论】:

      • 这个。我发现手动执行连接查询(session.doWork 等)实际上是你能得到的最快速度
      • 标准的EntityManager没有doWork操作。
      • 是的,这就是我写session 的原因,你可以通过entityManager.unwrap(Session.class); 获得它。 Idk 如果那是不好的编程风格。我想也可以写一个sessionFactory Bean
      • 这可能是特定于休眠的。 JPA 没有 Session 类。为了其他人的利益,您可以更新您的评论以说明会话类的完整包吗?
      • 我不能再更新评论了,所以:想法说:org.hibernate.Session。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-10
      • 2015-07-17
      • 1970-01-01
      • 2012-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多