文章目录
技术派项目源码地址 :
- Gitee :技术派 - https://gitee.com/itwanger/paicoding
- Github :技术派 - https://github.com/itwanger/paicoding
传统样板代码的困扰
假如没有使用 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 对象。
- @Mapper 注解:
@Mapper
是 MapStruct 提供的注解,表明这个接口是一个映射器(Mapper)。MapStruct 会在编译时自动生成这个接口的实现类,用于在不同类型的对象之间进行转换。
- 接口定义:
public interface ArticleStructMapper
:定义了一个名为ArticleStructMapper
的接口。用来描述对象转换的方法。
- 静态实例:
**ArticleStructMapper INSTANCE = Mappers.getMapper(ArticleStructMapper.class);**
- 这里声明了一个名为
INSTANCE
的静态变量,它是通过Mappers.getMapper(ArticleStructMapper.class)
获得的。这个方法会返回ArticleStructMapper
的实现类的实例。 - 这样做的好处是,你可以通过
ArticleStructMapper.INSTANCE
直接访问映射器,而不需要自己手动创建它的实例。
- 映射方法:
**ArticleDTO toDTO(ArticleDO do);**
- 这是一个抽象方法,定义了从
ArticleDO
到ArticleDTO
的转换。MapStruct 会在编译时生成这个方法的实现。 ArticleDO
和ArticleDTO
分别代表数据对象和数据传输对象,这里假设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>
- org.mapstruct:mapstruct
- MapStruct 的核心库。它提供了 MapStruct 所需的主要注解和工具方法,例如 @Mapper, @Mapping 等注解以及 Mappers.getMapper() 方法。
- 在运行时,这个库是必需的,生成的映射代码会依赖它。
- 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 注解部分用法
- 基本映射
@Mapping(source = "name", target = "fullName")
- 常量映射
@Mapping(target = "status", constant = "ACTIVE")
这会将目标对象的 status 属性设置为 “ACTIVE”
- 默认值 : 当源属性为 null 时,可以为目标属性设置默认值
@Mapping(source = "count", target = "total", defaultValue = "0")
如果 count 为 null,则 total 将被设置为 “0”。
- 表达式
@Mapping(target = "timestamp", expression = "java(source.getDate().getTime())")
这会将 Date 对象转换为时间戳。
- 日期格式
@Mapping(source = "date", target = "formattedDate", dateFormat = "yyyy-MM-dd")
这会将 Date 对象转换为 “yyyy-MM-dd” 格式的字符串。
- 嵌套映射
@Mapping(source = "address.street", target = "streetName")
这会将源对象中的 address 对象的 street 属性映射到目标对象的 streetName 属性。
- 忽略映射
@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 插件
安装完成后,可以直接在 @Mapper 接口和它的实现类之间快速导航。
就比如下面的就是某个实现类
小结
在Java开发中,Bean对象之间的数据拷贝是一项常见但繁琐的任务。MapStruct作为一个高效的Java注解处理器,通过简洁的注解配置,自动生成类型安全的Bean拷贝代码,使这一过程变得简洁优雅。与传统的手动拷贝方式相比,MapStruct不仅减少了样板代码,还提高了代码的可读性和维护性。开发者只需定义接口并添加相关注解,MapStruct便会在编译时生成实现类,将源对象的属性无缝拷贝到目标对象中。通过这种方式,Bean拷贝不仅变得高效,还如同艺术般精致,极大地提升了开发体验。