Home > Software engineering >  JPA query to get most recent child of child
JPA query to get most recent child of child

Time:04-10

I have a Soring/JPA project with entities Plane, MaintenanceCheck, Transponder.

In terms of ORM dsetup a Plane has many MaintenanceChecks. Most planes are only ever given 1 Transponder (done during a MaintenanceCheck) but very occasionally a plane may have a Transponder changed.

I am also using ModelMapper and DTO's for the views.

Entities

Plane

@Entity
@Data
public class Plane {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;
    @Column(unique = true)
    private String name;
    @JsonIgnore
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "plane")
    private List<MaintCheck> listMaintCheck = new ArrayList<>();

Maintenance Check

@Entity
@Data
public class MaintCheck {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Plane plane;
    private LocalDate checkDate;

    @JsonIgnore
    @OneToMany (cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.MERGE, CascadeType.REFRESH}, orphanRemoval = true, mappedBy = "maintCheck")
    private List<Transponder> listTransponder = new ArrayList<>();

Transponder

@Entity
@Data
public class Transponder {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;
    private String code;

    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private MaintCheck maintCheck;

Plane Dashboard DTO

@Data
public class PlaneDashboardDTO {

    private String name;          // plane name
    private LocalDate last_check; // last maintenance date
    private String code;          // most recent transponder code
}

Why have I structured the entities this way when the real relationship is between Transponder and Plane (a maintenance check is just when a transponder change happens). Because maintenance checks already have a date field, and if transponders only get changed with a maintenance check then I do not end up storing date twice.

TABLE PLANE
ID, NAME
1, NC899
2, N345C

TABLE MAINT CHECK
ID, NAME, DATE, PLANE_ID
1, Check 1, 2022-03-01, 1
2, Check 2, 2022-03-05, 2
3, Check 3, 2022-03-08, 1
4, Check 4, 2022-03-10, 2
5, Check 5, 2022-03-11, 1

TABLE TRANSPONDER
ID, CODE, MAINT_ID
1, DF000AB, 1
2, AB000DC, 3
3, AE000DE, 4

Note not every maintenance check has a transponder record. In fact most don't

Question Assuming this is the correct way to structure the data (and I am not certain it is) how can I get the current (most recent) transponder for a given plane ID into a PlaneDashboardDTO? (I presume this would be done on service layer with either JPQL or SQL)

In the above example I would be retrieving the most recent transponder for Plane ID 1, which had maintenance checks id 1,3, 5. Only maintenance checks 1 and 3 are on the Transponder table for plane id 1 and since maintenance check 3 has the most recent date, the current transponder code is AB000DE.

A fiddle here : https://www.db-fiddle.com/f/pxvtyUrQWz3d7s6tdQFT1B/1

CodePudding user response:

Without testing on a db I would try an SQL like this:

select transponder.code
from transponder
join maint_check on maint_check.id=transponder.maint_id
join plane on plane.id=maint_check.plane_id
where plane.id=1
and maint_check.date=(select max(m.date) from maint_check m join transponder t on t.maint_id=m.id and m.plane_id=1)

CodePudding user response:

In the end, I decided that it was better for Transponder to be a direct child of Plane. i.e. Plane (One) to Transponder (Many).

The process for then getting data out of Plane and Transponder into a DTO sits on the service layer.

PlaneServiceImpl

@Override
public PlaneSummaryDTO getPlaneSummaryDTOById(Long id) {
  Plane plane = this.get(id);
  PlaneSummaryDTO planeSummaryDTO = new PlaneSummaryDTO();
  
    List<Transponder> listTransponder;
    listTransponder = plane.getListTransponder();
    
    Transponder newest = listTransponder.stream().max(Comparator.comparing(Transponder::getDateInserted)).get();
    
    ModelMapper mapper = new ModelMapper();
    planeSummaryDTO = modelMapper.map(get(id), PlaneSummaryDTO.class);
    planeSummaryDTO.setPitCode(newest.getCode());
    return planeSummaryDTO;
}
  • Related