Java MapStruct优雅地实现对象转换

 更新时间:2023年06月05日 14:12:05   作者:Java小虫  
MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器,用它可以轻松实现对象转换,下面就来和大家聊聊具体操作吧

1、什么是MapStruct

1.1 JavaBean 的困扰

对于代码中 JavaBean之间的转换, 一直是困扰我很久的事情。在开发的时候我看到业务代码之间有很多的 JavaBean 之间的相互转化, 非常的影响观感,却又不得不存在。我后来想的一个办法就是通过反射,或者自己写很多的转换器。

第一种通过反射的方法确实比较方便,但是现在无论是 BeanUtils, BeanCopier 等在使用反射的时候都会影响到性能。虽然我们可以进行反射信息的缓存来提高性能。但是像这种的话,需要类型和名称都一样才会进行映射,有很多时候,由于不同的团队之间使用的名词不一样,还是需要很多的手动 set/get 等功能。

第二种的话就是会很浪费时间,而且在添加新的字段的时候也要进行方法的修改。不过,由于不需要进行反射,其性能是很高的。

1.2 MapStruct 带来的改变

MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。

  • 注解处理器
  • 可以生成 JavaBean 之间那的映射代码
  • 类型安全,高性能,无依赖性

2、MapStruct 入门

2.1 添加依赖

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

2.2 po类

@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
}

2.3 dto类

@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
}

2.4 创建转换接口

//可以使用abstract class代替接口
@Mapper
publicinterface UserMapper {
UserDto userToUserDto(User user);
//集合
List<UserDto> userToUserDto(List<User> users);
}

2.5 测试方法

@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 UserDto userDto = mapper.userToUserDto(user);
 System.out.println(userDto);
}

2.6 运行效果

2.7 查看编译的class

底层通过自动取值赋值操作完成

3、MapStruct优点分析

3.1 性能高

这是相对反射来说的,反射需要去读取字节码的内容,花销会比较大。而通过 MapStruct 来生成的代码,其类似于人手写。速度上可以得到保证。

3.2 使用简单

如果是完全映射的,使用起来肯定没有反射简单。用类似 BeanUtils 这些工具一条语句就搞定了。但是,如果需要进行特殊的匹配(特殊类型转换,多对一转换等),其相对来说也是比较简单的。

基本上,使用的时候,我们只需要声明一个接口,接口下写对应的方法,就可以使用了。当然,如果有特殊情况,是需要额外处理的。

3.3 代码独立

生成的代码是对立的,没有运行时的依赖。

3.4 易于 debug

在我们生成的代码中,我们可以轻易的进行 debug。

4、MapStruct使用案例

4.1 属性名称相同

在实现类的时候,如果属性名称相同,则会进行对应的转化。通过此种方式,我们可以快速的编写出转换的方法。(入门案例)

4.2 属性名不相同

属性名不相同,在需要进行互相转化的时候,则我们可以通过@Mapping 注解来进行转化。

@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
private String password;
}
@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private String pwd;
}
@Mapper
publicinterface UserMapper {
//单个属性
//@Mapping(source = "pwd",target = "password")
//多个属性
@Mappings({
@Mapping(source = "pwd",target = "password")
 })
UserDto userToUserDto(User user);
}
  • source 需要转换的对接,通常是入参
  • target 转换的对接,通常是出参
  • ignore 忽略,默认false不忽略,需要忽略设置为true
  • defaultValue 默认值
  • expressions 可以通过表达式来构造一些简单的转化关系。虽然设计的时候想兼容很多语言,不过目前只能写Java代码。
@Mappings({
@Mapping(source = "birthdate", target = "birth"),//属性名不一致映射
@Mapping(target = "birthformat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthdate(),"yyyy-MM-dd HH:mm:ss"))"),//自定义属性通过java代码映射
 })
public PersonVo PersonToPersonVo(Person person);

这里用到演示了如何使用TimeAndFormat对time和format操作,这里必须要指定需要使用的Java类的完整包名,不然编译的时候不知道你使用哪个Java类,会报错。

@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 user.setPwd("123456");
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 UserDto userDto = mapper.userToUserDto(user);
 System.out.println(userDto);
}

4.3 转换非基础类型属性

如果subUser与subUserDto字段名称相同直接配置即可完成(对象类型,包括list)

@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
private String password;
private List<SubUserDto> subUserDto;
}
@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private String pwd;
private List<SubUser> subUser;
}
@Mappings({
@Mapping(source = "pwd",target = "password"),
@Mapping(source = "subUser", target = "subUserDto")
})
UserDto userToUserDto(User user);

4.4 Mapper 中使用自定义的转换

有时候,对于某些类型,无法通过代码生成器的形式来进行处理。那么, 就需要自定义的方法来进行转换。这时候,我们可以在接口(同一个接口,后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
private String password;
private SubUserDto subUserDto;
}
@Data
publicclass SubUserDto {
private Boolean result;
private String name;
}
@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private String pwd;
private SubUser subUser;
}
@Data
publicclass SubUser {
private Integer deleted;
private String name;
}
@Mapper
publicinterface UserMapper {
@Mappings({
@Mapping(source = "pwd",target = "password"),
@Mapping(source = "subUser", target = "subUserDto")
 })
UserDto userToUserDto(User user);
default SubUserDto subSource2subTarget(SubUser subUser) {
if (subUser == ) {
return;
 }
 SubUserDto subUserDto = new SubUserDto();
 subUserDto.setResult(!subUser.getDeleted().equals(0));
 subUserDto.setName(subUser.getName()==?"":subUser.getName());
return subUserDto;
 }
}

只能存在一个default修饰的方法

@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 user.setPwd("123456");
 SubUser subUser =new SubUser();
 subUser.setDeleted(0);
 subUser.setName("rkw");
 user.setSubUser(subUser);
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 UserDto userDto = mapper.userToUserDto(user);
 System.out.println(userDto);
}

4.5 多转一

我们在实际的业务中少不了将多个对象转换成一个的场景。MapStruct 当然也支持多转一的操作。

@Data
publicclass SubUser {
private Integer deleted;
private String name;
}
@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private String pwd;
}
@Mapper
publicinterface UserMapper {
@Mappings({
@Mapping(source = "user.pwd",target = "password"),
@Mapping(source = "subUser.name", target = "name")
 })
NewUserDto userToUserDto(User user,SubUser subUser);
}
@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 user.setPwd("123456");
 SubUser subUser =new SubUser();
 subUser.setDeleted(0);
 subUser.setName("rkw");
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 NewUserDto userDto = mapper.userToUserDto(user,subUser);
 System.out.println(userDto);
}

4.5.1 遵循原则

  • 当多个对象中, 有其中一个为 , 则会直接返回
  • 如一对一转换一样, 属性通过名字来自动匹配。因此, 名称和类型相同的不需要进行特殊处理
  • 当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。如上面的 name

属性也可以直接从传入的参数来赋值

@Mapping(source = "person.description", target = "description")
@Mapping(source = "name", target = "name")
DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, String name);

4.6 更新 Bean 对象

有时候,我们不是想返回一个新的 Bean 对象,而是希望更新传入对象的一些属性。这个在实际的时候也会经常使用到。

@Mapper
publicinterface UserMapper {
NewUserDto userToNewUserDto(User user);
/**
 * 更新, 注意注解 @MappingTarget
 * 注解 @MappingTarget后面跟的对象会被更新。
 */
void updateDeliveryAddressFromAddress(SubUser subUser,@MappingTarget NewUserDto newUserDto);
}
@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 SubUser subUser =new SubUser();
 subUser.setDeleted(0);
 subUser.setName("rkw");
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 NewUserDto userDto = mapper.userToNewUserDto(user);
 mapper.updateDeliveryAddressFromAddress(subUser,userDto);
 System.out.println(userDto);
}

4.7 map映射

@MapMapping(valueDateFormat ="yyyy-MM-dd HH:mm:ss")
public Map<String ,String> DateMapToStringMap(Map<String,Date> sourceMap);
@Test
public void mapMappingTest(){
 Map<String,Date> map=new HashMap<>();
 map.put("key1",new Date());
 map.put("key2",new Date(new Date().getTime()+9800000));
 Map<String, String> stringObjectMap = TestMapper.MAPPER.DateMapToStringMap(map);
}

4.8 多级嵌套

只需要在mapper接口中定义相关的类型转换方法即可,list类型也适用

4.8.1 方式1

@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private Boolean isDisable;
private List<SubUser> user;
}
@Data
publicclass SubUser {
private Integer deleted;
private String name;
private List<SubSubUser> subUser;
}
@Data
publicclass SubSubUser {
private String aaa;
private String ccc;
}
@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
private String isDisable;
private List<SubUserDto> user;
}
@Data
publicclass SubUserDto {
private Integer deleted;
private String name;
private List<SubSubUserDto> subUser;
}
@Data
publicclass SubSubUserDto {
private String aaa;
private String bbb;
}
@Mapper
publicinterface UserMapper {
UserDto userToNewUserDto(User user);
//子集字段相同方法不用编写会自动生成
//孙子集字段不相同(list会自动读取此方法生成list)
@Mapping(source = "ccc",target = "bbb")
SubSubUserDto bbb(SubSubUser subSubUser);
}

4.8.2 方式2

通过uses配置类型转换

@Mapper(uses = {TestMapper.class})
public interface UserMapper {
UserDto userToNewUserDto(User user);
}
@Mapper
publicinterface TestMapper {
@Mapping(source = "ccc",target = "bbb")
SubSubUserDto bbb(SubSubUser subSubUser);
}

5、获取 mapper

5.1 通过 Mapper 工厂获取

我们都是通过 Mappers.getMapper(xxx.class) 的方式来进行对应 Mapper 的获取。此种方法为通过 Mapper 工厂获取。

如果是此种方法,约定俗成的是在接口内定义一个接口本身的实例 INSTANCE, 以方便获取对应的实例。

@Mapper
publicinterface SourceMapper {
 SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
// ......
}

这样在调用的时候,我们就不需要在重复的去实例化对象了。类似下面

Target target = SourceMapper.INSTANCE.source2target(source);

5.2 使用依赖注入

对于 Web 开发,依赖注入应该很熟悉。MapSturct 也支持使用依赖注入,同时也推荐使用依赖注入。

@Mapper(componentModel = "spring")

5.3 依赖注入策略

可以选择是通过构造方法或者属性注入,默认是属性注入。

publicenum InjectionStrategy {
/** Annotations are written on the field **/
 FIELD,
/** Annotations are written on the constructor **/
 CONSTRUCTOR
}

类似如此使用

@Mapper(componentModel = "cdi" injectionStrategy = InjectionStrategy.CONSTRUCTOR)

5.4 自定义类型转换

有时候,在对象转换的时候可能会出现这样一个问题,就是源对象中的类型是Boolean类型,而目标对象类型是String类型,这种情况可以通过@Mapper的uses属性来实现:

@Data
publicclass User {
private Integer id;
private String name;
private String address;
private Date birth;
private Boolean isDisable;
}
@Data
publicclass UserDto implements Serializable {
private Integer id;
private String name;
private String address;
private Date birth;
private String isDisable;
}
@Mapper(uses = {BooleanStrFormat.class})
public interface UserMapper {
UserDto userToNewUserDto(User user);
}
publicclass BooleanStrFormat {
public String toStr(Boolean isDisable) {
if (isDisable) {
return"Y";
 } else {
return"N";
 }
 }
public Boolean toBoolean(String str) {
if (str.equals("Y")) {
returntrue;
 } else {
returnfalse;
 }
 }
}

要注意的是,如果使用了例如像spring这样的环境,Mapper引入uses类实例的方式将是自动注入,那么这个类也应该纳入Spring容器

@Test
public void userPoToUserDto() {
 User user =new User();
 user.setId(1);
 user.setName("myx");
 user.setAddress("河北沧州");
 user.setBirth(new Date());
 user.setIsDisable(true);
 SubUser subUser =new SubUser();
 subUser.setDeleted(0);
 subUser.setName("rkw");
 UserMapper mapper = Mappers.getMapper(UserMapper.class);
 UserDto userDto = mapper.userToNewUserDto(user);
 System.out.println(userDto);
}

以上就是Java MapStruct优雅地实现对象转换的详细内容,更多关于Java MapStruct的资料请关注脚本之家其它相关文章!

相关文章

  • Springboot内嵌tomcat应用原理深入分析

    Springboot内嵌tomcat应用原理深入分析

    懂得SpringBoot的童鞋应该很清楚,不管应用程序是属于何种类型,都是一个Main方法走遍天下,对于web应用,只需要引入spring-boot-starter-web中这个依赖,应用程序就好像直接给我们来了个tomcat一样,对于嵌入式Tomcat,其实也非常简单,就是调用Tomcat提供的外部类
    2022-09-09
  • 如何让java只根据数据库表名自动生成实体类

    如何让java只根据数据库表名自动生成实体类

    今天给大家带来的知识是关于Java的,文章围绕着如何让java只根据数据库表名自动生成实体类展开,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • Spring Security实现身份认证和授权的示例代码

    Spring Security实现身份认证和授权的示例代码

    在 Spring Boot 应用中使用 Spring Security 可以非常方便地实现用户身份认证和授权,本文主要介绍了Spring Security实现身份认证和授权的示例代码,感兴趣的可以了解一下
    2023-06-06
  • Java Api实现Elasticsearch的滚动查询功能

    Java Api实现Elasticsearch的滚动查询功能

    这篇文章主要介绍了Java Api实现Elasticsearch的滚动查询,解决ES每次只能查询一万条数据的问题,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • Maven配置仓库的方法步骤

    Maven配置仓库的方法步骤

    本文主要介绍了Maven配置仓库的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java并发编程之创建线程

    Java并发编程之创建线程

    这篇文章主要介绍了Java并发编程中创建线程的方法,Java中如何创建线程,让线程去执行一个子任务,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Java咖啡馆(1)——叹咖啡

    Java咖啡馆(1)——叹咖啡

    这篇文章主要给大家介绍了关于Java咖啡馆之叹咖啡,需要的朋友可以参考下
    2006-12-12
  • SpringBoot实现邮件推送的详细代码

    SpringBoot实现邮件推送的详细代码

    在项目中经常会遇到SpringBoot推送消息的业务,除了站内推送通知,邮件推送也是一种常见的方式,本文小编就带大家实现邮件推送,文中有详细代码讲解,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-04-04
  • 用java实现的获取优酷等视频缩略图的实现代码

    用java实现的获取优酷等视频缩略图的实现代码

    想获取优酷等视频缩略图,在网上没有找到满意的资料,参考了huangdijia的PHP版工具一些思路,写了下面的JAVA版代码。。其实也可以做成JS版的
    2013-05-05
  • mybatis collection关联查询多个参数方式

    mybatis collection关联查询多个参数方式

    在使用MyBatis进行关联查询时,往往需要根据多个参数进行查询,例如,使用evtId和businessType作为查询条件,同时在resultMap中配置id和businessType1作为结果映射,这种情况下,可以通过<sql>标签定义参数模板,或者使用@Param注解指定参数名称
    2024-10-10

最新评论