首页 > 解决方案 > 为什么当多对一是惰性的时,Hibernate 会出现 StackOverflowError?

问题描述

我有下表架构,其中模拟有很多搜索,任何搜索都有很多属性

表模式

由于我想同时保存一个 Simulation 实体及其搜索及其属性因此我像这样映射我的实体:

模拟.java

@Data
@EqualsAndHashCode(of = "id")
@ToString(exclude = "searches")
@Entity
@Table(name = "SIMULATION")
public class Simulation implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "simulation_generator")
    @SequenceGenerator(name = "simulation_generator", sequenceName = "SIMULATION_SEQ", allocationSize = 1)
    private Long id;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "SIMULATION_ID")
    private Set<SimulationSearch> searches = new HashSet<>(0);

    // other fields
}

模拟搜索.java

@Data
@EqualsAndHashCode(of = "id")
@ToString(exclude = "properties")
@Entity
@Table(name = "SIM_SEARCH")
public class SimulationSearch implements Serializable {

    @EmbeddedId
    private SimulationSearchId id;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumns({
            @JoinColumn(name = "SIMULATION_ID", referencedColumnName = "SIMULATION_ID"),
            @JoinColumn(name = "POSITION", referencedColumnName = "POSITION")
    })
    private Set<SimulationSearchProperty> properties = new HashSet<>(0);

    // other fields...

    @Data
    public static class SimulationSearchId implements Serializable {
        @ManyToOne
        @JoinColumn(name = "SIMULATION_ID", insertable = false, updatable = false)
        private Simulation simulation;
        private int position;
    }
}

SimulationSearchProperties.java

@Data
@EqualsAndHashCode(of = "id")
@Entity
@Table(name = "SIM_SEARCH_PROPERTY")
public class SimulationSearchProperty implements Serializable {
    @EmbeddedId
    private SimulationSearchPropertyId id;
    private String value;

    @Data
    public static class SimulationSearchPropertyId implements Serializable {
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumns({
                @JoinColumn(name = "SIMULATION_ID", referencedColumnName = "SIMULATION_ID", insertable = false, updatable = false),
                @JoinColumn(name = "POSITION", referencedColumnName = "POSITION", insertable = false, updatable = false)
        })
        private SimulationSearch search;
        private String label;
    }
}

发生的事情是 Hibernate 一直打印以下查询,直到它发生 StackOverflowError。

select simulation0_.*, searches1_.*, properties5_.*
  from simulation simulation0_ 
  left outer join sim_search searches1_ on simulation0_.id = searches1_.simulation_id
  left outer join sim_search_property properties5_ on searches1_.position = properties5_.position and searches1_.simulation_id = properties5_.simulation_id
 where simulation0_.id = ?

虽然和之间的映射与Simulation和映射SimulationSearch非常相似SimulationSearch,但当我将注释设置为惰性获取SimulationSearchProperty时开始发生此错误,即使我也设置为惰性也不会停止。ManyToOneSimulationSearch#propertiesSimulationSearchPropertyId#search

我错过了什么?

更新

我正在使用 Hibernate 4.2.6.Final

部分堆栈跟踪日志:

java.lang.StackOverflowError
    at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:148)
    at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:104)
    at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:81)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2114)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:82)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:72)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3927)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:460)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:429)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:206)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:262)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1092)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1019)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)
    at org.hibernate.type.EntityType.resolve(EntityType.java:490)
    at org.hibernate.type.ComponentType.resolve(ComponentType.java:667)
    at org.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:349)
    at org.hibernate.type.ManyToOneType.hydrate(ManyToOneType.java:190)
    at org.hibernate.type.ComponentType.hydrate(ComponentType.java:642)
    at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:775)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:708)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:943)
    at org.hibernate.loader.Loader.doQuery(Loader.java:911)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:342)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:312)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2121)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:82)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:72)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3927)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:460)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:429)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:206)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:262)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1092)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1019)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)
    at org.hibernate.type.EntityType.resolve(EntityType.java:490)
    at org.hibernate.type.ComponentType.resolve(ComponentType.java:667)
    at org.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:349)
    at org.hibernate.type.ManyToOneType.hydrate(ManyToOneType.java:190)
    at org.hibernate.type.ComponentType.hydrate(ComponentType.java:642)
    at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:775)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:708)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:943)
    at org.hibernate.loader.Loader.doQuery(Loader.java:911)
    ...

我刚刚更新了一些实体映射,删除了mappedBy注释属性并添加了@JoinColumn注释。现在持久性工作正常,但是当我尝试加载单个模拟时 StackOverflowError 仍然存在。

我还清理了 Hibernate 生成的 sql,删除了无趣的信息。

标签: javahibernatestack-overflowhibernate-mapping

解决方案


您忘记将您的关系注释为双向的。

对于你的第一堂课,变化应该是

@Data
@EqualsAndHashCode(of = "id")
@ToString(exclude = "searches")
@Entity
@Table(name = "SIMULATION")
public class Simulation implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "simulation_generator")
    @SequenceGenerator(name = "simulation_generator", sequenceName = "SIMULATION_SEQ", allocationSize = 1)
    private Long id;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "SimulationSearchId")
    private Set<SimulationSearch> searches = new HashSet<>(0);

    // other fields
}

如果没有该属性mappedBy,JPA 提供程序会假定您有 2 个单向关系,这会导致您的数据之间存在无限循环引用。

参考

JSR 338:Java TM 持久性 API,版本 2.1

11.1.40 一对多注解

第 4 段,第 2 句

如果关系是双向的,则该 mappedBy元素必须用于指定作为关系所有者的实体的关系字段或属性。


推荐阅读