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

