Issue
I have this error with JPA when I update a Person with my repository appUserRepository.save(): " 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 " I've looked at posts on stackoverflow regarding this error but can't find any solutions. I also tried the cascadeType.ALL. Just bellow you can see in Class 'WeightRecordController' the post method 'saveWeight'. The 'personRepository.save(personToUpdate);' create error when it's called.
@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;
@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;
@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;
// @Size(min = 3, max = 5, message = "Please choose between MAN or WOMAN")
@Column(name = "sex", nullable = true)
private String sex;
@Min(1917)
@Max(2022)
@Column(name = "year_birth", nullable = true)
private Integer yearBirth;
@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;
@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) {
// TODO user/principal is null... so exception is raise with message "User not
// found"
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();
}
// TODO
@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);
}
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}
Solution
In fact the problem concerned entities mapping. Instead of @ElementCollection i use now @OneToMany for the user's weights list and it's ok. I have imported a screenshoot of my Entities after this change. Thanks for help ! Just bellow my new entity 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;
@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;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<WeightRecord> weightsList;
/*Getters and setters*/
Answered By - Quentin Genet
Answer Checked By - David Goodson (JavaFixing Volunteer)