Spring Boot 常见的类转换(对象映射)工具:ModelMapper 与 MapStruct 使用示例对比

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

在 Spring Boot 中,常见的类转换(对象映射)工具如下:


1. ModelMapper

  • 特点:自动根据属性名匹配转换,配置灵活。

  • 依赖

    
    

    xml

    复制

    
    
    <dependency>
        <groupId>org.modelmapper</groupId>
        <artifactId>modelmapper</artifactId>
        <version>3.2.0</version>
    </dependency>

2. MapStruct

  • 特点:编译时生成映射代码,性能高,无反射。

  • 依赖(需结合注解处理器):

    
    

    xml

    复制

    
    
    <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>
        <scope>provided</scope>
    </dependency>

3. Spring BeanUtils(轻量复制)

  • 特点:Spring 自带工具,仅支持同名属性浅拷贝。

  • 示例:

    
    

    java

    下载

    复制

    运行

    
    
    BeanUtils.copyProperties(source, target);

4. Apache BeanUtils(不推荐)

  • 注意:性能较差,一般推荐使用 Spring BeanUtils​ 替代。

5. Dozer(已停止维护)

  • 特点:老项目中较流行,但当前版本已不再活跃。

6. Orika

  • 特点:字节码生成映射,性能接近 MapStruct。

  • 依赖

    
    

    xml

    复制

    
    
    <dependency>
        <groupId>ma.glasnost.orika</groupId>
        <artifactId>orika-core</artifactId>
        <version>1.5.4</version>
    </dependency>

📌 选择建议:

  1. 1.

    追求性能​ → 选 MapStruct

  2. 2.

    快速开发​ → 选 ModelMapper​ 或 Orika

  3. 3.

    简单属性复制​ → 直接用 Spring BeanUtils

根据具体场景(性能要求、复杂度、反射容忍度)选择合适的工具即可。


“快速开发”在这里指的是以最短的学习成本、最少的代码量、最快的速度实现对象映射功能,让开发者能更专注于核心业务逻辑,而不是花费大量时间在映射配置上。

具体来说,选择 ModelMapper​ 或 Orika​ 能带来以下“快速”优势:


🚀 为何它们适合“快速开发”?

  1. 1.

    开箱即用,配置简单

    • ModelMapper:大多数情况下,只需声明一个Bean(new ModelMapper())即可使用,它会自动尝试匹配属性名。

    • Orika:也只需简单的初始化,就能处理大部分常规映射。

  2. 2.

    “约定优于配置”

    • 当源对象(如UserEntity)和目标对象(如UserDTO)的属性名称和类型相同或兼容时,无需任何额外配置或代码,工具会自动完成映射。

    • 示例:userEntity.getName()会自动复制到 userDTO.setName()

  3. 3.

    灵活的注解或API配置

    • 当属性名不一致时(如 sourceCode-> targetCode),可以通过简单的API调用或轻量注解来配置,无需编写完整的转换代码。


⚖️ 与“高性能选择”的对比

为了更直观,这里与 MapStruct​ 进行对比:

特性

快速开发工具 (ModelMapper/Orika)

高性能工具 (MapStruct)

对“快速开发”的影响

原理

基于反射在运行时动态分析并映射属性。

编译时生成Java映射代码,无反射。

反射让使用更简单,但牺牲一些性能。

配置/代码

声明式,配置少,或零配置。

需要定义Mapper接口并声明方法。

省去了定义接口和方法的步骤,上手更快。

启动速度

首次运行时需构建元数据,稍慢。

编译期完成,启动无额外开销。

对开发体验影响微乎其微。

错误发现时机

配置错误或映射失败可能在运行时才发现。

编译时就会检查类型、缺失属性等问题。

这是“快速”的主要代价,可能将问题隐藏到运行时。


📊 总结与建议

场景

推荐工具

原因

原型设计、内部工具、管理后台

ModelMapper / Orika

业务变化快,追求开发效率,性能非关键瓶颈。

复杂的、高性能的核心业务(如交易、网关)

MapStruct

需要最佳性能、编译时安全和长期可维护性。

简单的属性复制(如PO到VO)

Spring BeanUtils

无复杂逻辑,Spring自带,无引入额外依赖的成本。

简单来说:如果你希望现在就写更少的代码、让项目尽快跑起来,并且可以接受微小的性能损失和潜在的运行时映射风险,那么 ModelMapper 或 Orika​ 就是“快速开发”的绝佳选择。当项目发展到对性能或稳定性有更高要求时,再考虑迁移到像MapStruct这样的编译期工具。


ModelMapper 与 MapStruct 使用示例对比

一、项目准备

假设我们有以下实体类和数据对象:


java

下载

复制

运行


// 实体类
public class UserEntity {
    private Long id;
    private String username;
    private String email;
    private LocalDate birthDate;
    private Address address;
    // getters/setters 省略...
}

// 地址实体
public class Address {
    private String city;
    private String street;
    // getters/setters 省略...
}

// DTO
public class UserDTO {
    private Long id;
    private String userName;  // 注意:与实体属性名不同
    private String emailAddress;  // 注意:与实体属性名不同
    private int age;  // 需要计算
    private String city;  // 嵌套属性映射
    // getters/setters 省略...
}

二、ModelMapper 示例

1. 添加依赖


xml

复制


<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.2.0</version>
</dependency>

2. 基本使用


java

下载

复制

运行


import org.modelmapper.ModelMapper;
import org.modelmapper.Converter;
import org.modelmapper.PropertyMap;
import org.modelmapper.TypeMap;

public class ModelMapperExample {
    
    public static void main(String[] args) {
        // 1. 创建 ModelMapper 实例
        ModelMapper modelMapper = new ModelMapper();
        
        // 2. 基本映射(属性名相同时自动映射)
        UserEntity entity = new UserEntity();
        entity.setId(1L);
        entity.setUsername("张三");
        entity.setEmail("zhangsan@example.com");
        entity.setBirthDate(LocalDate.of(1990, 1, 1));
        entity.setAddress(new Address("北京", "长安街"));
        
        // 3. 简单转换
        UserDTO dto = modelMapper.map(entity, UserDTO.class);
        System.out.println(dto.getUserName());  // 自动匹配 username -> userName
    }
}

3. 高级配置(处理复杂映射)


java

下载

复制

运行


public class ModelMapperConfig {
    
    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        
        // 配置全局映射
        modelMapper.getConfiguration()
            .setFieldMatchingEnabled(true)
            .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
            .setSourceNamingConvention(NamingConventions.JAVABEANS_ACCESSOR)
            .setDestinationNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
        
        // 自定义类型映射
        TypeMap<UserEntity, UserDTO> typeMap = modelMapper.createTypeMap(UserEntity.class, UserDTO.class);
        
        // 1. 属性名不匹配时的映射
        typeMap.addMapping(UserEntity::getUsername, UserDTO::setUserName);
        typeMap.addMapping(UserEntity::getEmail, UserDTO::setEmailAddress);
        
        // 2. 嵌套属性映射
        typeMap.addMapping(src -> src.getAddress().getCity(), UserDTO::setCity);
        
        // 3. 自定义转换器(计算年龄)
        Converter<LocalDate, Integer> ageConverter = ctx -> 
            Period.between(ctx.getSource(), LocalDate.now()).getYears();
        
        typeMap.addMappings(mapper -> mapper.using(ageConverter)
            .map(UserEntity::getBirthDate, UserDTO::setAge));
        
        // 4. 跳过某些属性
        typeMap.addMappings(mapper -> mapper.skip(UserDTO::setId));
        
        return modelMapper;
    }
}

4. Spring Boot 中使用


java

下载

复制

运行


@Service
public class UserService {
    
    @Autowired
    private ModelMapper modelMapper;
    
    public UserDTO convertToDTO(UserEntity entity) {
        return modelMapper.map(entity, UserDTO.class);
    }
    
    public UserEntity convertToEntity(UserDTO dto) {
        return modelMapper.map(dto, UserEntity.class);
    }
    
    // 批量转换
    public List<UserDTO> convertToListDTO(List<UserEntity> entities) {
        return entities.stream()
            .map(entity -> modelMapper.map(entity, UserDTO.class))
            .collect(Collectors.toList());
    }
}

三、MapStruct 示例

1. 添加依赖


xml

复制


<properties>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2. 创建 Mapper 接口


java

下载

复制

运行


import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = "spring")  // 与 Spring 集成
public interface UserMapper {
    
    // 获取实例(非Spring方式)
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
    // 1. 基本映射 - 属性名不同时使用 @Mapping
    @Mapping(source = "username", target = "userName")
    @Mapping(source = "email", target = "emailAddress")
    @Mapping(source = "address.city", target = "city")
    @Mapping(target = "age", expression = "java(calculateAge(entity.getBirthDate()))")
    UserDTO toDTO(UserEntity entity);
    
    // 2. 反向映射
    @InheritInverseConfiguration
    @Mapping(target = "id", ignore = true)  // 忽略ID
    UserEntity toEntity(UserDTO dto);
    
    // 3. 更新已有对象
    @Mapping(target = "id", ignore = true)
    void updateEntity(UserDTO dto, @MappingTarget UserEntity entity);
    
    // 4. 批量转换
    List<UserDTO> toDTOList(List<UserEntity> entities);
    
    // 5. 自定义方法(计算年龄)
    default int calculateAge(LocalDate birthDate) {
        if (birthDate == null) {
            return 0;
        }
        return Period.between(birthDate, LocalDate.now()).getYears();
    }
    
    // 6. 多参数映射
    @Mapping(source = "user.id", target = "id")
    @Mapping(source = "user.username", target = "userName")
    @Mapping(source = "address.city", target = "city")
    UserDTO toDTOWithParams(UserEntity user, Address address);
}

3. 更复杂的配置示例


java

下载

复制

运行


@Mapper(componentModel = "spring", 
        uses = {DateMapper.class},  // 使用其他Mapper
        imports = {LocalDate.class})  // 导入类
public interface AdvancedUserMapper {
    
    // 条件映射
    @Mapping(source = "username", target = "userName", 
             conditionExpression = "java(entity.getUsername() != null && !entity.getUsername().isEmpty())")
    @Mapping(source = "birthDate", target = "birthDateString", 
             dateFormat = "yyyy-MM-dd")
    @Mapping(source = "address", target = "fullAddress", 
             qualifiedByName = "addressToString")
    UserDTO toDTO(UserEntity entity);
    
    // 限定符:当有多个相同类型映射时使用
    @Named("addressToString")
    default String addressToString(Address address) {
        return address.getCity() + ", " + address.getStreet();
    }
}

4. 日期转换器


java

下载

复制

运行


@Mapper(componentModel = "spring")
public abstract class DateMapper {
    
    public String asString(LocalDate date) {
        return date != null ? date.toString() : null;
    }
    
    public LocalDate asLocalDate(String date) {
        return date != null ? LocalDate.parse(date) : null;
    }
}

5. Spring Boot 中使用


java

下载

复制

运行


@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;  // 自动注入MapStruct生成的实现类
    
    public UserDTO getUserDTO(UserEntity entity) {
        // 直接调用接口方法
        return userMapper.toDTO(entity);
    }
    
    public List<UserDTO> getAllUsers(List<UserEntity> entities) {
        return userMapper.toDTOList(entities);
    }
    
    public void updateUser(UserDTO dto, UserEntity entity) {
        // 更新现有实体
        userMapper.updateEntity(dto, entity);
    }
}

6. 测试验证


java

下载

复制

运行


@SpringBootTest
class UserMapperTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    void testMapping() {
        UserEntity entity = new UserEntity();
        entity.setId(1L);
        entity.setUsername("张三");
        entity.setEmail("zhangsan@example.com");
        entity.setBirthDate(LocalDate.of(1990, 1, 1));
        entity.setAddress(new Address("北京", "长安街"));
        
        UserDTO dto = userMapper.toDTO(entity);
        
        // 验证映射结果
        assertEquals("张三", dto.getUserName());
        assertEquals("zhangsan@example.com", dto.getEmailAddress());
        assertEquals("北京", dto.getCity());
        assertTrue(dto.getAge() > 0);
    }
}

四、对比总结

对比项

ModelMapper

MapStruct

映射方式

运行时反射

编译时生成代码

性能

中(反射开销)

(无反射)

代码量

少(自动配置)

中(需定义接口)

错误检查

运行时

编译时

与IDE集成

一般

好(生成真实类)

调试便利性

困难(反射)

容易(可查看生成的Java文件)

复杂映射支持

强大(多种转换器)

强大(注解丰富)

学习成本

生成的文件位置(MapStruct):

MapStruct 在编译时会在 target/generated-sources/annotations/目录下生成实现类:


复制


target/generated-sources/annotations/
└── com/example/mapper/
    └── UserMapperImpl.java  ← MapStruct 自动生成的实现

五、选择建议

  1. 1.

    选择 ModelMapper 当

    • 需要快速原型开发

    • 项目简单,映射关系不复杂

    • 可以接受运行时的小性能损失

    • 不想写太多映射代码

  2. 2.

    选择 MapStruct 当

    • 性能是关键因素

    • 需要编译时类型安全检查

    • 项目较大,需要长期维护

    • 需要更好的调试体验

    • 愿意为类型安全写一些接口代码

两种工具都可以在Spring Boot中通过@Bean@Mapper(componentModel = "spring")轻松集成,选择哪种主要取决于你的具体需求和对性能/安全性的权衡。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值