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)
问题原因
- 级联配置冲突:Hotel的
@OneToMany设置了cascade = CascadeType.ALL,意味着删除User时会级联操作关联的Hotel;手动删除User后,Hibernate会尝试更新Hotel的users集合,触发集合懒加载,形成循环加载导致栈溢出。 - 手动删除与级联重复操作:手动遍历删除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
相关产品推荐
相关产品推荐

