使用MapStruct映射对象属性

摘要

本文介绍Mapstruct的作用、特点、常用注解、注意事项及坑点解决方案,并用代码示例如何集成到SpringBoot项目中使用。

认识MapStruct

MapStruct适用于所有需要对象映射的场景,如接口的DTO转换、数据库实体与业务对象转换。

官网: mapstruct.org/

特点:

  • 对比BeanUtils的更优选择
  • 支持String转Date等自定义转换
  • 编译时生成代码、高性能、类型安全
  • 约定大于配置,映射时默认按字段名匹配
  • 支持复杂映射,适合对象、列表、分页转换

常用注解:

  • @Mapper定义接口
  • @Mappings包含多个@Mappering
  • @Mappering定义属性映射
  • 使用@Mappings注解,字段名相同时可不显式指定;defaultValue表示源对象字段不存在时,指定目标字段默认值;ignore表示目标对象字段不需要从源对象中映射过来时,对其忽略;defaultExpression表示源对象中没有对应的属性或源属性值为null时,目标属性将使用这个默认值

注意:

  • 应该预编译(clean-install)生成实现类
  • MapStruct生成的实现类位于target目录下
  • 确保IDEA安装Lombok插件并开启了注解处理器
  • 集成lombok使用时应注意注解冲突(正文有解决方案)

代码示例

1)导入依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-jdk8</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <!-- lombok处理器 防止lombok和mapstruct冲突 致使lombok注解失效 -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.30</version>
                    </path>
                    <!-- MapStruct处理器 lombok和mapstruct冲突 致使lombok注解失效 -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2)原始对象

kotlin 复制代码
package org.coffeebeans.entity;

import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;

/**
 * <li>ClassName: User </li>
 * <li>Author: OakWang </li>
 */
@Data
@Builder
public class User {
    private Integer id;
    private String username;
    private Integer age;
    private String address;
    private BigDecimal money;
}

3)目标对象

arduino 复制代码
package org.coffeebeans.entity;

import lombok.Data;
import java.math.BigDecimal;

/**
 * <li>ClassName: UserDto </li>
 * <li>Author: OakWang </li>
 */
@Data
public class UserDto {
    private Integer id;
    private String name;
    private Integer age;
    private String familyAddress;
    private BigDecimal money;

}

4)定义属性映射接口

ini 复制代码
package org.coffeebeans.transform;

import org.coffeebeans.entity.User;
import org.coffeebeans.entity.UserDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;

/**
 * <li>ClassName: UserTransform </li>
 * <li>Author: OakWang </li>
 */
@Mapper(componentModel = "spring") //生成Spring Bean
public interface UserTransform {

    UserTransform INSTANCE = Mappers.getMapper(UserTransform.class);

    /*
    方式1:使用 @Mappings 注解
       字段名相同时可不显式指定
       defaultValue 源对象字段不存在,指定默认值
       ignore 目标对象中存在一些字段不需要从源对象中映射过来时,可以使用ignore属性来忽略这些字段。
       defaultExpression 源对象中没有对应的属性或源属性值为null时,目标属性将使用这个默认值
     */
    //@Mapping(source = "id", target = "id") //同名的可写可不写
    @Mapping(source = "username", target = "name")
    @Mapping(source = "age", target = "age", defaultValue = "100")
    @Mapping(source = "address", target = "familyAddress", ignore = true)
    @Mapping(source = "money", target = "money", defaultExpression = "java(new java.math.BigDecimal("3000"))")
    UserDto toUserDTO(User user);

    //方式2:使用 @Mappings({})
//  @Mappings({
//     @Mapping(source = "username", target = "name"),
//     @Mapping(source = "age", target = "age", defaultValue = "0"),
//     @Mapping(source = "address", target = "familyAddress", ignore = true),
//     @Mapping(source = "money", target = "money", defaultExpression = "java(new BigDecimal("0")")
//  })
//  UserDto toUserDTO(User user);

    List<UserDto> toUserDtoList(List<User> users);

    //todo 一样的用法
    //Page<UserDto> toUserDtoPage(Page<User> users);

}

5)编译生成类clean-install

kotlin 复制代码
package org.coffeebeans.transform;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.coffeebeans.entity.User;
import org.coffeebeans.entity.UserDto;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2025-07-01T21:06:20+0800",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_401 (Oracle Corporation)"
)
@Component
public class UserTransformImpl implements UserTransform {

    @Override
    public UserDto toUserDTO(User user) {
        if ( user == null ) {
            return null;
        }

        UserDto userDto = new UserDto();

        userDto.setName( user.getUsername() );
        if ( user.getAge() != null ) {
            userDto.setAge( user.getAge() );
        }
        else {
            userDto.setAge( 100 );
        }
        if ( user.getMoney() != null ) {
            userDto.setMoney( user.getMoney() );
        }
        else {
            userDto.setMoney( new java.math.BigDecimal("3000") );
        }
        userDto.setId( user.getId() );

        return userDto;
    }

    @Override
    public List<UserDto> toUserDtoList(List<User> users) {
        if ( users == null ) {
            return null;
        }

        List<UserDto> list = new ArrayList<UserDto>( users.size() );
        for ( User user : users ) {
            list.add( toUserDTO( user ) );
        }

        return list;
    }
}

6)测试映射结果

csharp 复制代码
package org.coffeebeans;

import org.coffeebeans.entity.User;
import org.coffeebeans.entity.UserDto;
import org.coffeebeans.transform.UserTransform;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * <li>ClassName: MapstructTest </li>
 * <li>Author: OakWang </li>
 */
@SpringBootTest
public class MapstructTest {

    @Test
    public void test() {
       // 创建一个User对象
       List<User> list = new ArrayList<>();
       User user = User.builder().id(1).age(null).username("姓名1").address("地址1").money(null).build();
       list.add(user);
       // 使用UserTransform将User对象转换为UserDto对象
       UserDto userDto = UserTransform.INSTANCE.toUserDTO(user);
       List<UserDto> userDtos = UserTransform.INSTANCE.toUserDtoList(list);
       // 验证转换结果
       System.out.println("id属性是否相等:" + Objects.equals(user.getId(), userDto.getId()));
       System.out.println("name属性是否相等:" + Objects.equals(user.getUsername(), userDto.getName()));
       System.out.println("age属性是否相等:" + Objects.equals(user.getAge(), userDto.getAge()));
       System.out.println("address属性是否相等:" + Objects.equals(user.getAddress(), userDto.getFamilyAddress()));
       System.out.println("money属性是否相等:" + Objects.equals(user.getMoney(), userDto.getMoney()));
       System.out.println("User对象:" + user);
       System.out.println("UserDto对象:" + userDto);
       System.out.println("User集合:" + list);
       System.out.println("UserDto集合:" + userDtos);
    }
}

报错处理!!

mapstruct和lombok依赖冲突导致lombok注解全失效

xml 复制代码
<!-- 显式配置maven-compiler-plugin的<annotationProcessorPaths>,强制指定Lombok的注解处理器路径,确保其优先被调用。-->

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <!-- Lombok 处理器 -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.30</version>
                    </path>
                    <!-- MapStruct 处理器 -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

总结

以上我们了解了Mapstruct的作用、特点、常用注解、注意事项及坑点解决方案,需特别注意预编译生成target-Impl实现类,并在集成lombok使用时处理好注解冲突。

关注公众号:咖啡Beans

在这里,我们专注于软件技术的交流与成长,分享开发心得与笔记,涵盖编程、AI、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。

相关推荐
杨杨杨大侠2 小时前
手把手教你写 httpclient 框架(二)- 核心注解系统设计与实现
java·开源·github
vker3 小时前
第 2 天:工厂方法模式(Factory Method Pattern)—— 创建型模式
java·后端·设计模式
准时睡觉3 小时前
SpringSecurity的使用
java·后端
对不起初见3 小时前
如何在后端优雅地生成并传递动态错误提示?
java·spring boot
tingyu3 小时前
JAXB 版本冲突踩坑记:SPI 项目中的 XML 处理方案升级
java
NightDW3 小时前
amqp-client源码解析1:数据格式
java·后端·rabbitmq
程序员清风3 小时前
美团二面:KAFKA能保证顺序读顺序写吗?
java·后端·面试
风象南4 小时前
SpringBoot的零配置API文档工具的设计与实现
spring boot·后端
ytadpole18 小时前
揭秘xxl-job:从高可用到调度一致性
java·后端