Issue
I have a few models that has relationship to each other within this object I am tryin to save and fetch from the database, but I can't seem to get it to work properly. The object contains a property that also contains another object. My approach was to save to all related models and for those models, I would also save this current model to them as well so it shows relation on the database table.
Below the models for the app.
@Entity
@Table(name = "daily_entry")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class DailyEntry {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "daily_entry_id", unique = true)
private Long id;
@Column(unique = true)
private LocalDate date;
private int weight;
@ManyToOne
@JoinColumn(name="user_id")
private User user;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "daily_macros_id", unique = true)
private DailyMacros dailyMacros;
@OneToMany(mappedBy = "dailyEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Exercise> exercise = new ArrayList<Exercise>();
}
@Entity
@Table(name = "daily_macros")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class DailyMacros {
@Id
@GeneratedValue
@Column(name = "daily_macros_id")
private Long id;
private int calories;
private int protein;
private int fat;
private int carbs;
@OneToOne(mappedBy = "dailyMacros", cascade = CascadeType.ALL, orphanRemoval = true)
private DailyEntry dailyEntry;
}
@Setter
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name= "exercises")
public class Exercise {
@Id
@GeneratedValue
@Column(name = "exercise_id")
private Long id;
private String name;
private int sets;
private int reps;
private int weight;
@ManyToOne
@JoinColumn(name = "daily_entry_id", unique = true)
private DailyEntry dailyEntry;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
private String role = "user";
private String token;
@Column(unique = true, nullable = false)
protected String username;
@Column(unique = true ,nullable = false)
private String emailAddress;
@Column(nullable = false)
private String password;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "macros_goal_id")
private MacrosGoal macrosGoal;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DailyEntry> dailyEntry = new ArrayList<>();
}
Im tryin to save the model of DailyEntry to the database. User is the owner of DailyEntry DailyEntry owns MacrosGoal (@OneToOne) and Exercise (@OneToMany). How exactly can I save and fetch this model into the database?
This is what I've tried - For my PutMapping, I get this error : Cannot call sendError() after the response has been committed
My GetMapping just returns a empty response although http status code 200
DailyEntryController file
@RequestMapping(path = "/api/v1/")
@RequiredArgsConstructor
public class DailyEntryController {
@Autowired
DailyEntryService dailyEntryService;
@GetMapping("/getDailyEntry")
public ResponseEntity<DailyEntry> getDailyEntry(@RequestParam("username") String username, @RequestParam String date) throws ResponseStatusException {
DailyEntry dailyEntry = dailyEntryService.getDailyEntry(username, date);
return new ResponseEntity<>(dailyEntry, HttpStatus.OK);
}
@PutMapping("/addDailyEntry")
public ResponseEntity<DailyEntry> addDailyEntry(@RequestBody DailyEntry dailyEntry, @RequestParam("username") String username) throws ResponseStatusException {
DailyEntry dailyEntryInfo = dailyEntryService.createDailyEntry(dailyEntry, username);
return new ResponseEntity<>( dailyEntryInfo, HttpStatus.OK);
}
}
@Slf4j
@Service
public class DailyEntryServiceImpl implements DailyEntryService {
@Autowired
DailyEntryRepository dailyEntryRepository;
@Autowired
ExerciseRepository exerciseRepository;
@Autowired
UserRepository userRepository;
@Autowired
DailyMacrosRepository dailyMacrosRepository;
@Override
public DailyEntry addExercise(Exercise exercise) {
return null;
}
@Override
public DailyEntry getDailyEntry(String username, String date) {
DailyEntry entry = null;
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
LocalDate localDate = LocalDate.parse(date);
List<DailyEntry> dailyEntryList = user.getDailyEntry();
for(DailyEntry e : dailyEntryList) {
if(e.getDate() == localDate){
entry = e;
}
}
return entry;
}
@Override
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
DailyEntry entry = new DailyEntry();
// Find user
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
entry.setUser(user);
entry.setDate(LocalDate.parse(dailyEntry.getDate().toString()));
entry.setDailyMacros(dailyEntry.getDailyMacros());
entry.setWeight(dailyEntry.getWeight());
entry.setExercise(dailyEntry.getExercise());
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(entry);
dailyMacrosRepository.save(dailyMacros);
List<Exercise> exercise = dailyEntry.getExercise();
for (Exercise e : exercise) {
e.setDailyEntry(entry);
exerciseRepository.save(e);
}
List<DailyEntry> dailyEntryList = user.getDailyEntry();
dailyEntryList.add(entry);
user.setDailyEntry(dailyEntryList);
userRepository.save(user);
return entry;
}
I also tried with Query inside repository, but I may have done it wrong.
Solution
You should probably add @Transactional
to createDailyEntry
.
I don't really understand what's happening in createDailyEntry
but I think it should look something like this:
@Override
@Transactional
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
User user = userRepository
.findByUsername(username)
.get();
dailyEntryRepository.save(dailyEntry);
user.getDailyEntry().add(dailyEntry);
dailyEntry.setUser(User);
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(dailyEntry);
dailyMacrosRepository.save(dailyMacros);
dailyEntry.getExcercise()
.forEach( e -> {
e.setDailyEntry(dailyEntry);
exerciseRepository.save(e);
});
// I don't think you need this because user is already managed
// and userFromDatabase.get() will throw an exception
// if the user does not exist.
// userRepository.save(user);
return dailyEntry;
}
I think getDailyEntry
doesn't work because you are using e.getDate() == localDate
and it's always going to be false
.
One solution is to add a method to the DailyEntryRepository that accept username
and date
:
interface DailyEntryRepository extends JpaRepository<DailyEntry, Long> {
@Query("select de from DailyEntry de where de.user.username = :username and de.date = :localDate")
DailyEntry findByUsernameAndDate(@Param("username") String username, @Param("localDate") LocalDate date);
}
...
@Override
public DailyEntry getDailyEntry(String username, String date) {
LocalDate localDate = LocalDate.parse(date);
return dailyEntryRepository.findByUsernameAndDate(username, localdate);
}
This solution will run a query and load only the entry you need.
But, if you need to validate the username, this should work:
@Override
public DailyEntry getDailyEntry(String username, String date) {
User user = userRepository
.findByUsername(username)
.get();
LocalDate localDate = LocalDate.parse(date);
for(DailyEntry e : user.getDailyEntry()) {
if (localDate.equals(e.getDate)){
// No need to continue the loop, you've found the entry
return entry;
}
}
// daily entry not found
return null;
}
Another thing that's missing is that in your model you are not handling the bidirectional association during convertion to JSON.
The solution is to use I end up using @JsonManagedReference
and @JsonBackReference
where you have the association.
Answered By - Davide D'Alto
Answer Checked By - Timothy Miller (JavaFixing Admin)