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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:50:23