背景
在之前的博客中,我们探讨了如何在复杂业务场景中使用多态技术来优雅地处理前后端映射和数据库映射问题。具体可以参考以下两篇文章:
这些文章详细介绍了如何通过多态技术保持数据结构的可维护性,尤其是在复杂的业务逻辑中。然而,在实际开发中,我们还需要处理从请求体到数据库实体之间的映射问题。在博客优雅处理对象映射:MapStruct 实战指南中,我们已经讨论了 MapStruct 的优点和使用方法。MapStruct 不仅适用于简单的对象映射,同样也能在多态场景下大显身手,成为映射前端请求体到数据库实体的最佳选择。
实战
定义前端请求体
关于前端请求体的定义,可以参考博客:优雅处理复杂请求体:Jackson 多态反序列化
定义数据库实体
关于数据库实体的定义,可以参考博客:优雅处理数据库表多态:JPA 的多态映射
定义mapstruct接口类
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class AbstractReadPermissionMapper {
public static final AbstractReadPermissionMapper READ_PERMISSION_MAPPER = Mappers.getMapper(AbstractReadPermissionMapper.class);
@SubclassMapping(target = DateRangePermissionEntity.class, source = DateRangeConditionRequest.class)
@SubclassMapping(target = MemberLevelPermissionEntity.class, source = MemberLevelConditionRequest.class)
@SubclassMapping(target = PaidMemberPermissionEntity.class, source = PaidMemberConditionRequest.class)
@SubclassMapping(target = PointsPermissionEntity.class, source = PointsConditionRequest.class)
public abstract ReadPermissionEntity toEntity(BaseConditionRequest request);
@Mapping(target = "permission", expression = "java(DateRangePermissionEntity.buildPermissionJson(request))")
public abstract DateRangePermissionEntity toDateRangeEntity(DateRangeConditionRequest request);
@Mapping(target = "permission", expression = "java(MemberLevelPermissionEntity.buildPermissionJson(request))")
public abstract MemberLevelPermissionEntity toMemberLevelEntity(MemberLevelConditionRequest request);
@Mapping(target = "permission", expression = "java(PointsPermissionEntity.buildPermissionJson(request))")
public abstract PointsPermissionEntity toDateRangeEntity(PointsConditionRequest request);
@Mapping(target = "permission", expression = "java(PaidMemberPermissionEntity.buildPermissionJson(request))")
public abstract PaidMemberPermissionEntity toMemberLevelEntity(PaidMemberConditionRequest request);
}
在这个示例中,我们使用 @SubclassMapping
注解来定义多态情况下的映射关系,指定源数据类型和目标数据类型。
此外,由于数据库实体中存在内部类 MemberLevel
、PaidMember
、DateRange
和 Points
,而这些内部类中的字段在请求体中则是平铺的,因此我们需要使用 @Mapping
注解的 expression
属性来自定义映射方法。例如:
public class MemberLevelPermissionEntity extends ReadPermissionEntity {
@Type(type = "json")
@Column(name = "permission", columnDefinition = "json")
private MemberLevel permission;
public static MemberLevel buildPermissionJson(BaseConditionRequest request) {
return new MemberLevel(
((MemberLevelConditionRequest) request).getMinLevel(),
((MemberLevelConditionRequest) request).getMaxLevel()
);
}
public static class MemberLevel {
private Integer minLevel;
private Integer maxLevel;
}
}
生成实现类
运行 Spring 后,MapStruct 会自动为这个接口类生成实现类:
可以看到,根据 @SubclassMapping
注解,不同类型的子类会被映射为不同的数据结构。我们以 MemberLevelConditionRequest
为例,详细看一下前端请求体子类映射为数据库实体子类的实现:
可以看到,permission
字段已经按照 @Mapping
中指定的 expression
找到了对应方法进行自定义映射。
测试
@Test
void should_save_entity_given_request() throws JsonProcessingException {
String json1 = "{\n" +
" \"type\": \"MEMBER_LEVEL\",\n" +
" \"minLevel\": 1,\n" +
" \"maxLevel\": 10\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
MemberLevelConditionRequest memberLevelConditionRequest = (MemberLevelConditionRequest) objectMapper.readValue(json1, BaseConditionRequest.class);
ReadPermissionEntity entity1 = service.getEntity(memberLevelConditionRequest);
assertEquals(1, ((MemberLevelPermissionEntity) entity1).getPermission().getMinLevel());
assertEquals(10, ((MemberLevelPermissionEntity) entity1).getPermission().getMaxLevel());
String json2 = "{\n" +
" \"type\": \"POINTS\",\n" +
" \"points\": 10\n" +
"}";
PointsConditionRequest pointsConditionRequest = (PointsConditionRequest) objectMapper.readValue(json2, BaseConditionRequest.class);
ReadPermissionEntity entity2 = service.getEntity(pointsConditionRequest);
assertEquals(10, ((PointsPermissionEntity) entity2).getPermission().getPoints());
}
总结
MapStruct 是一个功能强大且高效的 Java 对象映射工具,特别适合处理复杂的多态映射场景。通过 @SubclassMapping
注解,开发者可以轻松实现多态对象的转换,而无需编写繁琐的类型检查代码。
在实际项目中,MapStruct 可以显著减少样板代码,提高开发效率,同时保证类型安全和性能。如果你正在寻找一种优雅的方式来处理对象映射,尤其是多态映射,MapStruct 无疑是一个值得尝试的工具。
留言