Spring Data JPA报错:object references an unsaved transient instance求助
问题:Spring Data JPA调用
personRepository.save()更新Person实体时触发TransientObjectException错误 错误日志:
2022-07-16 10:27:30.080 ERROR 4792 --- [nio-7777-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; WeightRecord; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.quentingenet.openweighttracker.entity.WeightRecord] with root cause
调用personRepository.save(personToUpdate)时触发上述错误,已尝试配置CascadeType.ALL但未解决,问题出在WeightRecordController的saveWeight方法中。
相关代码
实体类代码
Person实体
@Entity @Table(name = "person") public class Person implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id_person") private Long idPerson; @ElementCollection private List<WeightRecord> userPersonnalWeightsRecord = new ArrayList<WeightRecord>(); @OneToOne( cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "id_initial_data") private InitialData userInitData; @OneToOne( cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "id_user") private AppUser appUserPerson; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "id_weight_record") private WeightRecord weightRecord; // 省略getter/setter }
WeightRecord实体
@Entity @Table(name = "weight_record") public class WeightRecord implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id_weight_record") private Long idWeightRecord; @Column(name = "weight_record_date", nullable = true) private LocalDate weightRecordDate; @Min(1) @Max(635) @Column(name = "weight_kg_record", nullable = true, precision = 1) private Double weightKgRecord; @Min(1) @Max(99) @Column(name = "percent_fat_mass", nullable = true, precision = 1) private Double percentFatMass; @Min(1) @Max(99) @Column(name = "percent_muscular_mass", nullable = true, precision = 1) private Double percentMuscularMass; @Min(1) @Max(99) @Column(name = "percent_body_water", nullable = true, precision = 1) private Double percentBodyWater; @Min(1) @Max(99) @Column(name = "percent_bone_mass", nullable = true, precision = 1) private Double percentBoneMass; // 省略getter/setter }
InitialData实体
@Table(name = "initial_data") @Entity public class InitialData implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id_initial_data") private Long idInitialData; @Min(5) @Max(636) @Column(name = "goal_weight", nullable = true, precision = 1) private Double goalWeight; @Min(25) @Max(300) @Column(name = "body_size", nullable = true) private Integer bodySize; @Column(name = "sex", nullable = true) private String sex; @Min(1917) @Max(2022) @Column(name = "year_birth", nullable = true) private Integer yearBirth; // 省略getter/setter }
AppUser实体
@Entity @Table(name = "app_users") public class AppUser implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id_user") private Long idUser; @NotEmpty @Size(min = 3, max = 36) @Column(name = "appUsername", nullable = false, unique = true) private String appUsername; @NotEmpty @Size(min = 4, max = 255) @Column(name = "password", nullable = false) private String password; // 省略getter/setter }
控制器与仓库代码
WeightRecordController
@RestController @RequestMapping("/weights") public class WeightRecordController { @Autowired WeightRecordServiceImpl weightRecordServiceImpl; @Autowired WeightRecordRepository weightRecordRepository; @Autowired AppUserRepository appUserRepository; @Autowired PersonRepository personRepository; private final Logger logger = LoggerFactory.getLogger(WeightRecordController.class); public Long getAppUserConnectedId(Principal principal) { if (!(principal instanceof UsernamePasswordAuthenticationToken)) { throw new RuntimeException("User not found"); } logger.info("USER IS PRESENT IN DATABASE FROM FUNCTION 'getAppUserConnectedId()'"); UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) principal; AppUser appUserFinded = appUserRepository.findByAppUsername(token.getName()); return appUserFinded.getIdUser(); } @PostMapping public ResponseEntity<WeightRecord> saveWeight(@RequestBody WeightRecord weightRecord, Principal principal) { logger.info("POST /weights"); Long appUserConnectedId = this.getAppUserConnectedId(principal); Optional<Person> personUserToSave = personRepository.findById(appUserConnectedId); logger.info("PERSON FINDED IN DATABASE"); if (personUserToSave.isPresent()) { logger.info("USER IS PRESENT IN DATABASE FROM saveWeightController"); weightRecordServiceImpl.saveWeightRecord(weightRecord); logger.info("WEIGHT RECORD IS SAVED NOW"); } else { return new ResponseEntity<WeightRecord>(HttpStatus.BAD_REQUEST); } Person personToUpdate = personRepository.findById(appUserConnectedId).orElseThrow(); List<WeightRecord> personWeightsList = personToUpdate.getUserPersonnalWeightsRecord(); personWeightsList.add(weightRecord); personToUpdate.setUserPersonnalWeightsRecord(personWeightsList); logger.info("WEIGHT UPDATED IN PERSONNAL USER LIST WITH ID n°{}",appUserConnectedId); // TODO : FIX PROBLEM just bellow personRepository.save(personToUpdate); logger.info("PERSON WITH ID N°{} IS UPDATED NOW.", personToUpdate.getIdPerson()); return new ResponseEntity<WeightRecord>(weightRecord, HttpStatus.CREATED); } }
PersonRepository
@Repository public interface PersonRepository extends JpaRepository<Person, Long> { }
问题原因与解决方案
核心原因
Person实体中userPersonnalWeightsRecord字段使用了@ElementCollection注解,但WeightRecord是独立的@Entity实体类,并非嵌入式对象(@Embeddable)。@ElementCollection仅适用于映射非实体类或基本类型的集合,无法处理独立实体的关联,导致JPA无法识别级联关系,触发瞬时对象异常。
解决方案
修正实体关联关系
将@ElementCollection替换为@OneToMany注解,明确Person与WeightRecord的一对多关联,并配置级联策略:// Person实体中修改集合字段 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person") private List<WeightRecord> userPersonnalWeightsRecord = new ArrayList<>();同时在WeightRecord实体中添加反向关联:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "person_id") private Person person;优化控制器逻辑
删除冗余的数据库查询和单独保存WeightRecord的操作,利用级联特性直接通过保存Person来同步WeightRecord:@PostMapping public ResponseEntity<WeightRecord> saveWeight(@RequestBody WeightRecord weightRecord, Principal principal) { logger.info("POST /weights"); Long appUserConnectedId = this.getAppUserConnectedId(principal); Person personToUpdate = personRepository.findById(appUserConnectedId) .orElseThrow(() -> new RuntimeException("Person not found")); logger.info("USER IS PRESENT IN DATABASE FROM saveWeightController"); weightRecord.setPerson(personToUpdate); personToUpdate.getUserPersonnalWeightsRecord().add(weightRecord); personRepository.save(personToUpdate); logger.info("PERSON WITH ID N°{} AND WEIGHT RECORD UPDATED.", personToUpdate.getIdPerson()); return new ResponseEntity<>(weightRecord, HttpStatus.CREATED); }清理冗余字段
Person实体中@OneToOne类型的weightRecord字段与集合逻辑冲突(一个用户对应多条体重记录),建议删除该字段,避免数据不一致。
内容的提问来源于stack exchange,提问作者Quentin Genet
相关产品推荐
相关产品推荐

