【问题标题】:findById causes StackOverflowError but findAll workds finefindById 导致 StackOverflowError 但 findAll 工作正常
【发布时间】: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


【解决方案1】:

解决方案

由 Chris 和 PaulD 在 cmets 中提供:

@GetMapping("/v1/goals/{id}")
    ResponseEntity<Goal> singleGoal(@PathVariable Long id) {
        return new ResponseEntity<>(service.getGoalById(id), HttpStatus.OK);
    }

删除 toString() 因为这会导致整个对象被 lombok 序列化,这不考虑杰克逊注释。将 ResponseEntity 更改为 Goal 类型并传入整个实体。

【讨论】:

    猜你喜欢
    • 2020-04-03
    • 1970-01-01
    • 1970-01-01
    • 2015-07-27
    • 1970-01-01
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多