Search code examples
javaspring-bootmapstructmodelmapper

Model Mapper - using custom methods


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.


Solution

  • 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>