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实体对象时就会触发该异常。
问题原因
- 懒加载与Session生命周期冲突:JPA中
@OneToMany默认是FetchType.LAZY懒加载,Spring事务默认在service方法执行完就提交,Hibernate Session随之关闭。Jackson序列化返回的User对象时,需要访问Playlist中的songs集合(从堆栈跟踪可确认是这个集合触发的异常),而songs也是懒加载集合,没有Session无法去数据库加载数据,因此抛出懒加载异常。 - 内存中实体的序列化问题:虽然数据库中的用户数据已被删除,但内存中的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
相关产品推荐
相关产品推荐

