摘要
本文介绍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、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。