findById 导致 StackOverflowError 但 findAll 工作正常

findById causes StackOverflowError but findAll workds fine

我在调用 JpaRepository findById() 的 spring 引导端点时遇到了一个奇怪的问题。每当我向端点 /v1/goals/{id} 发送 GET 请求时,都会发生堆栈溢出错误,而对 /v1/goals 的 GET 请求工作正常。

编辑:将错误消息添加到底部

简化控制器class:

@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);
    }
}

简化服务class:

@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(); }

}

简化实体class:

@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.WhosebugError: 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]

解决方案

Chris 和 PaulD 在评论中提供:

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

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