MapStruct魔法:让Bean拷贝变成艺术

技术派项目源码地址 :


传统样板代码的困扰

假如没有使用 MapStruct 的话,当我们需要把 DO 对象转成一个 DTO 对象时

public static ArticleDTO toDto(ArticleDO articleDO) {
    if (articleDO == null) {
        return null;
    }
    ArticleDTO articleDTO = new ArticleDTO();
    articleDTO.setAuthor(articleDO.getUserId());
    articleDTO.setArticleId(articleDO.getId());
    articleDTO.setArticleType(articleDO.getArticleType());
    articleDTO.setTitle(articleDO.getTitle());
    articleDTO.setShortTitle(articleDO.getShortTitle());
    articleDTO.setSummary(articleDO.getSummary());
    articleDTO.setCover(articleDO.getPicture());
    articleDTO.setSourceType(SourceTypeEnum.formCode(articleDO.getSource()).getDesc());
    articleDTO.setSourceUrl(articleDO.getSourceUrl());
    articleDTO.setStatus(articleDO.getStatus());
    articleDTO.setCreateTime(articleDO.getCreateTime().getTime());
    articleDTO.setLastUpdateTime(articleDO.getUpdateTime().getTime());
    articleDTO.setOfficalStat(articleDO.getOfficalStat());
    articleDTO.setToppingStat(articleDO.getToppingStat());
    articleDTO.setCreamStat(articleDO.getCreamStat());
    articleDTO.setCategory(new CategoryDTO(articleDO.getCategoryId(), null));
    return articleDTO;
}

如果是 List 的时候,还需要进行遍历。

public static List<ArticleDTO> toArticleDtoList(List<ArticleDO> articleDOS) {
    return articleDOS.stream().map(ArticleConverter::toDto).collect(Collectors.toList());
}

这样的样板代码写多了,就会感觉毫无意义!!!

引入MapStruct

@Mapper
public interface ArticleStructMapper {
    ArticleStructMapper INSTANCE = Mappers.getMapper(ArticleStructMapper.class );
    ArticleDTO toDTO(ArticleDO do);
}

该接口的主要作用是将 ArticleDO 对象转换为 ArticleDTO 对象。


  1. @Mapper 注解
  • @Mapper 是 MapStruct 提供的注解,表明这个接口是一个映射器(Mapper)。MapStruct 会在编译时自动生成这个接口的实现类,用于在不同类型的对象之间进行转换。
  1. 接口定义
  • public interface ArticleStructMapper:定义了一个名为 ArticleStructMapper 的接口。用来描述对象转换的方法。
  1. 静态实例

**ArticleStructMapper INSTANCE = Mappers.getMapper(ArticleStructMapper.class);**

  • 这里声明了一个名为 INSTANCE 的静态变量,它是通过 Mappers.getMapper(ArticleStructMapper.class) 获得的。这个方法会返回 ArticleStructMapper 的实现类的实例。
  • 这样做的好处是,你可以通过 ArticleStructMapper.INSTANCE 直接访问映射器,而不需要自己手动创建它的实例。
  1. 映射方法

**ArticleDTO toDTO(ArticleDO do);**

  • 这是一个抽象方法,定义了从 ArticleDOArticleDTO 的转换。MapStruct 会在编译时生成这个方法的实现。
  • ArticleDOArticleDTO 分别代表数据对象和数据传输对象,这里假设 ArticleDO 是数据库实体类,ArticleDTO 是数据传输对象

整合MapStruct

引入依赖
<mapstruct.version>1.5.5.Final</mapstruct.version>

<!-- MapStruct -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${mapstruct.version}</version>
    <scope>compile</scope>
</dependency>
  1. org.mapstruct:mapstruct
  • MapStruct 的核心库。它提供了 MapStruct 所需的主要注解和工具方法,例如 @Mapper, @Mapping 等注解以及 Mappers.getMapper() 方法。
  • 在运行时,这个库是必需的,生成的映射代码会依赖它。
  1. org.mapstruct:mapstruct-processor
  • MapStruct 的注解处理器。它在编译时生成具体的映射实现代码。
  • compile 作用域,意味着它只在编译时被使用。
  • 当你编译一个使用了 MapStruct 注解的项目时,注解处理器会检测你的代码,然后为你的 @Mapper 注解的接口或抽象类生成实现

再来个稍微复杂一些的接口

@Mapper
public interface ColumnStructMapper {
    ColumnStructMapper INSTANCE = Mappers.getMapper( ColumnStructMapper.class );

    /**
     * ColumnInfoDO to ColumnDTO
     * @param columnInfoDO
     * @return
     */
    // sources 是参数,target 是目标
    @Mapping(source = "id", target = "columnId")
    @Mapping(source = "columnName", target = "column")
    @Mapping(source = "userId", target = "author")
    // Date 转 Long
    @Mapping(target = "publishTime", expression = "java(columnInfoDO.getPublishTime().getTime())")
    @Mapping(target = "freeStartTime", expression = "java(columnInfoDO.getFreeStartTime().getTime())")
    @Mapping(target = "freeEndTime", expression = "java(columnInfoDO.getFreeEndTime().getTime())")
    ColumnDTO infotoDto(ColumnInfoDO columnInfoDO);

    List<ColumnDTO> infoToDtos(List<ColumnInfoDO> columnInfoDOs);

    @Mapping(source = "column", target = "columnName")
    @Mapping(source = "author", target = "userId")
    // Long 转 Date
    @Mapping(target = "freeStartTime", expression = "java(new java.util.Date(req.getFreeStartTime()))")
    @Mapping(target = "freeEndTime", expression = "java(new java.util.Date(req.getFreeEndTime()))")
    ColumnInfoDO toDo(ColumnReq req);
}

@Mapping 注解

当两个对象中的字段名或者字段类型不一致的时候,就需要该注解来进行转换。
如果对象的字段名/类型完全一样,就完全不需要该注解,Mapstruct 会自动拷贝。

比如说 SimpleSource 和 SimpleDestination 的字段名和类型完全一样:

public class SimpleSource {
    private String name;
    private String description;
    // getters and setters
}
 
public class SimpleDestination {
    private String name;
    private String description;
    // getters and setters
}

只需要定义映射器 SimpleSourceDestinationMapper 就行了。

@Mapper
public interface SimpleSourceDestinationMapper {
    SimpleSourceDestinationMapper INSTANCE = Mappers.getMapper(SimpleSourceDestinationMapper.class);
    SimpleDestination sourceToDestination(SimpleSource source);
    SimpleSource destinationToSource(SimpleDestination destination);
}

测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = QuickForumApplication.class)
public class SimpleSourceDestinationMapperIntegrationTest {

    @Test
    public void givenSourceToDestination_whenMaps_thenCorrect() {
        SimpleSource simpleSource = new SimpleSource();
        simpleSource.setName("莜莜想睡觉");
        simpleSource.setDescription("困困睡不醒");
        SimpleDestination destination = SimpleSourceDestinationMapper.INSTANCE.sourceToDestination(simpleSource);
        assertEquals(simpleSource.getName(), destination.getName());
        assertEquals(simpleSource.getDescription(), destination.getDescription());
    }
}

@Mapping 注解部分用法
  1. 基本映射
@Mapping(source = "name", target = "fullName")
  1. 常量映射
@Mapping(target = "status", constant = "ACTIVE")

这会将目标对象的 status 属性设置为 “ACTIVE”

  1. 默认值 : 当源属性为 null 时,可以为目标属性设置默认值
@Mapping(source = "count", target = "total", defaultValue = "0")

如果 count 为 null,则 total 将被设置为 “0”。

  1. 表达式
@Mapping(target = "timestamp", expression = "java(source.getDate().getTime())")

这会将 Date 对象转换为时间戳。

  1. 日期格式
@Mapping(source = "date", target = "formattedDate", dateFormat = "yyyy-MM-dd")

这会将 Date 对象转换为 “yyyy-MM-dd” 格式的字符串。

  1. 嵌套映射
@Mapping(source = "address.street", target = "streetName")

这会将源对象中的 address 对象的 street 属性映射到目标对象的 streetName 属性。

  1. 忽略映射
@Mapping(target = "internalId", ignore = true)

这会确保目标对象的 internalId 属性不被设置。


Spring依赖注入

在 Spring 环境下,还可以在 @Mapper 注解中添加 componentModel = “spring” 参数来告诉 MapStruct 在生成映射实现类的时候,提供 Spring 依赖注入。

@Mapper(componentModel = "spring")
public interface ColumnStructMapper {}

可以直接通过 @Autowired 注解来注入 ColumnStructMapper 对象

@Autowired
private ColumnStructMapper columnStructMapper;
ColumnInfoDO columnInfoDO = columnStructMapper.toDo(req);

MapStruct 的 IDEA 插件

image.png

安装完成后,可以直接在 @Mapper 接口和它的实现类之间快速导航。
image.png

就比如下面的就是某个实现类
image.png


小结

在Java开发中,Bean对象之间的数据拷贝是一项常见但繁琐的任务。MapStruct作为一个高效的Java注解处理器,通过简洁的注解配置,自动生成类型安全的Bean拷贝代码,使这一过程变得简洁优雅。与传统的手动拷贝方式相比,MapStruct不仅减少了样板代码,还提高了代码的可读性和维护性。开发者只需定义接口并添加相关注解,MapStruct便会在编译时生成实现类,将源对象的属性无缝拷贝到目标对象中。通过这种方式,Bean拷贝不仅变得高效,还如同艺术般精致,极大地提升了开发体验。

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值