Spring Boot多对多POST接口报NPE:Groups.getId()返回值为空
解决Spring Boot多对多关联中创建群组的NullPointerException问题
问题描述
我在学习Spring Boot,已实现用户与群组的双向多对多数据库关联(用户可创建多个群组,群组可包含多个用户),期望仅在用户创建/加入群组时维护关联表关系,而非用户注册阶段。但GroupsController中的POST映射接口无法正常工作,Postman返回500错误,IntelliJ抛出异常:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null] with root cause java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null
我使用了Lombok的@Data、@Getter注解,Groups类的getId()方法应可正常调用。以下是相关代码:
GroupsController的POST接口代码
@RestController @RequestMapping("api/groups") // pre-path public class GroupsController{ @Autowired private GroupsService groupsService; @Autowired private UserService userService; @Autowired private final GroupsRepository groupsRepository; @Autowired private UserRepository userRepository; public GroupsController(GroupsRepository groupsRepository) { this.groupsRepository = groupsRepository; } @GetMapping("/all-groups") public List<Groups> getGroups(){ return (List<Groups>) groupsRepository.findAll(); } @PostMapping("/user/{usersId}/create-group") public ResponseEntity<Groups> createGroup(@PathVariable(value = "usersId") Long usersId, @RequestBody Groups groupRequest){ Groups group = userRepository.findById(usersId).map(users -> { long groupsId = groupRequest.getId(); // add and create new group users.addGroup(groupRequest); return groupsRepository.save(groupRequest); }).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId)); return new ResponseEntity<>(group, HttpStatus.CREATED); } }
Groups实体类代码
@Data @Entity @AllArgsConstructor @NoArgsConstructor @Getter @Table(name = "group_collection") public class Groups { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name ="group_name", unique = true, nullable = false, length = 20) private String groupName; @Column(name = "size", nullable = false) private int size; @Column(name = "strict", nullable = false) private boolean strict; @Column(name = "open", nullable = false) private boolean open; @Column(name ="description", length = 300) private String description; @Column(name = "create_time", nullable = false) private LocalDateTime createTime; @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH }, mappedBy = "groups") @JsonIgnore private Set<User> users = new HashSet<>(); public Set<User> getUsers() { return users; } public void setUsers(Set<User> users) { this.users = users; } }
User实体类代码
@Data @Entity @AllArgsConstructor @NoArgsConstructor @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "username", unique = true, nullable = false, length = 100) private String username; @Column(name = "password", nullable = false) private String password; @Column(name = "email", nullable = false) private String email; @Column(name = "create_time", nullable = false) private LocalDateTime createTime; @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) private Role role; @Transient private String accessToken; @Transient private String refreshToken; @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH }) @JoinTable(name = "groups_x_user", joinColumns = { @JoinColumn(name = "users_id") }, inverseJoinColumns = {@JoinColumn(name = "groups_id")}) private Set<Groups> groups = new HashSet<>(); public void addGroup(Groups group) { this.groups.add(group); group.getUsers().add(this); } public void removeGroup(long id){ Groups group = this.groups.stream().filter(g -> g.getId() == id).findFirst().orElse(null); if(group != null){ this.groups.remove(group); group.getUsers().remove(this); } } }
GroupsService实现类代码
@Service public class GroupsServiceImpl implements GroupsService{ private final GroupsRepository groupsRepository; public GroupsServiceImpl(GroupsRepository groupsRepository) { this.groupsRepository = groupsRepository; } @Override public Groups saveGroup(Groups group) { group.setCreateTime(LocalDateTime.now()); return groupsRepository.save(group); } @Override public Optional<Groups> findByGroupName(String groupName) { return groupsRepository.findByGroupName(groupName); } }
问题排查与修复
1. 根因分析
- NullPointerException触发点:
createGroup方法中long groupsId = groupRequest.getId();这行代码,groupRequest是前端传入的新群组对象,此时还未被持久化到数据库,由于使用IDENTITY自增策略,ID在插入数据库后才会生成,所以groupRequest.getId()返回null,拆箱为long时直接抛出空指针异常。 - 未使用Service层逻辑:直接调用
groupsRepository.save(groupRequest),没有通过GroupsService的saveGroup方法设置createTime,会导致数据库非空约束违反。 - 依赖注入冗余:
GroupsRepository同时使用了@Autowired和构造注入,不符合Spring最佳实践,可能引发注入问题。
2. 修复步骤
(1)修改GroupsController的createGroup方法
移除无用的ID获取代码,改用Service层保存群组,调整关联维护顺序(先保存群组,再关联用户):
@PostMapping("/user/{usersId}/create-group") public ResponseEntity<Groups> createGroup(@PathVariable(value = "usersId") Long usersId, @RequestBody Groups groupRequest){ return userRepository.findById(usersId).map(user -> { // 先通过Service保存群组,自动设置createTime Groups savedGroup = groupsService.saveGroup(groupRequest); // 关联用户与已保存的群组 user.addGroup(savedGroup); // 保存用户以更新关联关系 userRepository.save(user); return new ResponseEntity<>(savedGroup, HttpStatus.CREATED); }).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId)); }
(2)修正Controller的依赖注入
移除冗余的@Autowired注解,统一使用构造注入(Spring推荐的方式):
@RestController @RequestMapping("api/groups") public class GroupsController{ private final GroupsService groupsService; private final UserService userService; private final GroupsRepository groupsRepository; private final UserRepository userRepository; // 构造注入所有依赖,无需@Autowired(Spring 4.3+支持) public GroupsController(GroupsService groupsService, UserService userService, GroupsRepository groupsRepository, UserRepository userRepository) { this.groupsService = groupsService; this.userService = userService; this.groupsRepository = groupsRepository; this.userRepository = userRepository; } // ... 其他方法保持不变 }
(3)优化多对多关联维护(可选)
为避免操作未持久化实体引发的Hibernate问题,可以调整User的addGroup方法,确保只关联已持久化的群组:
public void addGroup(Groups group) { if (group.getId() != null) { // 确保群组已被持久化 this.groups.add(group); group.getUsers().add(this); } }
内容的提问来源于stack exchange,提问作者Ilze
相关产品推荐
相关产品推荐

