StackOverflow 错误 - 带有 HashMap <Entity,Integer> 的 JPA 实体

问题描述

我想做一个小生产计算器。我有一个需要 7 秒的 A 部分,它由需要 3 秒的 x 部分 B 和需要 2 秒的 y 部分 C 组成。 C 部分由 n 个 D 部分组成。 所以我在 Class Part 中有一个递归,它的使用次数是“配方”。 我的实体类

@Entity
@Data
public class ProductEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique=true)
    private String name;
    private double productionCycleTimeInSeconds;
    private int batchPerProductionCycle;

    @Enumerated(EnumType.ORDINAL)
    private FacilityEnum facility;

    @ElementCollection
    @JoinTable(name = "end_product_recipe",joinColumns = @JoinColumn(name = "end_product_id"))
    @MapKeyColumn(name = "resourceId")
    @Column(name = "usage_count")
    private Map<ProductEntity,Integer> recipe;
}

为了测试,我想用 3 个基础来初始化表

        ProductEntity ironore = new ProductEntity();
        ironore.setFacility(FacilityEnum.MINING_MACHINE);
        ironore.setName("Iron Ore");
        ironore.setProductionCycleTimeInSeconds(1.0);
        ironore.setBatchPerProductionCycle(1);
        productRepository.save(ironore);

        ProductEntity ironIngot = new ProductEntity();
        ironIngot.setFacility(FacilityEnum.SMELTER);
        ironIngot.setName("Iron Ingot");
        ironIngot.setProductionCycleTimeInSeconds(1.0);
        ironIngot.setBatchPerProductionCycle(1);
        Map<ProductEntity,Integer> recipe = new HashMap<>();
        recipe.put(ironore,1);
        ironIngot.setRecipe(recipe);
        productRepository.save(ironIngot);

        ProductEntity gear = new ProductEntity();
        gear.setFacility(FacilityEnum.ASSEMBLING_MACHINE);
        gear.setName("Gear");
        gear.setProductionCycleTimeInSeconds(1.0);
        gear.setBatchPerProductionCycle(1);
        recipe.clear();
        recipe.put(ironIngot,1);
        gear.setRecipe(recipe);
        productRepository.save(gear);

代码到达最后一行时 (productRepository.save(gear);) 我得到错误

java.lang.StackOverflowError
    at java.util.HashMap$HashIterator.<init>(HashMap.java:1427)
    at java.util.HashMap$EntryIterator.<init>(HashMap.java:1477)
    at java.util.HashMap$EntrySet.iterator(HashMap.java:1014)
    at java.util.AbstractMap.hashCode(AbstractMap.java:528)
    at org.hibernate.collection.internal.PersistentMap.hashCode(PersistentMap.java:574)
    at com.hoernerice.dspcalculator.entity.ProductEntity.hashCode(ProductEntity.java:12)
    at java.util.Objects.hashCode(Objects.java:98)
    at java.util.HashMap$Node.hashCode(HashMap.java:297)
    at java.util.AbstractMap.hashCode(AbstractMap.java:530)
    at org.hibernate.collection.internal.PersistentMap.hashCode(PersistentMap.java:574)
    at com.hoernerice.dspcalculator.entity.ProductEntity.hashCode(ProductEntity.java:12)
    at java.util.Objects.hashCode(Objects.java:98)
    at java.util.HashMap$Node.hashCode(HashMap.java:297)
    at java.util.AbstractMap.hashCode(AbstractMap.java:530)
    at org.hibernate.collection.internal.PersistentMap.hashCode(PersistentMap.java:574)
    at com.hoernerice.dspcalculator.entity.ProductEntity.hashCode(ProductEntity.java:12)
    at java.util.Objects.hashCode(Objects.java:98)
    at java.util.HashMap$Node.hashCode(HashMap.java:297)
    at java.util.AbstractMap.hashCode(AbstractMap.java:530)
    at org.hibernate.collection.internal.PersistentMap.hashCode(PersistentMap.java:574)
    at com.hoernerice.dspcalculator.entity.ProductEntity.hashCode(ProductEntity.java:12)
....

它重复,所以我想我有一个无限循环。但是怎么样?我的错误在哪里?对于前 2 个项目,我将行作为数据库中接受的行。在连接表中是“只是”想要的 ID。这样工作就被接受了

通过替换解决 recipe.clear()

recipe = new HashMap<>();

解决方法

HashMap.hashCode() 负责溢出。

HashMapAbstractMap 扩展,在他的 hashCode() 方法中,对每个条目的哈希码求和:

public int hashCode() {             
    int h = 0;
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();   
    return h;
}

为什么是无限递归循环?

ironIngot.setRecipe(recipe);

以及以下内容

recipe.put(ironIngot,1);

使 HashMap 成为其自身的条目。因此,当它调用 hashCode() 时,它会在计算其条目的哈希码时再次调用他的哈希码方法:next().hashCode()。一次又一次……无限循环。

溢出循环的简化

        MapX
       +--------------+
      /|             /|
     / |            / |
    *--+-----------*  |
    |  |           |  |
    |  |- [MapX]------|-- ----
    |  |           |  |       |
    |  +-----------+--+       |
    | /            | /        |
    |/             |/         |
    *--------------*          |
                       MapX   V
                      +--------------+
                     /|             /|
                    *-+------------* |
                    | |            | |
                    | |- [MapX]----|-|---------    
                    | |            | |        |
                    | +------------+-+        |
                    |/             |/         |
                    *--------------*          |
                                       MapX   V
                                       +--------------+
                                      /|             /|
                                     *-+------------* |
                                     | |            | |
                                     | |- [MapX]--------------      
                                     | |            | |      |
                                     | +------------+-+      |
                                     |/             |/       |
                                     *--------------*        V
                                                     (Road to Overflow Town)

正如您所评论的,创建新的 HashMap 避免了这种情况,因为现在没有地图将自身作为其条目之一包含在内,您破坏了引用链。


解释这种行为的代码是这个,本质上和你的一样:

Map<Object,String> map = new HashMap<>();
map.put(map,"letsLooop");
map.hashCode();

此代码段还会导致 stackoverflow 错误,这是由于 AbstractMaphashCode() 实现中的无限循环,它将在死锁循环中调用其自己的 hashCode 方法结果是一个漂亮的StackOverflow