【问题标题】:StackOverflow Error - JPA Entity with HashMap <Entity,Integer>StackOverflow 错误 - 带有 HashMap <Entity,Integer> 的 JPA 实体
【发布时间】:2021-02-03 03:25:20
【问题描述】:

我想做一个小的生产计算器。我有一个需要 7 秒的 A 部分,它由需要 3 秒的 x 部分 B 和需要 2 秒的 C 部分组成。 C 部分由 n 个 D 部分组成。 因此,我在 Class Part 中进行了递归,并将其用作“recipe”的次数。 我的实体类

@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<>();

【问题讨论】:

    标签: java hibernate spring-data-jpa jackson stack-overflow


    【解决方案1】:

    HashMap.hashCode() 负责溢出。

    HashMap 扩展自 AbstractMap,在他的 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();
    

    这个sn-p也会导致stackoverflow错误,由于AbstractMaphashCode()实现中的无限循环,它会在死锁循环中调用自己的hashCode方法,导致美观StackOverflow.

    【讨论】:

    • 有时你看不到森林,因为所有的树。替换了 recipe.clear(); with recipe = new HashMap();谢谢
    • 我编辑我的评论。是的,铁锭配方图的链接仍然存在,所以有循环。通过用新的 HashMap 替换它的 sloved。现在我可以去睡觉并照顾 json 输出循环 ^^ 再次感谢
    • @JörgH.Kunath 你太受欢迎了,伙计!很高兴帮助:)
    猜你喜欢
    • 1970-01-01
    • 2017-07-06
    • 2016-05-13
    • 1970-01-01
    • 1970-01-01
    • 2012-02-02
    • 1970-01-01
    • 1970-01-01
    • 2015-02-19
    相关资源
    最近更新 更多