Java MapStruct使用配置實戰(zhàn)指南
一、MapStruct 簡介
MapStruct 是一個編譯期注解處理器,它會在編譯時自動生成類型安全、無反射的 Java Bean 映射代碼。
核心優(yōu)勢:
- 高性能:無運行時反射,生成代碼直接調(diào)用 getter/setter。
- 類型安全:編譯期檢查,映射出錯時編譯直接報錯。
- 易于集成:只需引入依賴,配合注解即可。
二、MapStruct 工作原理
- 編寫 Mapper 接口,使用
@Mapper注解。 - 編譯時,MapStruct 的注解處理器根據(jù)接口定義自動生成實現(xiàn)類(通常在
target/generated-sources/annotations下)。 - 運行時,調(diào)用自動生成的實現(xiàn)類進行對象轉(zhuǎn)換。
三、快速入門示例
1. 添加依賴
Maven:
<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>2. 定義源對象和目標(biāo)對象
// Entity
public class UserEntity {
private Long id;
private String name;
private String email;
// getter/setter
}
// DTO
public class UserDTO {
private Long id;
private String name;
// getter/setter
}3. 編寫 Mapper 接口
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO entityToDto(UserEntity entity);
UserEntity dtoToEntity(UserDTO dto);
}4. 使用 Mapper
UserEntity entity = new UserEntity();
entity.setId(1L);
entity.setName("Tom");
entity.setEmail("tom@example.com");
UserDTO dto = UserMapper.INSTANCE.entityToDto(entity);
// dto.id = 1, dto.name = "Tom"四、進階用法
1. 字段名不一致
public class UserEntity {
private String userName;
//...
}
public class UserDTO {
private String name;
//...
}
@Mapper
public interface UserMapper {
@Mapping(source = "userName", target = "name")
UserDTO entityToDto(UserEntity entity);
}2. 嵌套對象映射
public class AddressEntity { ... }
public class AddressDTO { ... }
public class UserEntity {
private AddressEntity address;
}
public class UserDTO {
private AddressDTO address;
}
@Mapper
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}MapStruct 會自動遞歸調(diào)用同名映射方法。
3. 集合映射
List<UserDTO> entityListToDtoList(List<UserEntity> entities);
4. 自定義轉(zhuǎn)換
@Mapper
public interface UserMapper {
@Mapping(target = "createTime", expression = "java(new java.util.Date())")
UserDTO entityToDto(UserEntity entity);
}五、常見問題
- 生成代碼找不到?
- 檢查 IDE 的 annotation processing 是否開啟,代碼在
target/generated-sources/annotations下。
- 檢查 IDE 的 annotation processing 是否開啟,代碼在
- 自定義方法怎么用?
- 可以在 Mapper 里定義 default 方法,或用
@Named結(jié)合@Mapping的qualifiedByName。
- 可以在 Mapper 里定義 default 方法,或用
- 與 Spring 集成?
- 用
@Mapper(componentModel = "spring"),Mapper 會注冊為 Spring Bean,可自動注入。
- 用
六、MapStruct 與 BeanUtils/Dozer/ModelMapper 比較
| 框架 | 性能 | 類型安全 | 反射 | 編譯期檢查 | 代碼生成 |
|---|---|---|---|---|---|
| MapStruct | 很高 | 是 | 否 | 是 | 是 |
| BeanUtils | 一般 | 否 | 是 | 否 | 否 |
| Dozer | 較低 | 否 | 是 | 否 | 否 |
| ModelMapper | 較低 | 否 | 是 | 否 | 否 |
七. Spring 集成與依賴注入
讓 Mapper 變成 Spring Bean,只需加 @Mapper(componentModel = "spring"):
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}然后就可以在 Spring 中自動注入:
@Autowired private UserMapper userMapper;
如果 Mapper 之間有依賴,可以直接注入其他 Mapper:
@Mapper(componentModel = "spring", uses = {AddressMapper.class})
public interface UserMapper { ... }八. 多級嵌套對象映射
比如 DTO 和 Entity 里都包含 Address 對象:
public class UserEntity {
private AddressEntity address;
}
public class UserDTO {
private AddressDTO address;
}
@Mapper
public interface AddressMapper {
AddressDTO entityToDto(AddressEntity entity);
}
@Mapper(uses = {AddressMapper.class})
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}MapStruct 會自動調(diào)用 AddressMapper 的方法。
九. 枚舉類型映射
如果枚舉名一致,MapStruct 會自動映射;如果不一致,可以手動指定:
public enum StatusEnum { ENABLED, DISABLED }
public enum StatusDTO { ON, OFF }
@Mapper
public interface StatusMapper {
@Mapping(source = "ENABLED", target = "ON")
@Mapping(source = "DISABLED", target = "OFF")
StatusDTO toDto(StatusEnum status);
}十. 自定義類型轉(zhuǎn)換(QualifiedByName、@Named)
比如把 String 轉(zhuǎn)成 Date:
@Mapper
public interface UserMapper {
@Mapping(source = "dateStr", target = "date", qualifiedByName = "stringToDate")
UserDTO entityToDto(UserEntity entity);
@Named("stringToDate")
default Date stringToDate(String dateStr) {
// 你自己的轉(zhuǎn)換邏輯
return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
}
}十一. 更新已有對象(@MappingTarget)
有時需要把 DTO 的值“更新”到已存在的 Entity:
@Mapper
public interface UserMapper {
void updateEntityFromDto(UserDTO dto, @MappingTarget UserEntity entity);
}這樣不會新建對象,而是直接修改傳入的 entity。
十二. 表達(dá)式與常量映射
如果目標(biāo)字段是常量或需要表達(dá)式:
@Mapper
public interface UserMapper {
@Mapping(target = "status", expression = "java(entity.isActive() ? \"ACTIVE\" : \"INACTIVE\")")
@Mapping(target = "role", constant = "USER")
UserDTO entityToDto(UserEntity entity);
}十三. 映射繼承與多態(tài)
可以通過接口繼承復(fù)用映射:
@Mapper
public interface BaseMapper<E, D> {
D toDto(E entity);
E toEntity(D dto);
}
@Mapper
public interface UserMapper extends BaseMapper<UserEntity, UserDTO> { }十四. 常見坑及調(diào)試方法
- IDE未生成 Mapper 實現(xiàn)類?
- 檢查 annotation processing 是否打開,Maven/IDEA/VSCode 都要設(shè)置。
- 字段名不一致未映射?
- 用
@Mapping(source, target)明確指定。
- 用
- 集合、嵌套類型未自動轉(zhuǎn)換?
- 檢查是否正確配置
uses,相關(guān) Mapper 是否存在。
- 檢查是否正確配置
- 調(diào)試生成代碼?
- 直接到
target/generated-sources/annotations查看生成的實現(xiàn)類,理解 MapStruct 的處理邏輯。
- 直接到
- 復(fù)雜類型轉(zhuǎn)換報錯?
- 用
@Named,qualifiedByName明確指定轉(zhuǎn)換方法。
- 用
十五、其他擴展
1. LocalDate/LocalDateTime 映射
場景:DTO 里是 String,Entity 里是 LocalDate。
public class UserEntity {
private LocalDate birthday;
}
public class UserDTO {
private String birthday; // "2024-07-11"
}Mapper 寫法:
@Mapper
public interface UserMapper {
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "stringToLocalDate")
UserEntity dtoToEntity(UserDTO dto);
@Named("stringToLocalDate")
default LocalDate stringToLocalDate(String dateStr) {
return LocalDate.parse(dateStr);
}
}反向轉(zhuǎn)換:
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "localDateToString")
@Named("localDateToString")
default String localDateToString(LocalDate date) {
return date != null ? date.toString() : null;
}2. BigDecimal 映射
場景:DTO 是 String 或 Double,Entity 是 BigDecimal。
public class ProductEntity {
private BigDecimal price;
}
public class ProductDTO {
private String price;
}Mapper 寫法:
@Mapper
public interface ProductMapper {
@Mapping(source = "price", target = "price", qualifiedByName = "stringToBigDecimal")
ProductEntity dtoToEntity(ProductDTO dto);
@Named("stringToBigDecimal")
default BigDecimal stringToBigDecimal(String price) {
return price != null ? new BigDecimal(price) : null;
}
}反向同理,寫個 bigDecimalToString 方法。
3. 枚舉類型映射
場景:DTO 和 Entity 枚舉名不同/枚舉類型不同。
public enum StatusEntity { ENABLED, DISABLED }
public enum StatusDTO { ON, OFF }Mapper 寫法:
@Mapper
public interface StatusMapper {
@Mapping(source = "ENABLED", target = "ON")
@Mapping(source = "DISABLED", target = "OFF")
StatusDTO toDto(StatusEntity status);
}或者用自定義方法:
@Named("statusToDto")
default StatusDTO statusToDto(StatusEntity status) {
switch (status) {
case ENABLED: return StatusDTO.ON;
case DISABLED: return StatusDTO.OFF;
default: return null;
}
}4. 嵌套集合映射
場景:DTO 和 Entity 都有嵌套集合,如 List、Set。
public class OrderEntity {
private List<ItemEntity> items;
}
public class OrderDTO {
private List<ItemDTO> items;
}Mapper 寫法:
@Mapper
public interface ItemMapper {
ItemDTO entityToDto(ItemEntity entity);
}
@Mapper(uses = ItemMapper.class)
public interface OrderMapper {
OrderDTO entityToDto(OrderEntity entity);
List<OrderDTO> entityListToDtoList(List<OrderEntity> entities);
}MapStruct 會自動將集合中的每個元素遞歸映射。
5. 常見 MapStruct 報錯及解決
(1)找不到映射方法
報錯內(nèi)容:
No property named 'xxx' exists in source parameter(s).
原因: DTO/Entity 字段名不一致或拼寫錯誤。
解決: 用 @Mapping(source = "xxx", target = "yyy") 顯式指定。
(2)類型不兼容
報錯內(nèi)容:
Can't map property "java.lang.String price" to "java.math.BigDecimal price".
原因: MapStruct 不知道怎么轉(zhuǎn)換 String 到 BigDecimal。
解決: 寫自定義轉(zhuǎn)換方法,并用 qualifiedByName 指定。
(3)嵌套集合或?qū)ο笪醋詣佑成?/h4>
報錯內(nèi)容:
No implementation for method entityToDto(ItemEntity entity) found.
原因: ItemMapper 沒有被 uses 引用,或沒有實現(xiàn)方法。
解決: 在主 Mapper 上加 uses = {ItemMapper.class},并實現(xiàn)相關(guān)方法。
(4)編譯未生成實現(xiàn)類
原因: IDEA/Maven 未開啟 annotation processing。
解決:
- IDEA: Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable。
- Maven:
mvn clean compile,確保target/generated-sources/annotations下有實現(xiàn)類。
(5)自定義方法未被調(diào)用
原因: 沒有用 @Named 和 qualifiedByName 關(guān)聯(lián)。
解決: 方法加 @Named("xxx"),@Mapping 里加 qualifiedByName = "xxx"。
6. 進階建議
- 對于復(fù)雜類型轉(zhuǎn)換,建議都用
@Named標(biāo)記方法,方便復(fù)用和維護。 - 對于枚舉、日期、金額等類型,推薦寫專門的 Mapper 或 Converter 類。
- 多層嵌套/集合映射時,合理拆分 Mapper,避免主 Mapper 過于龐大。
參考示例
@Mapper
public interface UserMapper {
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "stringToLocalDate")
@Mapping(source = "balance", target = "balance", qualifiedByName = "stringToBigDecimal")
UserEntity dtoToEntity(UserDTO dto);
@Named("stringToLocalDate")
default LocalDate stringToLocalDate(String dateStr) {
return dateStr != null ? LocalDate.parse(dateStr) : null;
}
@Named("stringToBigDecimal")
default BigDecimal stringToBigDecimal(String val) {
return val != null ? new BigDecimal(val) : null;
}
}到此這篇關(guān)于Java MapStruct使用配置實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Java MapStruct使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springBoot使用mybatis-plus插件實現(xiàn)分頁過程
本文介紹了MyBatisPlus的集成步驟,包括項目結(jié)構(gòu)調(diào)整、pom.xml依賴添加、MyBatisPlusConfig配置文件創(chuàng)建與配置、具體代碼實現(xiàn)(controller、service、dao、xml)、SQL結(jié)果打印、自定義分頁等環(huán)節(jié),此經(jīng)驗總結(jié)供讀者參考學(xué)習(xí)2026-04-04
Java對文本文件MD5加密并ftp傳送到遠(yuǎn)程主機目錄的實現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java對文本文件MD5加密并ftp傳送到遠(yuǎn)程主機目錄的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
詳解SpringBoot和Mybatis配置多數(shù)據(jù)源
本篇文章主要介紹了詳解SpringBoot和Mybatis配置多數(shù)據(jù)源,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
springboot實現(xiàn)maven多模塊和打包部署
本文主要介紹了springboot實現(xiàn)maven多模塊和打包部署,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
InterlliJ IDEA2020新建java web項目找不到Static Web的解決
這篇文章主要介紹了InterlliJ IDEA2020新建java web項目找不到Static Web的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解
這篇文章主要介紹了Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04
如何使用Spring RestTemplate訪問restful服務(wù)
這篇文章主要介紹了如何使用Spring RestTemplate訪問restful服務(wù),詳細(xì)的介紹了什么是RestTemplate以及簡單實現(xiàn),非常具有實用價值,需要的朋友可以參考下2018-10-10
Mybatis動態(tài)SQL foreach標(biāo)簽用法實例
這篇文章主要介紹了Mybatis動態(tài)SQL foreach標(biāo)簽用法實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10
Java concurrency集合之CopyOnWriteArraySet_動力節(jié)點Java學(xué)院整理
CopyOnWriteArraySet基于CopyOnWriteArrayList實現(xiàn),其唯一的不同是在add時調(diào)用的是CopyOnWriteArrayList的addIfAbsent(若沒有則增加)方法2017-06-06

