【发布时间】:2022-01-18 15:01:31
【问题描述】:
我在调用 JpaRepository findById() 的 Spring Boot 端点上遇到了一个奇怪的问题。当我向端点 /v1/goals/{id} 发送 GET 请求时,会发生堆栈溢出错误,而对 /v1/goals 的 GET 请求工作正常。
编辑:将错误消息添加到底部
简化的控制器类:
@RestController
public class GoalController {
private final GoalServiceImpl service;
@Autowired
GoalController(GoalServiceImpl service) { this.service = service; }
@GetMapping("/v1/goals")
ResponseEntity<List<Goal>> allGoals() { return new ResponseEntity<>(service.getGoals(), HttpStatus.OK); }
@GetMapping("/v1/goals/{id}")
ResponseEntity<String> singleGoal(@PathVariable Long id) {
return new ResponseEntity<>(service.getGoalById(id).toString(), HttpStatus.OK);
}
}
简化服务类:
@Service
public class GoalServiceImpl implements GoalService {
private final GoalRepository repository;
@Autowired
public GoalServiceImpl(GoalRepository repository) { this.repository = repository; }
public Goal getGoalById(Long id) {
return repository
.findById(id)
.orElseThrow(() -> new GoalNotFoundException(id));
}
public List<Goal> getGoals() { return repository.findAll(); }
}
简化实体类:
@NoArgsConstructor
@Data
@Entity
public class Goal {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long goalId;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "goal")
@JsonIgnoreProperties(value = "goal")
private List<Milestone> milestones;
}
简化里程碑实体:
@NoArgsConstructor
@Data
@Entity
public class Milestone {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long milestone_id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "goal_id")
private Goal goal;
简化的用户实体:
@NoArgsConstructor
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
private String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
@JsonIgnoreProperties(value = "user")
private List<Goal> goals;
}
错误信息:
java.lang.StackOverflowError: null
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.motivate.api.user.User$HibernateProxy$KaIclPZ9.toString(Unknown Source) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at com.motivate.api.goal.Goal.toString(Goal.java:16) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:168) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:473) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
at com.motivate.api.user.User.toString(User.java:11) ~[classes/:na]
at jdk.internal.reflect.GeneratedMethodAccessor58.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.motivate.api.user.User$HibernateProxy$KaIclPZ9.toString(Unknown Source) ~[classes/:na]
【问题讨论】:
-
一个端点正在使用对象并序列化对象的完整列表,而在失败的端点中,您在实例上使用 toString。检查错误以及触发堆栈的原因,但猜测是问题出在您的 toString 实现中。它不是 JSON 序列化方法,可能会递归地遍历整个对象。
-
感谢@Chris - 我已将错误添加到问题中。我没有明确实现 toString,它由 lombok 处理,我正在使用 JsonIgnore 和 JsonIgnoreProperties 来尝试限制潜在的递归。 toString 仍然是问题吗?
-
Lombok 不使用 JSON(杰克逊?)注释,所以是的,它是 toString 方法的实现导致了您的问题。它很有可能在您的所有属性上调用 toString,在用户-目标关系上调用递归。 Lombok 很棒,但前提是你知道它是如何实现的。简单的解决方案 - 不要调用 toString 来返回字符串,而是将目标实例传递给 ResponseEntity 构造函数进行序列化。
-
我同意 Chris 的观点,我不确定你为什么还要调用 toString,因为我看不出这会有什么用,但如果你真的需要你的实体的 String 表示,你总是可以只需编写自己的方法将其转换为字符串,您只需在更改实体中的内容时维护它
-
谢谢你们,你们是对的。更改 ResponseEntity 的类型并删除 toString() 解决了这个问题。我不明白序列化是如何工作的。 @克里斯
标签: spring-boot hibernate spring-mvc jpa spring-data-jpa