Reputation: 2354
A springboot project where I need to construct a DTO for a dashboard view using nominated fields from the parent and nominated fields from the newest
of each of the children.
The entities are Plane
which has a OneToMany relationship with Transponder, Maint Check and Transmitter.
Plane
@Entity
@Data
public class Plane {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
private String registration;
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, mappedBy = "plane")
private List<Transponder> listTransponder = new ArrayList<>();
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, mappedBy = "plane")
private List<Transmitter> listTransmitter = new ArrayList<>();
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, mappedBy = "plane")
private List<MaintCheck> listMaintCheck = new ArrayList<>();
Transponder
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Transponder {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
private String code;
private LocalDate dateInserted;
@ManyToOne(fetch = FetchType.LAZY, optional = true)
private Plane plane;
}
Maint Check
and Transmitter
have similar entities with a LocalDate field.
PlaneDTO looks liike
@Data
public class PlaneDTO {
private String registration;
private LocalDate maintCheck; // date of most recent Maint Check
private String transponderCode; // string of most recent Transponder code
private Integer channel; // Intger of most recent Transmitter Freq
}
I have attempted to consruct this PlaneDTO in the service layer, but I am manually doing much of the sorting of the lists of Transponder, Transmitter and Maint Check to get the most recent record from these lists.
//DRAFT METHOD CONSTRUCT DTO
@Override
public PlaneSummaryDTO getPlaneSummaryDTOById(Long id) {
Plane Plane = this.get(id);
PlaneSummaryDTO PlaneSummaryDTO = new PlaneSummaryDTO();
ModelMapper mapper = new ModelMapper();
PlaneSummaryDTO = modelMapper.map(get(id), PlaneSummaryDTO.class);
PlaneSummaryDTO.setTRANSPONDERCode(getNewestTRANSPONDERCode(Plane));
PlaneSummaryDTO.setLastMaintCheck(getNewestMaintCheckDate(Plane));
PlaneSummaryDTO.setChannel(getTransmitterCode(Plane));
PlaneSummaryDTO.setChannelOffset(getTransmitterOffset(Plane));
return PlaneSummaryDTO;
}
// RETURN NEWEST DATE OF MAINT CHECK BY CATCH DATE
public LocalDate getNewestMaintCheckDate(Plane Plane) {
List<MaintCheck> listMaintCheck = new ArrayList<>(Plane.getListMaintCheck());
MaintCheck newest = listMaintCheck.stream().max(Comparator.comparing(MaintCheck::getCatchDate)).get();
return newest.getCatchDate();
}
// RETURN NEWEST TRANSPONDER CODE FROM Plane BY DATE INSERTED
public String getNewestTransponderCode(Plane Plane) {
List<Transponder> listTransponder = new ArrayList<>(Plane.getListTransponder());
Transponder newest = listTransponder.stream().max(Comparator.comparing(Transponder::getDateInserted)).get();
return newest.getCode();
}
// OTHER METHODS TO GET MOST RECENT RECORD
QUESTION Is there a better way to calculate the most recent record of the child, using model mapper more efficiently (custom method?)
I am open to changing to MapStruct if it better supports getting the most recent child.
Upvotes: 3
Views: 1780
Reputation: 15018
So here's a different approach to the data model. I'll be using Transponder because the others are analog.
The target domain model could look like this:
@Data
class Plane {
Long id;
String registration;
Transponder activeTransponder;
}
@Data
class Transponder {
Long id;
Integer code; // this is a 4-digit octal number, why String? your call though
Long planeId;
Instant assignStart;
Instant assignEnd;
}
In the database, it would be sufficient to store the id and registration for the plane, because you can determine the current transponder with a proper query on the db entity, eg where transponder.planeId=id and transponder.assignEnd IS NULL
. You can also store the transponderId of course, but then you'd need to take care to keep the data consistent between the tables.
If you want a history of all transponders - which to me seems like an entirely different use case to me, you can easily retrieve it in a separate service with a query getTransponderHistoryByPlane(long planeId)
with a query like from transponders t where t.planeId=$planeId sorted by t.assignStart
.
Again, this does depend on your use cases, and assumes that you usually only need one transponder for a given plane except in special cases, like from a different endpoint.
Anyway, this were my thoughts on the domain model, and you were aiming for your Dto; however, this is then easily mapped with mapstruct like (assuming you do the same for maintCheck and transmitter)
@Mapper
interface PlaneDtoMapper {
@Mapping(target = "transponderCode", source = "transponder.code")
@Mapping(target = "maintCheck", source = "maintCheck.date")
@Mapping(target = "channel", source = "transmitter.channel")
PlaneDTO fromPlane(Plane p);
}
You don't need the "registration" mapping because the fields in plane and dto are the same, and the @Mapping
annotations tell mapstruct which subfields of which fields of plane to use.
Upvotes: 0
Reputation: 1763
I briefly used ModelMapper in the past. I would suggest using mapstruct since I personaly find it easier to use. I know your mapping can be done there ;). In Mapstruct your Mapper could look something like this:
@MapperConfig(
componentModel = "spring",
builder = @Builder(disableBuilder = true)
)
public interface PlaneMapper {
@Mapping(target = "lastMaintCheck", ignore = true)
PlaneDTO planeToPlaneDTO(Plane plane);
@AfterMapping
default void customCodePlaneMapping(Plane source, @MappingTarget PlaneDTO target) {
target.setLastMaintCheck(source.getListMaintCheck.stream().max(Comparator.comparing(Transponder::getDateInserted)).get())
}
Your mapper call would then only be one line:
@Service
@RequiuredArgsConstructor
public class someService{
private final PlaneMapper planeMapper;
public void someMethod(){
....
PlaneDTO yourMappedPlaneDTO = planeMapper.planeToPlaneDTO(plane);
....
}
I did not fill in all values. But i hope the concept is clear.
EDIT:
You would also have to add the dependency of "mapstruct-processor" so that the MapperImpl classes can be gererated.
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
Upvotes: 2