Spring Boot 項目集成MapStruct 看這一篇就夠了(超詳細步驟)
適用人群:會用 Spring Boot,想在項目里把 DTO ? Entity 的樣板代碼(getter/setter、拷貝字段)換成高性能、可編譯期校驗的 MapStruct。
示例構(gòu)建工具:Maven(附 Gradle 版)
示例 JDK:17(11+ 都可)
示例 Spring Boot:3.2+(2.7+ 也可)
示例 MapStruct:1.5.5.Final(寫作時的穩(wěn)定版)
為什么選 MapStruct
- 編譯期生成:沒有運行時反射,性能高,啟動開銷小。
- 類型安全:編譯期就能發(fā)現(xiàn)字段不匹配等問題,更早暴露錯誤。
- 可維護:映射規(guī)則集中在接口/注解上,團隊更易讀更穩(wěn)定。
- 可擴展:支持表達式、自定義方法、生命周期回調(diào)、依賴注入。
準備工作
- JDK 11+(示例使用 17)
- 一個可運行的 Spring Boot 項目(Web/Service 都可)
- IDE:開啟 Annotation Processing(注解處理),否則不會生成實現(xiàn)類
- IntelliJ IDEA:
Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable
- IntelliJ IDEA:
Maven 集成步驟(推薦)
1)在 pom.xml 添加依賴與注解處理器
<properties>
<java.version>17</java.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<lombok.version>1.18.34</lombok.version>
</properties>
<dependencies>
<!-- MapStruct API -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- Lombok(如項目使用) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
</compilerArgs>
<annotationProcessorPaths>
<!-- MapStruct 注解處理器(生成實現(xiàn)類) -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- Lombok 注解處理器(如項目使用) -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>說明:
-Amapstruct.defaultComponentModel=spring可全局指定生成@Component/@Mapper(componentModel="spring")的 Spring Bean;也可以逐個 Mapper 上寫注解。
Gradle 集成步驟
Kotlin DSL(build.gradle.kts)
plugins {
java
id("org.springframework.boot") version "3.3.4"
id("io.spring.dependency-management") version "1.1.6"
}
java.sourceCompatibility = JavaVersion.VERSION_17
dependencies {
implementation("org.mapstruct:mapstruct:1.5.5.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
}
tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf("-Amapstruct.defaultComponentModel=spring"))
options.encoding = "UTF-8"
}Groovy DSL(build.gradle)
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.6'
}
sourceCompatibility = '17'
dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
compileOnly 'org.projectlombok:lombok:1.18.34'
annotationProcessor 'org.projectlombok:lombok:1.18.34'
}
tasks.withType(JavaCompile) {
options.compilerArgs += ['-Amapstruct.defaultComponentModel=spring']
options.encoding = 'UTF-8'
}創(chuàng)建示例實體與 DTO
domain/User.java
package com.example.demo.domain;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
@Data
public class User {
private Long id;
private String username;
private String email;
private LocalDate birthday;
private Address address;
private List<String> roles;
private Gender gender;
@Data
public static class Address {
private String province;
private String city;
private String detail;
}
public enum Gender {
MALE, FEMALE, OTHER
}
}dto/UserDTO.java
package com.example.demo.dto;
import lombok.Data;
import java.util.List;
@Data
public class UserDTO {
private String id; // 注意:字符串
private String name; // username -> name
private String email;
private String birthday; // 字符串日期,格式:yyyy-MM-dd
private String fullAddress; // 由 address 組合
private List<String> roles;
private String gender; // enum -> 字符串
}編寫 Mapper 接口(Spring 集成)
mapper/UserMapper.java
package com.example.demo.mapper;
import com.example.demo.domain.User;
import com.example.demo.dto.UserDTO;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE // 未映射字段忽略(也可 WARN/ERROR)
)
public interface UserMapper {
// 也可以用 Spring 注入,不需要 INSTANCE
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mappings({
@Mapping(source = "id", target = "id"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd"),
@Mapping(target = "fullAddress", expression =
"java(user.getAddress()==null? null : " +
"user.getAddress().getProvince()+\"-\"+user.getAddress().getCity()+\"-\"+user.getAddress().getDetail())"),
@Mapping(source = "gender", target = "gender") // enum -> String,MapStruct 會調(diào)用 name()
})
UserDTO toDTO(User user);
// 反向映射由 @InheritInverseConfiguration 提供(見下一節(jié))
}常見映射技巧
1)字段名不同(source/target)
@Mapping(source = "username", target = "name")
2)類型不同(日期、枚舉、數(shù)值)
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
3)表達式 / 自定義方法
@Mapping(target = "fullAddress",
expression = "java(combineAddress(user))")
default String combineAddress(User user) {
if (user.getAddress() == null) return null;
var a = user.getAddress();
return String.join("-", a.getProvince(), a.getCity(), a.getDetail());
}4)忽略字段
@Mapping(target = "roles", ignore = true)
5)統(tǒng)一策略(全局)
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) // 強制必須映射
雙向映射與反向配置復用
UserMapper.java(續(xù))
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {
UserDTO toDTO(com.example.demo.domain.User user);
@InheritInverseConfiguration(name = "toDTO")
@Mappings({
// 反向時,字符串 -> Long
@Mapping(target = "id", expression = "java( userDTO.getId()==null? null : Long.valueOf(userDTO.getId()) )"),
// 字符串 -> LocalDate
@Mapping(target = "birthday", dateFormat = "yyyy-MM-dd"),
// 反向需要拆分 fullAddress
@Mapping(target = "address", expression = "java(splitAddress(userDTO.getFullAddress()))")
})
com.example.demo.domain.User toEntity(UserDTO userDTO);
default com.example.demo.domain.User.Address splitAddress(String full) {
if (full == null || full.isBlank()) return null;
String[] parts = full.split("-", 3);
var a = new com.example.demo.domain.User.Address();
a.setProvince(parts.length > 0 ? parts[0] : null);
a.setCity(parts.length > 1 ? parts[1] : null);
a.setDetail(parts.length > 2 ? parts[2] : null);
return a;
}
}增量更新(部分字段更新)
適用于 PATCH 場景:用 DTO 的非空字段更新已有實體。
import org.mapstruct.BeanMapping;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.MappingTarget;
@Mapper(componentModel = "spring")
public interface UserUpdateMapper {
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(UserDTO dto, @MappingTarget com.example.demo.domain.User entity);
}說明:當 DTO 某個字段為
null時,不覆蓋實體已有值。
集合、枚舉、日期格式化
- 集合:
List<A>↔List<B>,若存在A↔B的映射方法,集合會自動映射。 - 枚舉:默認使用
name()/valueOf;可通過@ValueMapping自定義映射。 - 日期:
@Mapping(dateFormat = "yyyy-MM-dd HH:mm:ss");或注冊自定義DateMapper。
自定義日期轉(zhuǎn)換器示例
@Component
public class DateMapper {
public String asString(LocalDate date) { return date == null ? null : date.toString(); }
public LocalDate asLocalDate(String date) { return date == null ? null : LocalDate.parse(date); }
}
@Mapper(componentModel = "spring", uses = DateMapper.class)
public interface UserMapper { /* ... */ }
使用 @AfterMapping/@BeforeMapping 做后/前處理
@Mapper(componentModel = "spring")
public interface TrimMapper {
@Mapping(target = "name", source = "username")
UserDTO toDTO(User user);
@AfterMapping
default void trimAll(@MappingTarget UserDTO dto) {
if (dto.getName() != null) dto.setName(dto.getName().trim());
if (dto.getEmail() != null) dto.getEmail().trim();
}
}@BeforeMapping 也可用于輸入預處理,參數(shù)既可以是源對象也可以是 @Context 上下文對象。
在 Spring Boot 中調(diào)用與測試
service/UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper; // Spring 自動注入
private final UserUpdateMapper updateMapper; // 增量更新示例
public UserDTO getUserDTO(User user) {
return userMapper.toDTO(user);
}
public void patchUser(UserDTO patch, User entity) {
updateMapper.updateFromDto(patch, entity);
}
}UserMapperTest.java
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testToDTO() {
User u = new User();
u.setId(1L);
u.setUsername("alice");
u.setEmail("a@x.com");
u.setBirthday(LocalDate.of(1990,1,1));
User.Address a = new User.Address();
a.setProvince("ZJ"); a.setCity("HZ"); a.setDetail("WestLake");
u.setAddress(a);
u.setGender(User.Gender.FEMALE);
UserDTO dto = userMapper.toDTO(u);
assertEquals("1", dto.getId());
assertEquals("alice", dto.getName());
assertEquals("1990-01-01", dto.getBirthday());
assertEquals("ZJ-HZ-WestLake", dto.getFullAddress());
assertEquals("FEMALE", dto.getGender());
}
}與 Lombok 共存注意事項
- 同時使用時,確保 兩個注解處理器都啟用(
mapstruct-processor與lombok)。 - IDE 必須開啟 Annotation Processing。
- 不再需要歷史上的
lombok-mapstruct-binding(新版本大多無需)。若遇到 builder 兼容性問題,再考慮加:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
<scope>provided</scope>
</dependency>常見報錯與排查
- 沒有生成實現(xiàn)類(
UserMapperImpl不存在)- IDE 未開啟 Annotation Processing。
- 忘記添加
mapstruct-processor到annotationProcessorPaths。 - 清理重建:
mvn -U clean compile/./gradlew clean build。
- unmapped target property(未映射字段告警/錯誤)
- 設(shè)置
unmappedTargetPolicy = ReportingPolicy.IGNORE|WARN|ERROR。 - 明確加
@Mapping(ignore = true)。
- 設(shè)置
- 類型不匹配
- 提供自定義轉(zhuǎn)換方法(
default方法或uses)。 - 用
expression處理復雜邏輯。
- 提供自定義轉(zhuǎn)換方法(
- 循環(huán)依賴映射(A 包含 B,B 又包含 A)
- 拆分為兩個 Mapper,并使用
uses注入彼此,或在其中一個方向上忽略回指字段。
- 拆分為兩個 Mapper,并使用
- Spring 注入失敗(
NoSuchBeanDefinitionException)- 確認
componentModel="spring"或編譯參數(shù)全局開啟; - Mapper 包路徑在
@SpringBootApplication掃描范圍內(nèi)。
- 確認
生產(chǎn)最佳實踐清單
- ? 給每個 Mapper 顯式設(shè)置
componentModel="spring"與unmappedTargetPolicy。 - ? 把「復雜組裝邏輯」封裝為可單測的
default方法或獨立的@Component,通過uses復用。 - ?
@BeanMapping(nullValuePropertyMappingStrategy = IGNORE)做增量更新(避免空值覆蓋)。 - ? 對 DTO 做最小字段暴露,防止過度映射。
- ? 加上 單元測試 覆蓋關(guān)鍵映射。
- ? 對外接口層盡量用 DTO,Domain 保持純凈。
參考命令(Maven)
mvn -U clean compile # 觸發(fā)注解處理器,生成 *MapperImpl mvn test # 運行單測 mvn spring-boot:run # 啟動應用
到此這篇關(guān)于Spring Boot 項目集成MapStruct 看這一篇就夠了(超詳細步驟)的文章就介紹到這了,更多相關(guān)Spring Boot 項目集成MapStruct 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java聊天室之實現(xiàn)接收和發(fā)送Socket
這篇文章主要為大家詳細介紹了Java簡易聊天室之實現(xiàn)接收和發(fā)送Socket功能,文中的示例代碼講解詳細,具有一定的借鑒價值,需要的可以了解一下2022-10-10
spring cloud oauth2 feign 遇到的坑及解決
這篇文章主要介紹了spring cloud oauth2 feign 遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
關(guān)于Jsoup將相對路徑轉(zhuǎn)為絕對路徑的方法
這篇文章主要介紹了關(guān)于Jsoup將相對路徑轉(zhuǎn)為絕對路徑的方法,jsoup 是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文本內(nèi)容,需要的朋友可以參考下2023-04-04
高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程
Protobuf(Protocol?Buffers)是由?Google?開發(fā)的一種輕量級、高效的數(shù)據(jù)交換格式,它被用于結(jié)構(gòu)化數(shù)據(jù)的序列化、反序列化和傳輸,本文主要介紹了它的具體使用方法,需要的可以參考一下2023-05-05
使用@value注解取不到application.xml配置文件中的值問題
這篇文章主要介紹了使用@value注解取不到application.xml配置文件中的值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java實現(xiàn)基礎(chǔ)銀行ATM系統(tǒng)
這篇文章主要為大家詳細介紹了Java實現(xiàn)基礎(chǔ)銀行ATM系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05

