【问题标题】:Random Hibernate Exception when using Java 8 ParallelStream in Action Bean在 Action Bean 中使用 Java 8 ParallelStream 时出现随机休眠异常
【发布时间】:2016-05-10 15:35:13
【问题描述】:

在我的 Action Bean 中,我从数据库加载实体,使用来自这些实体的数据使用 Java 8 ParallelStream 创建新的 EntityObject,并将这些 EntityObject 存储在 List 中以供以后在网页上使用。

我使用以下内容使用 Hibernate 映射实体创建这些对象:

List<Entity> entities = dao.getEntities();
List<Object> entityObjects = new ArrayList<>();
entityObjects.addAll(
        entities.parallelStream()
                .map(EntityObject::new)
                .collect(Collectors.toList())
);

EntityObject 构造函数看起来像:

public EntityObject(Entity entity) {...}

当尝试使用 Action Bean 加载页面时,我得到了休眠异常。每次我尝试加载页面时它们都不同,但都与共享引用有关,例如:

... ERROR: Found shared references to a collection

... ERROR: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance

我做错了什么?

编辑:修正了代码。

【问题讨论】:

  • 为什么要使用entityObjects.addAll而不是直接使用流操作的结果,即List&lt;Object&gt; entityObjects = entities.stream().map(entityObjects::new) .collect(Collectors.toList());
  • @darksmurf 先听听Holger 说了什么,然后再看你的代码,那个map 部分是错误的,应该是map(EntityObject::new) 可能
  • @darksmurf 从您提供的日志来看,您的问题可能隐藏在 EntityObject 构造函数中,您能否也准确显示?
  • @Eugene 我对 map 部分的错误,更改了它。构造函数很复杂,可能是问题所在,但在我的情况下不是。我已经在下面回答了我的问题,这个线程的目的是告诉人们将 ParallelStream 与 Hibernate 一起使用可能是一个坏主意。如果我做错了,请告诉我:这是我的第一个问答线程。
  • @darksmurf 的想法是,这可能不是因为 parallelStream 本身,它可能在构造函数中做一些有趣的事情并且作为副作用,因为涉及并行可能会破坏你的代码.您应该首先清楚地了解异常。欢迎来到 SO btw ;)

标签: java hibernate java-8 java-stream stripes


【解决方案1】:

parallelStream() 将在多个线程上执行代码(有利于性能)。然而,Hibernate Session 对象不是线程安全的,并且以线程方式使用它很可能会以神秘的方式失败。 Bryan Pugh 关于交易的另一个回答是类似的:在大多数情况下,交易保存在 ThreadLocal 变量中。执行 parallelStream() 意味着一些代码在不同的(不相关的)线程上运行。 这可以使用以下代码轻松显示:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class TestParallel {
    public static void main(String[] args) {
        // Keep a set of thread-ids that were used. Make sure this is thread-safe.
        Set<String> threadId = Collections.synchronizedSet(new HashSet<String>());

        // Create a long enough list such that spliterator will indeed use multiple threads.
        // If the collection is very small (1 or 2 entries) no multithreading will be done.
        Collection<Integer> numbers = new ArrayList<>();
        for(int i=0; i<10000; i++) {
            numbers.add(i);
        }

        // Now for each number in the list, see which thread is handling it by dumping the thread-name.
        numbers.parallelStream().forEach(n -> threadId.add(Thread.currentThread().toString()));

        // And give a summary of the total number of threads that were used in the parallelStream()
        System.out.println("Used threads: " + threadId.size());
    }
}

当使用一个小列表(将 10000 更改为 2)时,将使用一个线程。在我的机器上使用给定的常量 12 个线程。根据您数据库中的条目数量以及您使用的性能和 JDK 版本,这些数字会有所不同,但总的来说:除非您知道它的作用,否则请避免使用 parallelStream()。

【讨论】:

    【解决方案2】:

    我也没有明确的答案,但我注意到这似乎源于您在并行化时有时/总是会丢失您的事务。似乎不同的线程不能/不能访问其父级的事务。

    考虑:Spring transaction manager and multithreading

    https://dzone.com/articles/spring-transaction-management-over-multiple-thread-1

    我相信这就是原因。

    【讨论】:

      【解决方案3】:

      使用

      stream()
      

      而不是

      parallelStream()
      

      .

      我不太确定它是如何工作的,但是当使用 parallelStream() 时,您会同时对多个对象进行操作。这可能会导致您在某一时刻对同一个集合有不同的引用,而这在 Hibernate 不允许的特定情况下会导致异常。

      【讨论】:

      • 不要误会我的意思,但这根本不能解释任何事情。
      猜你喜欢
      • 2018-11-28
      • 1970-01-01
      • 2013-08-16
      • 2015-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-11
      相关资源
      最近更新 更多