Home > front end >  FindById() with a composite key
FindById() with a composite key

Time:12-14

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));
  • Related