JPA @ManyToMany java.lang.StackOverflowError: null

JPA 懒加载的一个坑 大神可绕路

package com.liuzm.controller;

import com.liuzm.entity.UacRole;
import com.liuzm.entity.UacUser;
import com.liuzm.service.UacUserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Set;

@RestController
public class UserController {

    @Resource
    private UacUserService uacUserService;

    @GetMapping("/")
    public Set<UacRole> findByLoginName() {
        UacUser uacUser = this.uacUserService.findByLoginName("admin");
        System.out.println(uacUser);
        Set<UacRole> roles = uacUser.getRoles();
        System.out.println(roles.size());
        return roles;
    }
}

package com.liuzm.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "tbl_uac_role")
public class UacRole implements Serializable {
    /**
     * serialVersionUID:用一句话描述这个变量表示什么.
     * @since JDK 1.7
     */
    private static final long serialVersionUID = -6263200210499558891L;

    /**
     * ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 角色编码
     */
    @Column(name = "role_code")
    private String roleCode;
    @JsonIgnore
    @ManyToMany
    @JoinTable(name = "tbl_uac_role_user", joinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")})
    private Set<UacUser> users = new HashSet<>();

    public UacRole() {
    }

    public UacRole(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleCode() {
        return roleCode;
    }

    public void setRoleCode(String roleCode) {
        this.roleCode = roleCode;
    }

    public Set<UacUser> getUsers() {
        return users;
    }

    public void setUsers(Set<UacUser> users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "UacRole{" +
                "id=" + id +
                ", roleCode='" + roleCode + '\'' +
                ", users=" + users +
                '}';
    }
}
package com.liuzm.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "tbl_uac_user")
public class UacUser implements Serializable {
    /**
     * serialVersionUID:用一句话描述这个变量表示什么.
     * @since JDK 1.7
     */
    private static final long serialVersionUID = 3439493474130693948L;

    /**
     * ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 登录名
     */
    @Column(name = "login_name")
    private String loginName;
    @ManyToMany(mappedBy = "users")
    @JsonIgnore
    private Set<UacRole> roles = new HashSet<>();

    @Override
    public String toString() {
        return "UacUser{" +
                "id='" + id + '\'' +
                ", loginName='" + loginName + '\'' +
                ", roles=" + roles +
                '}';
    }



    public UacUser() {
    }
    public UacUser(Long id) {
        this.id = id;
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public Set<UacRole> getRoles() {
        return roles;
    }

    public void setRoles(Set<UacRole> roles) {
        this.roles = roles;
    }
}

package com.liuzm.repository;

import com.liuzm.entity.UacUser;
import org.springframework.data.jpa.repository.JpaRepository;


public interface UacUserRepository extends JpaRepository<UacUser, Long> {

    UacUser findByLoginName(String loginName);
}

package com.liuzm.service;

import com.liuzm.entity.UacUser;

public interface UacUserService {

    UacUser findByLoginName(String loginName);
}

server:
  port: 8000
  tomcat:
    uri-encoding: UTF-8
spring:
  application:
    name: jpa-test
  jpa:
      generate-ddl: false
      show-sql: true
      hibernate:
        ddl-auto: update
      properties:
        hibernate:
          dialect: org.hibernate.dialect.MySQL5Dialect
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
logging:
  level:
    root: INFO
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

错误如下

Hibernate: select uacuser0_.id as id1_2_, uacuser0_.login_name as login_na2_2_ from tbl_uac_user uacuser0_ where uacuser0_.login_name=?
2017-04-19 13:49:46.187 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [admin]
2017-04-19 13:49:46.191 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_] : [BIGINT]) - [222]
2017-04-19 13:49:46.195 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([login_na2_2_] : [VARCHAR]) - [admin]
Hibernate: select roles0_.user_id as user_id2_1_0_, roles0_.role_id as role_id1_1_0_, uacrole1_.id as id1_0_1_, uacrole1_.role_code as role_cod2_0_1_ from tbl_uac_role_user roles0_ inner join tbl_uac_role uacrole1_ on roles0_.role_id=uacrole1_.id where roles0_.user_id=?
2017-04-19 13:49:46.208 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [222]
2017-04-19 13:49:46.213 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [1234]
2017-04-19 13:49:46.213 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([role_cod2_0_1_] : [VARCHAR]) - [admin]
2017-04-19 13:49:46.214 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([user_id2_1_0_] : [BIGINT]) - [222]
2017-04-19 13:49:46.214 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([role_id1_1_0_] : [BIGINT]) - [1234]
Hibernate: select users0_.role_id as role_id1_1_0_, users0_.user_id as user_id2_1_0_, uacuser1_.id as id1_2_1_, uacuser1_.login_name as login_na2_2_1_ from tbl_uac_role_user users0_ inner join tbl_uac_user uacuser1_ on users0_.user_id=uacuser1_.id where users0_.role_id=?
2017-04-19 13:49:46.217 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1234]
2017-04-19 13:49:46.218 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [222]
2017-04-19 13:49:46.218 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([role_id1_1_0_] : [BIGINT]) - [1234]
2017-04-19 13:49:46.218 TRACE 14504 --- [nio-8000-exec-1] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([user_id2_1_0_] : [BIGINT]) - [222]
2017-04-19 13:49:46.271 ERROR 14504 --- [nio-8000-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause

java.lang.StackOverflowError: null
    at java.util.AbstractCollection.toString(AbstractCollection.java:454) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacUser.toString(UacUser.java:37) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacRole.toString(UacRole.java:69) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacUser.toString(UacUser.java:37) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacRole.toString(UacRole.java:69) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacUser.toString(UacUser.java:37) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacRole.toString(UacRole.java:69) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacUser.toString(UacUser.java:37) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at com.liuzm.entity.UacRole.toString(UacRole.java:69) ~[classes/:na]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]
    at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60]
    at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60]
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:300) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60]

在网上查了一下,和大神们的问题略有不同,这里具体原因是 两个类都有toString(), 而两个类中同时使用了懒加载(默认)这样在A类toString的时候会发sql查询B类,而B类toString()又会发sql查A类这样会发生死循环最后导致java.lang.StackOverflowError.初用JPA 出现问题引以为鉴.

完整代码

demo

最后做一下说明 如果是springmvc jackson报response错误 那么最好是 多对多维护关系都不要toString() Set集合对象, 然后使用 dto 或者 vo 去做数据的传输去解决这个问题. 具体原因有待日后解决

<think>好的,我现在需要帮助用户解决他们遇到的`StackOverflowError`问题。首先,用户提供了一段错误日志,显示在特定的日期和时间,应用程序在处理请求时抛出了这个错误。错误信息提到了`dispatcherServlet`和`java.lang.StackOverflowError`,根本原因是`null`。用户之前询问过关于Spring Security登录接口的问题,现在可能是在开发过程中遇到了运行时错误,需要理解这个错误的原因和解决方法。 首先,我需要回忆一下`StackOverflowError`通常是什么原因引起的。这种错误通常是由于无限递归或者过深的递归调用导致的。在Java中,每个线程的栈大小是有限的,当方法调用层次过深,超出了栈的容量,就会抛出这个错误。常见的场景包括递归函数没有正确的终止条件,或者在对象之间的相互引用导致序列化时的无限循环。 接下来,我需要结合用户提供的错误日志来分析。错误发生在`dispatcherServlet`中,说明可能是在处理HTTP请求时,Spring MVC的调度过程中出现了问题。错误信息还提到了`Handler dispatch failed`,这可能表示在请求分发给控制器或者某个处理器时发生了异常。而根本原因是`StackOverflowError`,但具体原因显示为`null`,这可能是因为异常信息没有正确捕获或者日志输出配置的问题,需要进一步分析代码。 用户之前的问题是关于Spring Security的登录接口,所以可能当前的问题也是在这个上下文中出现的。例如,可能在某个控制器方法中,存在递归调用,或者在实体类之间存在循环引用,导致JSON序列化时无限循环,从而耗尽栈空间。 可能的步骤包括: 1. **检查递归调用**:查看最近修改的代码,特别是控制器或服务层中的方法,是否存在方法A调用方法B,而方法B又调用方法A的情况,导致无限递归。 2. **检查实体类的序列化**:如果使用了如Jackson这样的库来序列化对象为JSON,实体类之间的双向关联(如父子关系互相引用)可能导致无限循环。例如,一个`User`实体包含一个`Order`列表,而每个`Order`又引用了同一个`User`,这样在序列化时如果没有适当的注解(如`@JsonIgnore`),就会导致无限递归,从而引发栈溢出。 3. **检查AOP或拦截器配置**:如果使用了面向切面编程(AOP)或拦截器,可能存在不正确的切入点表达式,导致方法被重复拦截,形成循环调用。 4. **检查依赖注入问题**:使用Spring的依赖注入时,如果存在循环依赖(如Service A依赖Service B,而Service B又依赖Service A),在应用启动时Spring通常会警告,但在某些情况下可能在运行时导致问题,尤其是在使用构造函数注入时。 5. **查看完整的堆栈跟踪**:用户提供的日志可能只是部分,完整的堆栈跟踪会显示具体的调用链,帮助定位问题发生的位置。例如,如果堆栈中反复出现同一个方法,就能确定是递归调用的问题。 6. **日志和调试**:建议用户启用更详细的日志,或者在开发环境中使用调试模式,逐步跟踪请求处理流程,查看在哪里进入无限循环。 针对用户的情况,可能需要具体询问他们最近是否有新增的代码,特别是控制器、服务层或实体类。另外,检查实体类之间的关系,是否有双向引用未处理的情况。例如,在使用JPA时,`@OneToMany`和`@ManyToOne`注解可能导致循环引用,需要配合`@JsonIgnore`或`@JsonManagedReference`和`@JsonBackReference`来解决序列化问题。 此外,用户可能没有意识到在某些框架配置中,如Spring Data REST或Spring HATEOAS,自动生成的链接或资源可能会导致意外的递归。需要检查相关配置是否合理。 总结来说,需要引导用户检查代码中的递归调用、实体类的序列化问题以及依赖注入的循环依赖,同时建议他们查看完整的堆栈跟踪或提供更多代码细节,以便更准确地定位问题。</think>让我们逐步分析这个`StackOverflowError`的成因和解决方案: --- ### **错误本质解析** ``` java.lang.StackOverflowError: null ``` 这是JVM抛出的**栈溢出错误**,表示线程的调用栈深度超过了JVM栈内存限制(默认约1MB)。与空指针异常不同,这种错误几乎总是由**代码逻辑缺陷**引起。 --- ### **直接触发场景(按概率排序)** 1. **无限递归调用** ```java // 典型错误示例 public void recursiveMethod() { recursiveMethod(); // 没有终止条件 } ``` 2. **双向循环依赖序列化**(常见于JSON转换) ```java // 实体类A public class User { private List<Order> orders; } // 实体类B public class Order { private User user; // 反向引用未处理 } ``` 3. **AOP/拦截器循环拦截** ```java @Around("execution(* com.example.service.*.*(..))") public Object wrongAdvice(ProceedingJoinPoint pjp) { return wrongAdvice(pjp); // 错误:递归调用切面自身 } ``` --- ### **结合你的错误日志定位问题** 从日志关键信息分析: ``` [dispatcherServlet] threw exception [...] Handler dispatch failed ``` 说明错误发生在**请求处理链路中**,极可能与以下场景相关: #### **场景1:控制器/服务层递归调用** - **典型表现**:控制器A方法调用服务B方法,服务B又回调控制器A方法 - **检查点**: - 最近新增的接口方法 - 存在`@Transactional`注解的方法间调用 - 静态工具类中的循环调用 #### **场景2:Jackson序列化循环引用** - **触发条件**: ```java // 返回双向引用的对象 @GetMapping("/user") public User getUser() { return userRepository.findById(1L); // User包含Order,Order反向引用User } ``` - **解决方案**: ```java public class Order { @JsonBackReference // 标记反向引用端 private User user; } public class User { @JsonManagedReference // 标记主控端 private List<Order> orders; } ``` #### **场景3:Spring代理类生成异常** - **特殊案例**:当使用`this.xxx()`调用同类中的AOP增强方法时 ```java @Service public class PaymentService { public void process() { this.validate(); // 错误:绕过代理导致AOP失效 } @Transactional public void validate() {...} } ``` - **正确写法**:注入自身代理对象 ```java @Autowired private PaymentService selfProxy; // 通过ApplicationContext获取 public void process() { selfProxy.validate(); // 通过代理调用 } ``` --- ### **调试与修复步骤** 1. **查看完整堆栈轨迹** ```bash 在日志中查找重复出现的类和方法名模式,例如: com.example.Controller.login() -> com.example.Service.validate() -> com.example.Controller.login() (出现循环) ``` 2. **关键检查工具** - **Lombok警告**:检查是否因`@Data`注解生成`toString()`/`hashCode()`导致循环 - **JPA实体关系**:验证`@OneToMany`/`@ManyToOne`是否配置了`@JsonIgnore` - **Swagger配置**:某些旧版本Swagger会扫描Controller导致递归 3. **临时解决方案(测试环境)** ```java // 在application.properties中增加栈深度(不推荐长期使用) -Xss2m ``` --- ### **经典案例参考** #### 案例:用户权限校验递归 ```java @Controller public class AuthController { @Autowired private AuthService service; @PostMapping("/login") public ResponseEntity<?> login() { service.checkPermission(); // 进入Service层 } } @Service public class AuthService { public void checkPermission() { // 错误:直接调用Controller方法 authController.login(); // 形成循环调用链 } } ``` #### 修复方案:解耦调用层级 ```java @Service public class AuthService { // 通过事件机制或异步处理解耦 @Autowired private ApplicationEventPublisher eventPublisher; public void checkPermission() { eventPublisher.publishEvent(new AuthEvent(...)); } } ``` --- ### **总结建议** 1. 优先检查**最近修改的Controller/Service代码** 2. 使用`Postman`测试接口时**观察响应数据是否无限嵌套** 3. 在实体类中**显式配置`@JsonIgnore`或引用关系注解** 4. 在递归方法中**添加终止条件**和**深度计数器** 如果提供更多上下文代码(如控制器方法、实体类结构),可以进一步精准定位问题根源。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值