模型映射器 - 使用自定义方法

Model Mapper - using custom methods

一个 springboot 项目,我需要使用 parent 的指定字段和每个 children 的 newest 的指定字段为仪表板视图构建 DTO。

实体是 Plane,它与 Transponder、Maint Check 和 Transmitter 具有 OneToMany 关系。

飞机

@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<>();

转发器

@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 CheckTransmitter 具有具有 LocalDate 字段的相似实体。

PlaneDTO 看起来像

@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     
}

我试图在服务层中构建这个 PlaneDTO,但我手动对 Transponder、Transmitter 和 Maint Check 列表进行了大部分排序,以从这些列表中获取最新记录。

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

问题有没有更好的方法来计算child的最新记录,更有效地使用模型映射器(自定义方法?)

如果 MapStruct 更好地支持获取最新的 child,我愿意更改为 MapStruct。

我过去曾简单地使用过 ModelMapper。我建议使用 mapstruct,因为我个人觉得它更容易使用。我知道您的映射可以在那里完成;)。在 Mapstruct 中,您的 Mapper 可能看起来像这样:

@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())
   }

您的映射器调用将只有一行:

@Service
@RequiuredArgsConstructor
public class someService{

    private final PlaneMapper planeMapper;

    public void someMethod(){
        ....
        PlaneDTO yourMappedPlaneDTO = planeMapper.planeToPlaneDTO(plane);
        ....
    }

我没有填写所有值。但我希望这个概念是清楚的。

编辑:

您还必须添加“mapstruct-processor”的依赖项,以便可以生成 MapperImpl 类。

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${org.mapstruct.version}</version>
        <scope>provided</scope>
        <optional>true</optional>
    </dependency>

所以这里有一种不同的数据模型方法。我将使用转发器,因为其他都是模拟的。

目标域模型可能如下所示:

@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;
}

在数据库中,存储飞机的 ID 和注册就足够了,因为您可以通过对数据库实体进行适当的查询来确定当前的转发器,例如 where transponder.planeId=id and transponder.assignEnd IS NULL。您当然也可以存储 transponderId,但是您需要注意保持表之间的数据一致。

如果您想要所有转发器的历史记录 - 这对我来说似乎是一个完全不同的用例,您可以通过查询 getTransponderHistoryByPlane(long planeId) 和 [= 这样的查询在单独的服务中轻松检索它14=].

同样,这确实取决于您的用例,并假设您通常只需要一个给定飞机的转发器,特殊情况除外,例如来自不同的端点。

无论如何,这是我对域模型的想法,而你的目标是你的 Dto;然而,这很容易用 mapstruct 来映射(假设你对 maintCheck 和变送器做同样的事情)

@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);
}

你不需要“注册”映射,因为plane和dto中的字段是相同的,@Mapping注释告诉mapstruct使用plane的哪些字段的哪些子字段。