MapStruct的使用详解与常见用法介绍

项目转换DTO使用总结,常用技巧

概要

mapstruct在当前轻量级框架开发中的重点使用,@Named注解使用示例,@AfterMapping与@BeforeMapping注解的详细常见用法,在转换DTO时,与过去常用的beanUtil转换有高性能的转换优势,编译期自动生成的mapper实现类能够更加优雅的来实现各种隐式类型转换,以实现快速而又敏捷的开发,告别臃肿的手动get、set与类型的强转

引入

当前core-service引入版本如下

<properties>
    <mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>${mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${mapstruct.version}</version>
</dependency>
基本使用
  • 使用@Mapper注解,声明映射器,可以是接口,或者抽象类
  • 使用@Mapping注解,实现灵活的字段映射,定制映射的规则
注入方式
  • 工厂方式
@Mapper
public interface ItemMapper {
    //使用工厂方法获取Mapper实例
    ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);
    ItemDTO toDTO(Item item);
}
  • 依赖注入方式
@Mapper(componentModel = "spring")
public interface ItemMapper {
    ItemDTO toDTO(Item item);
}
简单映射

代码可以自动生成的Mapper接口,可以自动转换相同属性的字段,无需其他声明。

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AwardMapper extends BaseMapper<AwardDTO, Award> {

}

需要注意的是:再不写任何转换代码的情况下,如果实体类中配置了其他关联的一对多或一对一的实体,DTO如果同样需要转换的话,就需要保证属性名一致才能正常转换。

自动类型转换

对于基础数据类型会进行自动隐式的转换如int、long、String,Integer、Long等;

//生成的实现类代码可以看出来
@Component
public class AssemblerImpl implements Assembler {
    @Override
    public ProductDTO toDTO(Product product) {
        if ( product == null ) {
            return null;
        }
        ProductDTO productDTO = new ProductDTO();
        if ( product.getProductId() != null ) {
            //String自动转int
            productDTO.setProductId( Integer.parseInt( product.getProductId() ) );
        }
        if ( product.getPrice() != null ) {
            //Long转String
            productDTO.setPrice( String.valueOf( product.getPrice() ) );
        }
        return productDTO;
    }
}
指定格式转换

查看mapping注解可以看到

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface Mapping {
    String target();

    String source() default "";

    String dateFormat() default "";

    String numberFormat() default "";
    
    ...
}

dateFormat与numberFormat即可用来进行转换操作

  • dateFormat用于日期格式转换
@Mapper(componentModel = "spring")
public interface Demo4Assembler {
    @Mapping(target = "saleTime", dateFormat = "yyyy-MM-dd HH:mm:ss")  //Date转换成String
    @Mapping(target = "validTime", dateFormat = "yyyy-MM-dd HH:mm")    //String转换成Date
    ProductDTO toDTO(Product product);
}
  • 日期格式转换可以直接在DTO中注解形式转换,比mapper更为便捷
	// 活动开始时间
	@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss",timezone="GMT+8")
	private Timestamp startTime;
	
	// 活动结束时间
	@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss",timezone="GMT+8")
	private Timestamp endTime;
  • numberFormat用于数字格式转换【不常用】
   private String price;
   private Integer stock;
	@Mapper(componentModel = "spring")
	public interface Demo3Assembler {
    @Mapping(target = "price", numberFormat = "#.00元") 
    @Mapping(target = "stock", numberFormat = "#个") 
    ProductDTO toDTO(Product product);
}
不同参数映射
  • 基本不同属性名转换,需要使用@Mappings注解进行指定转换,参数为数组,支持多个字段转换
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DrawRecordDetailMapper extends BaseMapper<DrawRecordDetailDTO, DrawRecord> {
    @Override
    @Mappings({
            @Mapping(target = "chanceId", source = "participationChanceId")
    })
    DrawRecordDetailDTO toDto(DrawRecord entity);

} 
  • 对象属性提取和普通属性装载对象中
//将entity中activity实体中name属性装载至DrawRecordDetailDTO中activityName属性上
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DrawRecordDetailMapper extends BaseMapper<DrawRecordDetailDTO, DrawRecord> {

    @Override
    @Mappings({
            @Mapping(target = "activityName", source = "activity.name")
    })
    DrawRecordDetailDTO toDto(DrawRecord entity);   
}

//将entity中活动规则属性,转换至ActivityDetailDTO中activityRule对象中
	@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
	public interface ActivityDetailMapper extends BaseMapper<ActivityDetailDTO, Activity> {
	    @Override
	    @Mappings({
	            @Mapping(target = "activityRule.voteOverlapAvailable", source = "voteOverlapAvailable"),
	            @Mapping(target = "activityRule.voteMultipleOnceAvailable", source = 		                                    "voteMultipleOnceAvailable")
	    })
    ActivityDetailDTO toDto(Activity entity);
}    
自定义转换器
  • 基础映射方式,mapper接口中直接引入即可(对数据接口要求高)

一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配的

如果绑定的映射中,存在多个相同的入参和出参方法,将会报错

使用样例:将list中多个code在映射时,转换为String用逗号隔开

定义映射器:

@Component
public class MediaServiceFormater {
    public String format(List<String> serviceCodeList) {
        return String.join(",", serviceCode);
    }
}

mapper接口绑定映射器 【uses = {MediaServiceFormater.class}】

@Mapper(componentModel = "spring", uses = {MediaServiceFormater.class,unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MediaDetailMapper extends BaseMapper<MediaDetailDTO, Media> {

}
  • 基于named注解实现【常用】

映射器中使用注解来定义转换方法,使用时,具体属性绑定方法;

定义映射器:

@Component
public class MapStructConverterUtil {
    private static final Logger log = LoggerFactory.getLogger(MapStructConverterUtil.class);

    public MapStructConverterUtil() {
    }

    @Named("getNormalImage")
    public String getNormalImage(String images) {
        if (StringUtils.isEmpty(images)) {
            return null;
        } else {
            try {
                if (images.contains("normal")) {
                    JSONObject imageJson = JSON.parseObject(images);
                    JSONObject mapJson = imageJson.getJSONObject("map");
                    JSONArray normalJson = mapJson.getJSONArray("normal");
                    Integer index = normalJson.getInteger(0);
                    JSONObject object = imageJson.getJSONArray("list").getJSONObject(index);
                    return object.getString("fileUrl");
                }
            } catch (Exception var7) {
                log.error("normal图片解析出错,原images = [{}]", images);
            }

            return null;
        }
    }
  }

使用时绑定映射器:【uses = {MapStructConverterUtil.class}】

@Mapper(componentModel = "spring", uses = {MapStructConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ActivityUserDetailMapper extends BaseMapper<ActivityUserDetailDTO, Activity> {

    @Override
    @Mappings({
            @Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
            @Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
            @Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage")
    })
    ActivityUserDetailDTO toDto(Activity entity);

}
  • Map映射

mapStruct还支持map集合的转换,可以对map进行隐式转换,支持对Key与value进行隐式转换

例如:日期转字符串,字符串转日期;同样支持@name注解转换形式,或使用qualifiedBy指定一个转换类(类中默认使用相同入参出参来匹配)

@Mapper(componentModel = "spring", uses = {ConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface SourceTargetMapper {
   @MapMapping(keyQualifiedByName = "getValue", valueDateFormat = "dd.MM.yyyy")
   Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
复杂映射实现

多个字段映射到一个字段,单个字段快速特殊处理

  • 自定义字段表达式映射
    • 单个字段特殊转换处理
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface GroupDetailOrderMapper extends BaseMapper<GroupDetailOrderDTO, GroupDetailOrder> {

    @Override
    @Mappings({
            @Mapping(target = "userName", expression = "java(entity.getUserName() == null ? \"\" : new String(java.util.Base64.getDecoder().decode(entity.getUserName()), java.nio.charset.StandardCharsets.UTF_8))")
    })
    GroupDetailOrderDTO toDto(GroupDetailOrder entity);
}
  • 多个字段处理为一个字段
@Mapper(componentModel = "spring", imports = DecimalUtils.class) 
public interface Demo16Assembler {
    @Mapping(target = "price", expression = "java(product.getPrice1() + product.getPrice2())") 
    @Mapping(target = "price2", expression = "java(DecimalUtils.add(product.getPrice1(), product.getPrice2()))") 
    ProductDTO toDTO(Product product);
}
  • 前后置处理实现复杂映射

使用@BeforeMapping与@AfterMapping注解来实现,需要定义默认方法在mapper接口中【java8后支持】

使用前注意:

​ 在调用上下文上的映射方法之前/之后,不执行空检查参数。调用方需要确保在这种情况下不传递null;

@Mapper(componentModel = “spring”, uses = {MapStructConverterUtil.class, MediaConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MediaDetailMapper extends BaseMapper<MediaDetailDTO, Media> {

 	//前置执行
    @BeforeMapping 
    default MediaDetailDTO setMediaName(Media media) {
        MediaDetailDTO mediaDetailDTO = new MediaDetailDTO();
        mediaDetailDTO.setName("默认名称");
        return mediaDetailDTO;
    }
    
    @Override
    @Mappings({
            @Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
            @Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
            @Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage"),
            @Mapping(target = "cp", source = "contentProviderId", qualifiedByName = "getCP"),
            @Mapping(target = "audioTrack", source = "audioDescription")
    })
    MediaDetailDTO toDto(Media media);

	 //后置执行
    @AfterMapping
    default void setMediaType(@MappingTarget MediaDetailDTO mediaDetailDTO, Media media) {
        Integer type = media.getType();
        Integer contentType = media.getContentType();
        String contentTyp = String.format("%02d", contentType);
        mediaDetailDTO.setMediaType(type + contentTyp);
    }
}
隐式映射实现

利用继承关系,继承mapper自动生成的实现类,来重写增强

此转换方式,同样可以实现在dto转换后后置执行,实体真正实现类中代码简洁也较为优雅

需要注意的是,一定要先实现父类转换方法拿到结果后在进行后续增强转换

super.toDto(entity)
实现样例如下:

//实现类引入方式使用@Resource注解引入
 @Autowired
 @Resource(name = "activityDetailMapperImplPlus")
 private ActivityDetailMapper activityDetailMapper;

@Component("activityDetailMapperImplPlus")
@RequiredArgsConstructor
public class ActivityDetailMapperImplPlus extends ActivityDetailMapperImpl {

    private final VoteTargetRepository voteTargetRepository;

    @Override
    public ActivityDetailDTO toDto(Activity entity) {
        ActivityDetailDTO activityDetailDTO = super.toDto(entity);
        
        Long activityDetailDTOId = activityDetailDTO.getId();
        Integer type = activityDetailDTO.getType();
        boolean vote = LocalActConstant.ACT_TYPE_VOTE == type;
        if (vote) {
            Optional<List<VoteTarget>> voteOp = this.voteTargetRepository.findByActivityIdAndStatus(activityDetailDTOId, 1);
            if (voteOp.isPresent()) {
                List<VoteTarget> voteTargets = voteOp.get();
                //累积投票
                activityDetailDTO.setTotalVoteNum(voteTargets.stream()
                        .filter(voteTarget -> voteTarget.getCurrentNumber() != null)
                        .mapToInt(VoteTarget::getCurrentNumber).sum());
            }
        }
        return activityDetailDTO;
    }
}

隐式转换扩展

如果实体类中,关联了其他一对多或多对多的引用类型,JPA在mapper层自动转换后,又需要对引用对象增加出参或出参数据结构转换时,这时候利用转换生成是实现类继承强化后,再绑定到mapper中即可。这样就无需在接口实现层进行循环处理

使用样例如下:

MarketingActivityDetailDTO中有 private List registrationEvents;

需要在BaseRegistrationEventDTO对象中增加出参

//报名人数
private Long totalRegister;
接口侧转换使用的MarketingActivityDetailMapper接口如下:引用必须要包含RegistrationEventSimpleMapperImplPlus.class

uses = {MapStructConverterUtil.class, TemplateParamValueMapper1.class, RegistrationEventSimpleMapperImplPlus.class}

@Mapper(componentModel = "spring", uses = {MapStructConverterUtil.class, TemplateParamValueMapper1.class, RegistrationEventSimpleMapperImplPlus.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MarketingActivityDetailMapper extends BaseMapper<MarketingActivityDetailDTO, MarketingActivity> {

    @Override
    @Mappings({
            @Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
            @Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
            @Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage")
    })
    MarketingActivityDetailDTO toDto(MarketingActivity entity);
}

RegistationEventSimpleMapper如下

@Component("registrationEventSimpleMapperImplPlus")
@RequiredArgsConstructor
public class RegistrationEventSimpleMapperImplPlus extends RegistrationEventSimpleMapperImpl {
    private final UserRegistrationRepository userRegistrationRepository;
    @Override
    public BaseRegistrationEventDTO toDto(RegistrationEvent entity) {
        BaseRegistrationEventDTO baseRegistrationEventDTO = super.toDto(entity);
        Long registrationEventId = baseRegistrationEventDTO.getId();
      baseRegistrationEventDTO.setTotalRegister(
         this.userRegistrationRepository.countByRegistrationEventId(registrationEventId));
        return baseRegistrationEventDTO;
    }
}
  • 13
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MapStruct是一个Java注解处理器,可以帮助开发者快速生成Java Bean之间的映射代码。通过注解和代码生成,MapStruct能够自动映射两个Java Bean之间的属性。 下面是一个使用MapStruct的示例: 首先,在Maven项目中添加以下依赖: ```xml <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.4.2.Final</version> <scope>provided</scope> </dependency> ``` 然后,我们需要定义两个Java Bean类,例如: ```java public class SourceBean { private String name; private int age; // getter and setter } public class TargetBean { private String name; private int age; // getter and setter } ``` 接下来,我们需要定义一个Mapper接口,用于映射两个Java Bean之间的属性。例如: ```java @Mapper public interface MyMapper { MyMapper INSTANCE = Mappers.getMapper(MyMapper.class); TargetBean sourceToTarget(SourceBean sourceBean); } ``` 在这个接口中,我们使用MapStruct提供的@Mapper注解,表示这个接口是一个Mapper接口。同时,我们还定义了一个静态的INSTANCE实例,并使用了Mappers.getMapper方法来获取该实例。 接着,我们在这个接口中定义了一个sourceToTarget方法,用于将SourceBean映射到TargetBean。在这个方法中,我们只需要编写一个简单的映射规则即可,例如: ```java @Mapping(source = "name", target = "name") @Mapping(source = "age", target = "age") TargetBean sourceToTarget(SourceBean sourceBean); ``` 在这个方法中,我们使用MapStruct提供的@Mapping注解,指定了SourceBean和TargetBean中属性之间的映射关系。例如,source = "name"表示SourceBean中的name属性映射到TargetBean中的name属性。 最后,在我们的代码中,我们可以使用MyMapper.INSTANCE.sourceToTarget方法来将SourceBean转换为TargetBean,例如: ```java SourceBean sourceBean = new SourceBean(); sourceBean.setName("张三"); sourceBean.setAge(18); TargetBean targetBean = MyMapper.INSTANCE.sourceToTarget(sourceBean); System.out.println(targetBean.getName()); // 输出:张三 System.out.println(targetBean.getAge()); // 输出:18 ``` 这样,我们就完成了两个Java Bean之间的属性映射。使用MapStruct能够让我们的代码更加简洁和可维护,特别是在处理复杂的Java Bean之间的映射时,MapStruct能够大大提高我们的开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值