You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot删除Hotel实体时触发java.lang.StackOverflowError问题

解决Hotel删除时触发StackOverflowError的问题

问题背景

存在Hotel(父)与User(子)的一对多关联关系,user_master表通过hotelId关联hotel_master表。执行Hotel删除操作时,尝试先删除关联的User数据,触发了java.lang.StackOverflowError。

相关代码

实体类

public class Hotel {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer hotelId;
    private String hotelName;
    private String city;

    @JsonManagedReference
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "hotel")
    private Set<User> users;

    // getter、setter 省略
}

public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId;
    private String userName;
    private String email;
    private String password;

    @JsonBackReference
    @ManyToOne(targetEntity = Hotel.class)
    @JoinColumn(name = "hotelId")
    private Hotel hotel;

    // getter、setter 省略
}

Service实现类

@Override
public void deleteHotel(Integer hotelId) {
    Hotel hotel = hotelRepo.findById(hotelId).get();
    for (User user : hotel.getUsers()) {
        userRepo.deleteById(user.getUserId());
    }
    hotelRepo.delete(hotel);
}

Controller

@RequestMapping(value = "/{hotelId}", method = RequestMethod.DELETE)
public void deleteHotel(@PathVariable("hotelId") Integer hotelId) {
    hotelService.deleteHotel(hotelId);
}

报错信息

java.lang.StackOverflowError
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:972)
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:390)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:163)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:104)
    at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:705)
    at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
    at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2208)
    at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:595)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)

问题原因

  1. 级联配置冲突:Hotel的@OneToMany设置了cascade = CascadeType.ALL,意味着删除User时会级联操作关联的Hotel;手动删除User后,Hibernate会尝试更新Hotel的users集合,触发集合懒加载,形成循环加载导致栈溢出。
  2. 手动删除与级联重复操作:手动遍历删除User的逻辑,加上级联配置,触发了重复的持久化上下文操作,加剧了循环调用。

解决方案

方案1:利用级联删除简化逻辑

修改Hotel实体的级联类型为CascadeType.REMOVE(仅删除时级联),删除Hotel时Hibernate会自动删除关联的User,无需手动遍历:

修改后的Hotel实体

public class Hotel {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer hotelId;
    private String hotelName;
    private String city;

    @JsonManagedReference
    @OneToMany(cascade = CascadeType.REMOVE, mappedBy = "hotel")
    private Set<User> users;

    // getter、setter 省略
}

简化后的Service代码

@Override
public void deleteHotel(Integer hotelId) {
    hotelRepo.deleteById(hotelId);
}

方案2:手动删除时解除关联并批量操作

如果需要保留手动删除逻辑,先解除User与Hotel的关联,再用批量删除避免循环遍历:

优化后的Service代码

@Override
public void deleteHotel(Integer hotelId) {
    Hotel hotel = hotelRepo.findById(hotelId).orElseThrow(() -> new RuntimeException("Hotel not found"));
    
    // 解除所有关联的User与Hotel的绑定
    for (User user : hotel.getUsers()) {
        user.setHotel(null);
    }
    userRepo.saveAll(hotel.getUsers());
    
    // 批量删除关联的User
    userRepo.deleteAllByHotel_HotelId(hotelId);
    
    // 删除Hotel
    hotelRepo.delete(hotel);
}

新增UserRepository方法(Spring Data JPA)

public interface UserRepo extends JpaRepository<User, Integer> {
    void deleteAllByHotel_HotelId(Integer hotelId);
}

方案3:禁用不必要的级联

如果不需要级联操作,将Hotel的cascade设置为空,仅手动处理删除逻辑:

修改后的Hotel实体

public class Hotel {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer hotelId;
    private String hotelName;
    private String city;

    @JsonManagedReference
    @OneToMany(mappedBy = "hotel") // 移除cascade配置
    private Set<User> users;

    // getter、setter 省略
}

Service代码保持调整后的批量删除逻辑(同方案2)

内容的提问来源于stack exchange,提问作者Faheem azaz Bhanej

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 04:20:32