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

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无法识别级联关系,触发瞬时对象异常。

解决方案

  1. 修正实体关联关系
    将@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;
    
  2. 优化控制器逻辑
    删除冗余的数据库查询和单独保存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);
    }
    
  3. 清理冗余字段
    Person实体中@OneToOne类型的weightRecord字段与集合逻辑冲突(一个用户对应多条体重记录),建议删除该字段,避免数据不一致。


内容的提问来源于stack exchange,提问作者Quentin Genet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 21:15:36