当前位置:首页 > 文章列表 > 文章 > java教程 > 使用 MapStruct 映射继承层次结构

使用 MapStruct 映射继承层次结构

来源:dev.to 2024-10-15 08:00:43 0浏览 收藏

来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习文章相关编程知识。下面本篇文章就来带大家聊聊《使用 MapStruct 映射继承层次结构》,介绍一下,希望对大家的知识积累有所帮助,助力实战开发!

使用 MapStruct 映射继承层次结构

简介

mapstruct 提供了一组丰富的功能来映射 java 类型。技术文档广泛描述了 mapstruct 提供的类和注释以及如何使用它们。网络上的几篇社区撰写的文章描述了更复杂的用例。为了补充可用文章库,本文将重点关注映射继承层次结构,并提供一种可能的解决方案,该解决方案具有简单性和可重用性。我假设读者有 mapstruct 的基本知识。如果您对正在运行的示例感兴趣,请随时查看此存储库并尝试一下。

例子

为了以简单的方式演示 mapstruct 的功能,我们将使用一个非常小且无用的域模型,对于该模型,mapstruct 的使用似乎过于复杂,但它允许代码片段在整篇文章中保持简单。 mapstruct 的真正好处变得显而易见,尤其是对于较大的模型。

// source classes
public class sourceproject {
  private string name;
  private localdate duedate;
  // getters + setters omitted throughout the code
}

// target classes
public class targetproject {
  private projectinformation projectinformation;
}

public class projectinformation {
  private string projectname;
  private localdate enddate;
}

如您所见,源实体和目标实体表达相同的信息,但结构略有不同。映射器可以这样定义...

@mapper
public interface projectmapper {
  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  targetproject mapproject(sourceproject source);
}

...mapstruct 将生成如下所示的代码:

public class projectmapperimpl implements projectmapper {

    @override
    public targetproject mapproject(sourceproject source) {
        if ( source == null ) {
            return null;
        }

        targetproject targetproject = new targetproject();

        targetproject.setprojectinformation( sourceprojecttoprojectinformation( source ) );

        return targetproject;
    }

    protected projectinformation sourceprojecttoprojectinformation(sourceproject sourceproject) {
        if ( sourceproject == null ) {
            return null;
        }

        projectinformation projectinformation = new projectinformation();

        projectinformation.setprojectname( sourceproject.getname() );
        projectinformation.setenddate( sourceproject.getduedate() );

        return projectinformation;
    }
}

现在让我们介绍一些使用继承的新实体:

// source classes
@data
public class sourcescrumproject extends sourceproject {
  private integer velocity;
}

// target classes
@data
public class targetscrumproject extends targetproject {
  private velocity velocity;
}

@data
public class velocity {
  private integer value;
}

如果我们想通用地使用父映射器来映射父实体和子实体,我们可以使用@subclassmapping注释,它通过instanceof检查生成对可能的子类映射的调度。

@mapper
public interface projectmapper {
  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  @subclassmapping(source = sourcescrumproject.class, target = targetscrumproject.class)
  targetproject mapproject(sourceproject source);

  @mapping(target = "velocity.value", source = "velocity")
  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  targetscrumproject mapscrumproject(sourcescrumproject source);
}

这会生成以下代码。

public class projectmapperimpl implements projectmapper {

    @override
    public targetproject mapproject(sourceproject source) {
        if ( source == null ) {
            return null;
        }

        if (source instanceof sourcescrumproject) {
            return mapscrumproject( (sourcescrumproject) source );
        }
        else {
            targetproject targetproject = new targetproject();

            targetproject.setprojectinformation( sourceprojecttoprojectinformation( source ) );

            return targetproject;
        }
    }

    @override
    public targetscrumproject mapscrumproject(sourcescrumproject source) {
        if ( source == null ) {
            return null;
        }

        targetscrumproject targetscrumproject = new targetscrumproject();

        targetscrumproject.setvelocity( sourcescrumprojecttovelocity( source ) );
        targetscrumproject.setprojectinformation( sourcescrumprojecttoprojectinformation( source ) );

        return targetscrumproject;
    }

    protected projectinformation sourceprojecttoprojectinformation(sourceproject sourceproject) {
        if ( sourceproject == null ) {
            return null;
        }

        projectinformation projectinformation = new projectinformation();

        projectinformation.setprojectname( sourceproject.getname() );
        projectinformation.setenddate( sourceproject.getduedate() );

        return projectinformation;
    }

    protected velocity sourcescrumprojecttovelocity(sourcescrumproject sourcescrumproject) {
        if ( sourcescrumproject == null ) {
            return null;
        }

        velocity velocity = new velocity();

        velocity.setvalue( sourcescrumproject.getvelocity() );

        return velocity;
    }

    protected projectinformation sourcescrumprojecttoprojectinformation(sourcescrumproject sourcescrumproject) {
        if ( sourcescrumproject == null ) {
            return null;
        }

        projectinformation projectinformation = new projectinformation();

        projectinformation.setprojectname( sourcescrumproject.getname() );
        projectinformation.setenddate( sourcescrumproject.getduedate() );

        return projectinformation;
    }
}

我们已经可以在这里看到一些问题:

  1. 我们正在从父映射复制 @mapping 注释。
  2. 部分生成的代码是重复的(sourceprojecttoprojectinformation 和 sourcescrumprojecttoprojectinformation)。
  3. 接口变得更宽,因为它包含父实体和子实体的映射方法。

只有这两个字段,这看起来并不可怕,但想象一下,如果我们有更多包含更多字段的子类,生成的代码会是什么样子。效果会更大。

让我们尝试解决问题 #1。 mapstruct 提供了注释 @inheritconfiguration,它允许我们重用同一类或所使用的映射配置类中的映射配置:

@mapper
public interface projectmapper {
  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  @subclassmapping(source = sourcescrumproject.class, target = targetscrumproject.class)
  targetproject mapproject(sourceproject source);

  @mapping(target = "velocity.value", source = "velocity")
  @inheritconfiguration(name = "mapproject")
  targetscrumproject mapscrumproject(sourcescrumproject source);
}

这至少为我们省去了很多重复配置。剧透:我们以后不想再使用它了。但让我们首先解决问题#2 和#3。

由于我们可能拥有包含大量重复代码的潜在广泛接口,因此使用、理解和调试生成的代码可能会变得更加困难。如果我们为每个子类都有一个独立的映射器,并且只分派到子映射器或执行映射,但不能同时执行两者,那么会更容易。因此,让我们将 scrum 项目的映射移至单独的界面。

@mapper(uses = scrumprojectmapper.class)
public interface projectmapper {
  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  @subclassmapping(source = sourcescrumproject.class, target = targetscrumproject.class)
  targetproject mapproject(sourceproject source);
}

@mapper
public interface scrumprojectmapper {
  @mapping(target = "velocity.value", source = "velocity")
  @inheritconfiguration(name = "mapproject") // not working
  targetscrumproject mapscrumproject(sourcescrumproject source);
}

我们告诉projectmapper通过uses子句将scrumprojects的映射分派到scrumprojectmapper。这里的问题是,mapproject 方法中的配置对 scrumprojectmapper 不再可见。我们当然可以让它扩展projectmapper,但是这样我们又会遇到宽接口和重复代码的问题,因为所有方法都合并到scrumprojectmapper中。我们可以使用 @mapperconfig 注释将 projectmapper 设为一个配置,并在 scrumprojectmapper 中引用它,但由于它还在 uses 子句中使用 scrumprojectmapper 来启用调度,因此 mapstruct 会抱怨循环依赖。此外,如果我们有一个高度为 > 1 的继承层次结构,我们很快就会注意到 mapstruct 不会将配置在映射器层次结构中向下传递超过一级,从而使级别 0 的配置在级别 2 及更高级别上不可用。

幸运的是,有一个解决方案。 @mapping注解可以应用于其他注解。通过声明一个注释 projectmappings(它基本上包装了项目的所有映射信息),我们可以在任何我们想要的地方重用它。让我们看看这会是什么样子。

@mapper(uses = scrumprojectmapper.class)
public interface projectmapper {
  @mappings
  @subclassmapping(source = sourcescrumproject.class, target = targetscrumproject.class)
  targetproject mapproject(sourceproject source);

  @mapping(target = "projectinformation.projectname", source = "name")
  @mapping(target = "projectinformation.enddate", source = "duedate")
  @interface mappings {
  }
}

@mapper
public interface scrumprojectmapper {
  @mapping(target = "velocity.value", source = "velocity")
  @projectmapper.mappings
  targetscrumproject mapscrumproject(sourcescrumproject source);
}

想象一下,我们有比 scrumproject 更多的子类。通过简单地将映射信息捆绑在共享注释中,我们可以集中信息并避免重复带来的所有陷阱。这也适用于更深的继承层次结构。我只需要使用父映射器的 @mappings-annotation 来注释我的映射方法,该父映射器使用其父映射器的注释,依此类推。

我们现在可以在生成的代码中看到映射器仅针对它们构建的类进行调度或执行映射:

public class ProjectMapperImpl implements ProjectMapper {

    private final ScrumProjectMapper scrumProjectMapper = Mappers.getMapper( ScrumProjectMapper.class );

    @Override
    public TargetProject mapProject(SourceProject source) {
        if ( source == null ) {
            return null;
        }

        if (source instanceof SourceScrumProject) {
            return scrumProjectMapper.mapScrumProject( (SourceScrumProject) source );
        }
        else {
            TargetProject targetProject = new TargetProject();

            targetProject.setProjectInformation( sourceProjectToProjectInformation( source ) );

            return targetProject;
        }
    }

    protected ProjectInformation sourceProjectToProjectInformation(SourceProject sourceProject) {
        if ( sourceProject == null ) {
            return null;
        }

        ProjectInformation projectInformation = new ProjectInformation();

        projectInformation.setProjectName( sourceProject.getName() );
        projectInformation.setEndDate( sourceProject.getDueDate() );

        return projectInformation;
    }
}

public class ScrumProjectMapperImpl implements ScrumProjectMapper {

    @Override
    public TargetScrumProject mapScrumProject(SourceScrumProject source) {
        if ( source == null ) {
            return null;
        }

        TargetScrumProject targetScrumProject = new TargetScrumProject();

        targetScrumProject.setVelocity( sourceScrumProjectToVelocity( source ) );
        targetScrumProject.setProjectInformation( sourceScrumProjectToProjectInformation( source ) );

        return targetScrumProject;
    }

    protected Velocity sourceScrumProjectToVelocity(SourceScrumProject sourceScrumProject) {
        if ( sourceScrumProject == null ) {
            return null;
        }

        Velocity velocity = new Velocity();

        velocity.setValue( sourceScrumProject.getVelocity() );

        return velocity;
    }

    protected ProjectInformation sourceScrumProjectToProjectInformation(SourceScrumProject sourceScrumProject) {
        if ( sourceScrumProject == null ) {
            return null;
        }

        ProjectInformation projectInformation = new ProjectInformation();

        projectInformation.setProjectName( sourceScrumProject.getName() );
        projectInformation.setEndDate( sourceScrumProject.getDueDate() );

        return projectInformation;
    }
}

我想说这使得理解和调试单个映射器变得更容易。由于我们已经介绍了很多内容,现在让我们总结一下。仍然有一些边缘情况可能导致进一步的问题,但这些将在下一部分中介绍。

包起来

使用 mapstruct 编写用于继承层次结构的映射器应该是一项常见任务并且很容易实现,但您很快就会陷入 mapstruct 的一些怪癖中。将整个层次结构映射到一个类中会导致大型类实现难以阅读和调试的宽接口。当将映射器拆分为每个类一个时,我们希望重用父映射器的映射信息以避免重复映射信息。扩展父映射器以使其映射配置可见以供在 @inheritconfiguration 中使用是不可取的,因为我们将再次遇到带有大量重复代码的宽接口的问题。由于循环依赖,使用父映射器作为配置也是不可能的。我们可以看到,创建一个自定义注释来捆绑用于子映射器的映射信息可以解决这个问题。通过另外使用 subclassmapping,父映射器提供有关如何映射它所知道的实体的捆绑信息,仅包含该类的映射,并沿着映射器层次结构调度任何其他子实体的映射。

理论要掌握,实操不能落!以上关于《使用 MapStruct 映射继承层次结构》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

版本声明
本文转载于:dev.to 如有侵犯,请联系study_golang@163.com删除
win10怎么设置允许访问此设备上的相机win10怎么设置允许访问此设备上的相机
上一篇
win10怎么设置允许访问此设备上的相机
PHP 函数在用户体验优化中的妙招和窍门
下一篇
PHP 函数在用户体验优化中的妙招和窍门
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    509次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • AI边界平台:智能对话、写作、画图,一站式解决方案
    边界AI平台
    探索AI边界平台,领先的智能AI对话、写作与画图生成工具。高效便捷,满足多样化需求。立即体验!
    29次使用
  • 讯飞AI大学堂免费AI认证证书:大模型工程师认证,提升您的职场竞争力
    免费AI认证证书
    科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
    54次使用
  • 茅茅虫AIGC检测:精准识别AI生成内容,保障学术诚信
    茅茅虫AIGC检测
    茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
    177次使用
  • 赛林匹克平台:科技赛事聚合,赋能AI、算力、量子计算创新
    赛林匹克平台(Challympics)
    探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
    255次使用
  • SEO  笔格AIPPT:AI智能PPT制作,免费生成,高效演示
    笔格AIPPT
    SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
    196次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码