【问题标题】:Why does Hibernate goes StackOverflowError when many-to-one is lazy?为什么当多对一是惰性的时,Hibernate 会出现 StackOverflowError?
【发布时间】:2020-06-29 14:00:48
【问题描述】:

我有下表架构,其中 simulation 有很多 searches 并且任何 search 都有很多 properties .

由于我想一次性保存一个 Simulation 实体及其搜索及其属性,因此我将我的实体映射如下:

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
}

SimulationSearch.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 = ?

虽然SimulationSimulationSearch 之间的映射与SimulationSearchSimulationSearchProperty 映射非常相似,但是当我将ManyToOneManyToOne 注释设置为延迟获取并且甚至没有停止时,这个错误就开始发生了如果我也将SimulationSearchPropertyId#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 注释属性并添加了@JoinColumns 注释。 现在持久性工作正常,但是当我尝试加载单个模拟时 StackOverflowError 仍然存在。

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

【问题讨论】:

  • 请同时包含stackoverflow异常的stacktrace。
  • 你用的是什么休眠版本?
  • 我已经更新了我的问题,顺便说一句,异常只是内部调用,我使用的是 Hibernate 4.2.6
  • 看起来您的注释不正确... Hibernate 无法识别您具有双向关系并将您的关系视为单向关系。我稍后会尝试形成一个正确的答案......
  • 这似乎不是导致 stackoverflow 错误的哈希或 toString 递归:我尝试删除 @Data 以支持 @Getter@SetterSimulationSearchPropertyId 但没有任何改变.

标签: java hibernate stack-overflow hibernate-mapping


【解决方案1】:

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

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

@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 OneToMany 注释

第四段第二句

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

【讨论】:

  • 我在两个OneToMany 注释上都添加了mappedBy 属性,但我得到了一个休眠异常:org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: it.netechgroup.fastcheck.simulation.model.SimulationModel.searches
  • 然后,如果我删除JoinColumn 注释,我会回到原来的问题状态,StackoverflowError 仍然存在。
  • @JoinColumn 注释应该/可以保留在@ManyToOne。仅在@OneToMany 端不需要它们。
  • 我只删除了@OneToMany 上的@JoinColumn,但显然没有任何改变。
猜你喜欢
  • 1970-01-01
  • 2013-01-02
  • 2020-09-02
  • 2020-02-24
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 2023-01-22
  • 2020-07-16
相关资源
最近更新 更多