MapStruct 自定义字段转换

前言:

MapStruct 是一个强大的 Java Bean 映射工具,默认情况下能自动处理类型匹配的字段。
但在实际开发中,经常需要自定义字段转换逻辑,比如日期格式转换、枚举映射、复杂对象转换等。以下是几种常见的自定义转换方法

一、自定义转换方法

  1. 基本类型转换
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = "spring")
public interface UserMapper{

   UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
   
    // 自定义转换方法
    default String birthdayToString(Date date) {
        if (date == null) {
            return null;
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        return format.format(date);
    }
    
    // 使用 @Mapping 指定自定义转换方法
    @Mapping(target = "birthDateStr", source = "birthday", qualifiedByName = "birthdayToString")
    UserDto toDto(User user);
}
  1. 枚举映射
@Mapper(componentModel = "spring")
public interface OrderMapper {
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
    
    // 枚举映射方法
    default OrderStatusEnum orderStatusToDto(OrderStatusEnum orderStatus) {
        if (orderStatus== null) {
            return null;
        }
        switch (orderStatus) {
            case PAID:
                return OrderStatusEnum.PAID;
            case CANCELLED:
                return OrderStatusEnum.CANCELLED;
            default:
                return OrderStatusEnum.PENDING;
        }
    }
    
    @Mapping(target = "status", source = "orderStatus")
    OrderDto toDto(Order order);
}
  1. @Mappings 注解自定义字段映射举例
@Mapper(componentModel = "spring")
public interface ProcessTaskMapper {

 	@Mappings({
 			@Mapping(source = "title", target = "titleName"),  //不同字段名映射
            @Mapping(target = "delFlag", ignore = true)  //指定字段忽略转换
            @Mapping( target = "type", defaultValue= "2")   // 如果源属性为 null,则要设置的默认值。
            @Mapping(target = "flowid", expression = "java(taskPushDTO.getProcessInstId().concat(taskPushDTO.getNodeId()))"),  //java代码转换,字符串拼接
            @Mapping(target = "platType", expression = "java(form.getLoginPlat() != null ? java.lang.Integer.valueOf(form.getLoginPlat()) : null)") //java 代码三元表达式
            @Mapping(target = "createdTime",expression = "java(java.util.Date.from(taskPush.getGmtCreate().atZone(java.time.ZoneId.systemDefault()).toInstant()))"), //java.time.LocalDateTime 转换成 java.util.Date     
            @Mapping(source = "remark", target = "remarkJson", qualifiedByName = "converRemarkToJson"),
    })// @qualifiedByName 指定使用自定义的转换方法转换
    ProcessTaskPushDTO formToDto(ProcessTaskPushFrom taskPushFrom);
    
    // 集合转JsonString 
  	@Named("converRemarkToJson")
    static String converRemarkJson(List<RemarkDTO> remarkDTOList) {
        return CollectionUtil.isNotEmpty(remarkDTOList) ? JSON.toJSONString(remarkDTOList) : "";
    }

  }

二、使用 @Mapper 注解的 uses 属性

  1. 提取通用转换逻辑到单独类
// 通用转换器
public class DateConverter {
    public String dateToString(Date date) {
        if (date == null) {
            return null;
        }
        return new SimpleDateFormat("yyyy-MM-dd").format(date);
    }
    
    public Date stringToDate(String dateStr) {
        if (dateStr == null) {
            return null;
        }
        try {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

// 在 Mapper 中引用转换器
@Mapper(uses = DateConverter.class)
public interface ProductMapper {
    ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
    
    @Mapping(target = "createDateStr", source = "createDate")
    ProductDto toDto(Product product);
}

三、复杂对象转换

  1. 嵌套对象转换

@Mapper
public interface AddressMapper {
    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
    
    AddressDto toDto(Address address);
}


@Mapper(uses = AddressMapper.class)
public interface CustomerMapper {
    CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
    
    @Mapping(target = "addressDto", source = "address")
    CustomerDto toDto(Customer customer);
}
  1. 集合转换
@Mapper
public interface ItemMapper {
    ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);
    
    ItemDto toDto(Item item);
    
    // MapStruct 会自动使用 toDto 方法进行集合转换
    List<ItemDto> toDtoList(List<Item> items);
}

四、使用 @Context 传递上下文

  1. 带上下文的转换
// 上下文对象
public class LocaleContext {
    private final Locale locale;
    
    public LocaleContext(Locale locale) {
        this.locale = locale;
    }
    
    public Locale getLocale() {
        return locale;
    }
}

@Mapper
public interface PriceMapper {
    PriceMapper INSTANCE = Mappers.getMapper(PriceMapper.class);
    
    // 使用 @Context 传递上下文
    @Mapping(target = "formattedPrice", source = "price")
    PriceDto toDto(Price price, @Context LocaleContext context);
    
    default String formatPrice(BigDecimal price, @Context LocaleContext context) {
        if (price == null) {
            return null;
        }
        NumberFormat format = NumberFormat.getCurrencyInstance(context.getLocale());
        return format.format(price);
    }
}

五、高级自定义:@BeforeMapping 和 @AfterMapping 注解

  1. 映射前处理
    @BeforeMapping,在执行映射逻辑之前填充目标映射DTO
@Mapper
public abstract class CarsMapper {
    @BeforeMapping
    protected void enrichDTOWithFuelType(Car car, @MappingTarget CarDTO carDto) {
        if (car instanceof ElectricCar) {
            carDto.setFuelType(FuelType.ELECTRIC);
        }
        if (car instanceof BioDieselCar) { 
            carDto.setFuelType(FuelType.BIO_DIESEL);
        }
    }

    public abstract CarDTO toCarDto(Car car);
}
  1. 映射后处理
    @AfterMapping注释方法的情况下在执行之后填充
@Mapper
public interface OrderMapper {
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
    
    @Mapping(target = "totalPrice", ignore = true)  // 忽略自动映射
    OrderDto toDto(Order order);
    
    // 映射完成后计算总价
    @AfterMapping
    default void calculateTotalPrice(Order order, @MappingTarget OrderDto dto) {
        if (order.getItems() != null) {
            BigDecimal total = order.getItems().stream()
                .map(Item::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
            dto.setTotalPrice(total);
        }
    }
}

六、配置举例:

依赖配置:

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>

编译选项:

// 在 Maven 中配置处理器选项
<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>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值