模型映射器 - 使用自定义方法
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 Check
和 Transmitter
具有具有 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的哪些字段的哪些子字段。
一个 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 Check
和 Transmitter
具有具有 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的哪些字段的哪些子字段。