I have a composite primary key made of planId and planDate, when the user gives me both this attributes I cant find a way to retrieve it from my Repo. Should findById work like this?
public Plans assignPlansToMeds(Long id, Long planId, Date planDate) {
Set<Meds> medsSet = null;
Meds meds = medsRepo.findById(id).get();
Plans plans = plansRepo.findById(planId, planDate).get();
medsSet = plans.getAssignedMeds();
medsSet.add(meds);
plans.setAssignedMeds(medsSet);
return plansRepo.save(plans);
}
My Primary Key:
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Embeddable
public class PlansPKId implements Serializable {
private long planId;
private Date planDate; // format: yyyy-mm-dd
}
Plans entity:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "plans")
public class Plans {
@EmbeddedId
private PlansPKId plansPKId;
@Column
private String planName;
@Column
private String weekday;
@ManyToMany
@JoinTable(name = "Plan_Meds", joinColumns = {
@JoinColumn(name = "planDate", referencedColumnName = "planDate"),
@JoinColumn(name = "planId", referencedColumnName = "planId") }, inverseJoinColumns = @JoinColumn(name = "id"))
private Set<Meds> assignedMeds = new HashSet<>();
}
where I ask for the planId and planDate:
@PutMapping("/medicine/{id}/assignToPlan/{planId}/date/{plandate}")
public Plans assignMedToPlan(@PathVariable Long id, @PathVariable Long planId, @PathVariable Date planDate){
return assignService.assignPlansToMeds(id, planId, planDate);
}
CodePudding user response:
The Spring JpaRepository
only allows one type as the ID-type, as can be seen in the javadoc. Therefore, findById
will never accept two arguments.
You need to define your repository with your EmbeddedId-type as ID-type as follows:
@Repository
public interface PlansRepository extends JpaRepository<Plans, PlansPKId> {
}
You can then call the findById
method as follows:
Plans plans = plansRepo.findById(new PlansPKId(planId, planDate))
.orElseThrow(PlansNotFoundException.idAndDate(planId, planDate));
If you dont want to create a new PlansPKId
instance for every query, you could also define a repository method as follows and let Spring derive the query based on the method name:
Optional<Plans> findByPlansPKIdPlanIdAndPlansPKIdPlanDate(long planId, Date planDate);
If you don't like to have a cumbersome method name you could as well define a JPQL query and name the method as you like:
@Query("select p from Plans p where p.plansPKId.planId = :planId and p.plansPKId.planDate = :planDate")
Optional<Plans> findByCompositeId(@Param("planId) long planId, @Param("planDate") Date planDate);
On a side note, I strongly encourage you to use LocalDate
, LocalDateTime
or ZonedDateTime
(depending on your needs) instead of the legacy Date
class.
Moreover, you shouldn't call get()
on an Optional
without checking if it is present. I recently wrote an answer on SO describing how you can create an elegant error handling. If you stuck to my example, you had to create a NotFoundException
and then create the PlansNotFoundException
which extends NotFoundException
. by this means, everytime when a PlansNotFoundException
is thrown in thread started by a web request, the user would receive a 404 response and a useful message if you implement it like this:
public abstract class NotFoundException extends RuntimeException {
protected NotFoundException(final String object, final String identifierName, final Object identifier) {
super(String.format("No %s found with %s %s", object, identifierName, identifier));
}
protected NotFoundException(final String object, final Map<String, Object> identifiers) {
super(String.format("No %s found with %s", object,
identifiers.entrySet().stream()
.map(entry -> String.format("%s %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(" and "))));
}
}
public class PlansNotFoundException extends NotFoundException {
private PlansNotFoundException(final Map<String, Object> identifiers) {
super("plans", identifiers);
}
public static Supplier<PlansNotFoundException> idAndDate(final long planId, final Date planDate) {
return () -> new PlansNotFoundException(Map.of("id", id, "date", date));
}
}
For the Meds
case:
public class MedsNotFoundException extends NotFoundException {
private MedsNotFoundException(final String identifierName, final Object identifier) {
super("meds", identifierName, identifier);
}
public static Supplier<MedsNotFoundException> id(final long id) {
return () -> new MedsNotFoundException("id", id);
}
}
Meds meds = medsRepo.findById(id).orElseThrow(MedsNotFoundException.id(id));