Spring Boot:同时保存两个实体时出现NullPointerException求助
问题场景
开发Spring Boot应用时,尝试添加包含Sprint列表的Projet实体,调用添加接口触发NullPointerException,报错指向ProjetService类的projetRepository.save(projet)代码行。
相关代码
Projet实体类
import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.util.List; @AllArgsConstructor @NoArgsConstructor @Entity @Getter @Setter public class Projet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String title; private String description; @ManyToMany(mappedBy = "projets") private List<User> users; @OneToMany(mappedBy = "projet") @JsonIgnore private List<Sprint> sprints; }
Sprint实体类
import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.util.Date; @Entity @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class Sprint { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String description; @Temporal(TemporalType.DATE) private Date startDate; @ManyToOne @JsonIgnore private Projet projet; }
ProjetRepository
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import tn.esprit.spring.entities.Projet; @Repository public interface ProjetRepository extends JpaRepository<Projet, Integer> { }
ProjetService实现类
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tn.esprit.spring.entities.Projet; import tn.esprit.spring.entities.Sprint; import tn.esprit.spring.repositories.ProjetRepository; import java.util.List; @Service public class ProjetService implements IProjetService{ @Autowired ProjetRepository projetRepository; @Override public Projet addProject(Projet projet) { List<Sprint> sprints = projet.getSprints(); for ( Sprint sprint: sprints) { sprint.setProjet(projet); } return projetRepository.save(projet); } }
ProjetController控制器
import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import tn.esprit.spring.entities.Projet; import tn.esprit.spring.services.IProjetService; @RequiredArgsConstructor @RequestMapping("/projet") @RestController public class ProjetController { @Autowired IProjetService projetService; @PostMapping("/addProjet") public Projet addProject (@RequestBody Projet project){ return projetService.addProject(project); } }
请求详情
POST请求地址:http://localhost:8085/Exam/projet/addProjet
请求体:
{ "title": "MAP", "description": "Gestion de Mandats, Assignations et Projets", "sprints": [ { "description": "Sprint Projets", "startDate": "2023-05-27" } ] }
错误信息
- Postman返回500错误:
{ "timestamp": "2023-05-17T13:45:51.279+00:00", "status": 500, "error": "Internal Server Error", "path": "/Exam/projet/addProjet" }
- IDE控制台报错:
java.lang.NullPointerException: null at tn.esprit.spring.services.ProjetService.addProject(ProjetService.java:21) ~[classes/:na] at tn.esprit.spring.controllers.ProjetController.addProject(ProjetController.java:20) ~[classes/:na]
2023-05-17 14:56:42.938 ERROR 8448 --- [nio-8085-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/Exam] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
原因分析
报错行对应ProjetService中的这段代码:
List<Sprint> sprints = projet.getSprints(); for ( Sprint sprint: sprints) { sprint.setProjet(projet); }
空指针的核心原因是:Projet实体的sprints字段加了@JsonIgnore注解,导致Jackson无法将请求体中的sprints数组反序列化到Projet对象中,projet.getSprints()返回null,直接遍历null列表触发异常。
解决方法
1. 移除Projet实体中sprints的@JsonIgnore注解
让Jackson能正确映射请求体中的sprints数据:
@OneToMany(mappedBy = "projet") // 移除@JsonIgnore private List<Sprint> sprints;
注:若担心查询Projet时出现循环引用,可保留Sprint实体中projet字段的@JsonIgnore,避免序列化时嵌套返回关联对象。
2. 添加空值判断(兼容方案)
如果需要保留@JsonIgnore(比如不想在查询接口返回sprints),需在Service层先判断列表是否为null:
@Override public Projet addProject(Projet projet) { List<Sprint> sprints = projet.getSprints(); // 先判断列表非空再遍历 if (sprints != null && !sprints.isEmpty()) { for ( Sprint sprint: sprints) { sprint.setProjet(projet); } } return projetRepository.save(projet); }
注意:此方案下请求体中的sprints数据不会被映射到Projet对象,仅用于避免空指针,若需要接收前端传入的sprints数据,优先选择第一种方法。
3. 补充级联保存配置(可选)
为确保Sprint能随Projet自动保存,可在Projet的sprints字段添加级联配置:
@OneToMany(mappedBy = "projet", cascade = CascadeType.ALL) private List<Sprint> sprints;
这样调用projetRepository.save(projet)时,关联的Sprint会自动插入数据库。
内容的提问来源于stack exchange,提问作者Moe_blg

