讲师中心 微信公众号
AI工具推荐 视频效率加速

MapStruct更新现有目标对象:常见陷阱与最佳实践

星晨酱_3266

星晨酱_3266

发布时间:2025-12-03 08:51:06

|

797人浏览过

|

来源于php中文网

原创

MapStruct更新现有目标对象:常见陷阱与最佳实践

本文深入探讨了mapstruct在更新现有目标对象时可能遇到的常见问题及其解决方案。核心内容包括:强调进行干净编译的重要性(如`mvn clean compile`),以确保mapstruct生成的代码被正确识别和加载;以及明确指出目标对象必须具备可变属性(即非`final`字段和对应的`setter`方法),才能使mapstruct的更新操作生效。通过示例代码,本文将引导读者正确配置和使用mapstruct进行对象更新。

MapStruct对象更新机制概述

MapStruct是一个强大的代码生成器,它极大地简化了Java Bean之间的数据映射。除了常见的创建新目标对象进行映射外,MapStruct还支持将源对象的数据更新到现有目标对象实例中。这通过在映射器接口中使用@MappingTarget注解实现,允许开发者复用已存在的对象,避免不必要的对象创建,尤其适用于数据库实体更新等场景。

然而,在实际应用中,开发者可能会遇到@MappingTarget注解的更新方法似乎无效的问题。这通常是由两个主要原因造成的:编译环境问题和目标对象的可变性限制。

常见问题一:编译环境导致更新失效

许多开发者在使用MapStruct的更新功能时,可能会发现即使代码逻辑看起来正确,更新操作也未能按预期执行。这在集成开发环境(IDE)中运行测试时尤为常见。

问题现象示例:

假设我们有如下的Source和Destination类,以及一个MapStruct映射器。

// Source.java (源对象,可以保持不可变性)
public class Source {
    private final String id;
    private final String other;

    public Source(String id, String other) {
        this.id = id;
        this.other = other;
    }

    public String getId() {
        return id;
    }

    public String getOther() {
        return other;
    }
}

// Destination.java (目标对象,此处为演示问题,字段被声明为final)
public class Destination {
    private final String id;
    private final String other;

    public Destination(String id, String other) {
        this.id = id;
        this.other = other;
    }

    public String getId() {
        return id;
    }

    public String getOther() {
        return other;
    }
}

// MyMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

@Mapper
public interface MyMapper {
    MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);

    Destination createDestinationFromSource(Source source);
    void updateDestinationFromSource(Source source, @MappingTarget Destination destination);
}

// Test类
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MapStructUpdateTest {
    @Test
    void testMapStructUpdateFailure() {
        // 创建操作通常能成功
        var source = new Source("sourceId", "sourceOther");
        var destination1 = MyMapper.INSTANCE.createDestinationFromSource(source);
        Assertions.assertEquals(source.getId(), destination1.getId());

        // 尝试更新
        var destination2 = new Destination("destinationId", "destinationOther");
        MyMapper.INSTANCE.updateDestinationFromSource(source, destination2);
        // 期望:destination2.getId() 变为 "sourceId"
        // 实际:destination2.getId() 仍为 "destinationId"
        Assertions.assertEquals(source.getId(), destination2.getId()); // 此处断言失败
    }
}

在上述代码中,尽管createDestinationFromSource方法能够成功创建并映射数据,但updateDestinationFromSource方法却未能更新destination2对象的字段。一个常见的原因是,IDE可能没有完全重新编译MapStruct生成的映射器实现类。

解决方案:执行干净编译

MapStruct是一个注解处理器,它在编译时生成映射器的具体实现类。如果这些生成的类没有被正确地重新编译或加载,那么运行时将使用旧的或不完整的实现。

解决此问题的最直接方法是在项目根目录执行一次干净编译:

mvn clean compile

或者对于Gradle项目:

deep-java-review
deep-java-review

Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...

下载
./gradlew clean build

这会清除所有旧的编译产物,并强制重新编译整个项目,包括MapStruct生成的代码。在执行干净编译后,重新运行测试,问题通常会得到解决(前提是目标对象满足可变性要求,详见下文)。

常见问题二:目标对象的可变性限制

即使解决了编译问题,如果目标对象的字段被声明为final,或者没有提供公共的setter方法,MapStruct的更新操作仍然会失败。MapStruct需要通过setter方法来修改目标对象的属性值。

问题分析:

  • 创建操作 (createDestinationFromSource): MapStruct在创建新对象时,可以通过构造函数或直接字段赋值(如果字段不是final且可见)来初始化属性。因此,即使目标对象没有setter,创建操作也可能成功。
  • 更新操作 (updateDestinationFromSource): 当更新一个现有对象实例时,MapStruct必须能够修改该实例的属性。这意味着目标对象的字段不能是final的,并且必须提供公共的setter方法供MapStruct调用。

解决方案:确保目标对象可变

为了使MapStruct的更新方法正常工作,目标对象(即@MappingTarget注解的参数类型)必须满足以下条件:

  1. 字段非final: 目标对象中需要被更新的字段不能使用final修饰符。
  2. 提供公共setter方法: 目标对象必须为需要更新的每个字段提供一个公共的setter方法(例如,setId(String id))。

修正后的代码示例:

我们将Destination类修改为可变的,并添加setter方法。

// Destination.java (修正后:字段非final,并提供setter方法)
public class Destination {
    private String id; // 不再是final
    private String other; // 不再是final

    // 默认构造函数或带参构造函数都可以,MapStruct在更新时不会调用构造函数
    public Destination() {}

    public Destination(String id, String other) {
        this.id = id;
        this.other = other;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) { // 添加setter
        this.id = id;
    }

    public String getOther() {
        return other;
    }

    public void setOther(String other) { // 添加setter
        this.other = other;
    }
}

// MyMapper.java (保持不变)
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

@Mapper
public interface MyMapper {
    MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);

    Destination createDestinationFromSource(Source source);
    void updateDestinationFromSource(Source source, @MappingTarget Destination destination);
}

// Test类 (修正后,预期断言成功)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MapStructUpdateTest {
    @Test
    void testMapStructUpdateSuccess() {
        // 创建
        var source = new Source("sourceId", "sourceOther");
        var destination1 = MyMapper.INSTANCE.createDestinationFromSource(source);
        Assertions.assertEquals(source.getId(), destination1.getId());
        Assertions.assertEquals(source.getOther(), destination1.getOther());

        // 更新:现在 destination2 应该能被成功更新
        var destination2 = new Destination("initialDestinationId", "initialDestinationOther");
        MyMapper.INSTANCE.updateDestinationFromSource(source, destination2);
        Assertions.assertEquals(source.getId(), destination2.getId()); // 断言成功
        Assertions.assertEquals(source.getOther(), destination2.getOther()); // 断言成功
    }
}

在Destination类添加了setter方法并确保字段非final后,结合干净编译,updateDestinationFromSource方法将能够正确地更新目标对象destination2的属性。

总结与最佳实践

为了确保MapStruct的更新现有目标对象功能正常工作,请牢记以下关键点:

  1. 始终进行干净编译: 当MapStruct映射器或其依赖的Bean结构发生变化时,尤其是在IDE中遇到不一致行为时,执行mvn clean compile(或./gradlew clean build)是一个有效的首要排查步骤。这能确保MapStruct生成的代码是最新的。
  2. 目标对象必须可变: 对于需要通过MapStruct更新的现有目标对象,其字段不能是final的,并且必须提供相应的公共setter方法。这是MapStruct实现更新机制的基础。
  3. 依赖版本: 确保MapStruct处理器和核心库的版本兼容且稳定。本文示例中使用的版本为1.5.2.Final,并结合OpenJDK 17环境。

遵循这些实践,将能有效避免MapStruct更新现有目标对象时遇到的常见问题,并充分利用其强大的映射能力。

热门AI工具

更多
SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

5379

2023.08.02

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3068

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

4369

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3689

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

386

2026.01.19

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

2345

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

3681

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

2531

2023.08.31

PixTV AI视频生成与无限画布创作
PixTV AI视频生成与无限画布创作

PixTV专题整理AI视频与视觉内容创作相关功能使用教程,涵盖AI生图、视频生成、无限画布、多模型创作、素材管理、声音音乐及视频剪辑等功能,帮助用户快速掌握PixTV从创意到成片的完整制作方法。

0

2026.09.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn