You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot删除User实体返回时懒加载JSON序列化异常问题咨询

问题:@OneToMany双向关联删除操作返回实体时的懒加载异常

我在实现@OneToMany双向关联时,删除User实体后返回该对象出现了问题:数据库中用户数据已被删除,但返回User对象时触发警告Could not write JSON: failed to lazily initialize a collection, could not initialize proxy - no Session,POSTMAN返回500内部服务器错误。

相关代码实现

User 实体

@Entity
@Table(name = "users")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class User {

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

    private String username ;
    private String password ;
    private String email ;
    private String firstname ;
    private String lastname ;

    @OneToMany(
            mappedBy = "user" ,
            cascade = CascadeType.ALL ,
            orphanRemoval = true
    )
    private List<Playlist> playlists = new ArrayList<>() ;

    // getters and setters
}

Playlist 实体

@Entity
@Table(name = "playlist")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Playlist {

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

    private String name;
    private String description;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User user;

    // getters and setters

}

UserController 类

@RestController
@RequestMapping("/api/users")
public class userController {

    @Autowired
    UserService userService ;

    // ... 其他控制器映射

    @PostMapping("")
    public User createUser(@RequestBody User reqUser){
        return userService.createUser(reqUser) ;
    }

    @DeleteMapping("/{userID}")
    public User removeUser (@PathVariable Long userID) {
        return userService.removeUser(userID) ;
    }
}

UserService 类中的removeUser方法

@Service
public class UserService {

    @Autowired
    UserRepo userRepo ;

    public List<User> getAllUsers() {
        List <User> users = new ArrayList<>() ;
        userRepo.findAll().forEach(users::add);
        return users ;
    }

    public User createUser(User reqUser) {
        User newUser = new User() ;
        newUser.setUsername(reqUser.getUsername());
        newUser.setEmail(reqUser.getEmail());
        newUser.setPassword(reqUser.getPassword());
        newUser.setFirstname(reqUser.getFirstname());
        newUser.setLastname(reqUser.getLastname());

        // 添加播放列表
        reqUser.getPlaylists().forEach( playlist -> {
            newUser.addPlaylist(playlist);
        });

        userRepo.save(newUser) ;

        return newUser ;
    }
    
    public User removeUser(Long userID) {

        // 检查用户是否存在
        User user = userRepo.findById(userID).orElseThrow(
                () -> new EntityNotFoundException("User doesn't exists with userID " + userID)
        );

        userRepo.deleteById(userID) ;
        return user ;
    }
}

正常查询返回结果

{
    "id": 20,
    "username": "poodie",
    "password": "randomPasswrod",
    "email": null,
    "firstname": "Raj",
    "lastname": "Bhushan",
    "playlists": [
        {
            "id": 27,
            "name": "playlist-01",
            "description": "first playlist",
            "user": 20,
            "songs": []
        },
        {
            "id": 28,
            "name": "playlist-02",
            "description": "first playlist",
            "user": 20,
            "songs": []
        }
    ]
}

删除请求的错误返回

{
    "timestamp": "2023-04-02T14:39:45.433+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/api/users/20"
}

堆栈跟踪警告信息

2023-04-02 20:09:45.418  WARN 26500 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter
.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection, could not initialize proxy - no Session; nested exception is com.fasterxml
.jackson.databind.JsonMappingException: failed to lazily initialize a collection, could not initialize proxy - no Session (through reference chain: com.rudra.musicStreaming
.models.User["playlists"]->org.hibernate.collection.internal.PersistentBag[0]->com.rudra.musicStreaming.models.Playlist["songs"])]

注意:当修改删除方法不返回User对象时,一切正常,但返回User实体对象时就会触发该异常。


问题原因

  1. 懒加载与Session生命周期冲突:JPA中@OneToMany默认是FetchType.LAZY懒加载,Spring事务默认在service方法执行完就提交,Hibernate Session随之关闭。Jackson序列化返回的User对象时,需要访问Playlist中的songs集合(从堆栈跟踪可确认是这个集合触发的异常),而songs也是懒加载集合,没有Session无法去数据库加载数据,因此抛出懒加载异常。
  2. 内存中实体的序列化问题:虽然数据库中的用户数据已被删除,但内存中的User对象依然存在,Jackson序列化时会遍历所有关联属性,包括未初始化的懒加载代理对象,从而触发异常。

解决方案

方案1:提前初始化懒加载集合(Session关闭前)

在删除操作前,主动初始化User的playlists以及Playlist的songs集合,确保在Session还存在时把数据加载到内存:

import org.hibernate.Hibernate;

public User removeUser(Long userID) {
    User user = userRepo.findById(userID).orElseThrow(
            () -> new EntityNotFoundException("User doesn't exists with userID " + userID)
    );
    // 初始化playlists集合
    Hibernate.initialize(user.getPlaylists());
    // 初始化每个playlist的songs集合
    user.getPlaylists().forEach(playlist -> Hibernate.initialize(playlist.getSongs()));
    userRepo.deleteById(userID);
    return user;
}

方案2:修改关联的FetchType为EAGER(不推荐大集合场景)

将User的playlists或Playlist的songs改为FetchType.EAGER,查询时一次性加载所有关联数据:

// User实体中修改@OneToMany注解
@OneToMany(
        mappedBy = "user" ,
        cascade = CascadeType.ALL ,
        orphanRemoval = true,
        fetch = FetchType.EAGER
)
private List<Playlist> playlists = new ArrayList<>() ;

⚠️ 注意:这种方式会导致查询性能下降,不适合关联数据量大的场景。

方案3:返回DTO而非实体类(推荐最佳实践)

创建DTO类,只包含需要返回的字段,避免序列化实体中的懒加载关联属性,同时分离持久层与视图层模型:

// UserDTO类
public class UserDTO {
    private Long id;
    private String username;
    private String email;
    private String firstname;
    private String lastname;
    private List<PlaylistDTO> playlists;

    // 构造方法、getter、setter
}

// PlaylistDTO类
public class PlaylistDTO {
    private Long id;
    private String name;
    private String description;
    private List<SongDTO> songs;

    // 构造方法、getter、setter
}

// 修改UserService的removeUser方法
public UserDTO removeUser(Long userID) {
    User user = userRepo.findById(userID).orElseThrow(
            () -> new EntityNotFoundException("User doesn't exists with userID " + userID)
    );
    userRepo.deleteById(userID);
    
    // 手动转换实体到DTO(也可使用ModelMapper等工具简化)
    UserDTO dto = new UserDTO();
    dto.setId(user.getId());
    dto.setUsername(user.getUsername());
    dto.setEmail(user.getEmail());
    dto.setFirstname(user.getFirstname());
    dto.setLastname(user.getLastname());

    List<PlaylistDTO> playlistDTOs = user.getPlaylists().stream().map(playlist -> {
        PlaylistDTO pDto = new PlaylistDTO();
        pDto.setId(playlist.getId());
        pDto.setName(playlist.getName());
        pDto.setDescription(playlist.getDescription());
        // 如需返回songs,同样转换为SongDTO,注意提前初始化或用DTO避免懒加载
        return pDto;
    }).collect(Collectors.toList());
    dto.setPlaylists(playlistDTOs);
    
    return dto;
}

// 修改UserController的返回类型
@DeleteMapping("/{userID}")
public UserDTO removeUser (@PathVariable Long userID) {
    return userService.removeUser(userID) ;
}

方案4:使用@Transactional延长Session生命周期

在service的removeUser方法上添加@Transactional注解,让Session在方法执行完成(包括序列化返回对象)后再关闭:

import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    // ...其他代码
    
    @Transactional
    public User removeUser(Long userID) {
        User user = userRepo.findById(userID).orElseThrow(
                () -> new EntityNotFoundException("User doesn't exists with userID " + userID)
        );
        userRepo.deleteById(userID);
        return user;
    }
}

⚠️ 注意:这种方式会让删除后的实体处于detached状态,可能引发潜在问题,且如果关联集合较大,会触发不必要的数据库查询。


内容的提问来源于stack exchange,提问作者humblefool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 20:47:01