顶级工具,性能爆棚的 Java 实体转换 / 复制神器

Java技术迷

共 15242字,需浏览 31分钟

 · 2023-11-06

点击关注公众号,Java干货及时送达

      来源:blog.csdn.net/qq_30231473/article/details/129782881
  • 1 优点
  • 2 性能对比
  • 3 使用
    • 依赖
    • 定义转换接口
    • 编译结果
    • 调用
  • 4 插件
    • 特性
  • 5 其他用法
    • 基础映射
    • 映射器添加自定义方法
    • 多个源参数映射
    • 嵌套属性映射到当前目标
    • 表达式方式
    • 更新现有实例
    • Map映射
    • 更多用法
  • 6 总结

Java项目中实体转换无处不在,当实体字段较多或者大批量的进行复制时,通过手工setter/getter显得太LOW,同时兼备高性能要求情况下,MapStruct完全完全能够胜任。

官方解释,MapStruct是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。因为MapStruct是在编译期间生成setter/getter方法,实际运行时就是直接调用setter/getter,效率会非常高。

1 优点

  • MapStruct编译期生成映射代码,所以可以在编译时暴露映射错误的代码,让错误提前暴露;
  • 因为使用setter/getter方式,而非反射方式,所以可以更快的执行效率;
  • 可以实现深拷贝,自动类型转换,如枚举转换;
  • 进行自定义的映射,多种映射方式,下边具体说明;

2 性能对比

对比对象 10个对象复制1次 1万个对象复制1次 100万个对象复制1次 100万个对象复制5次
MapStruct 0ms 3ms 96ms 281ms
Hutools的BeanUtil 23ms 102ms 1734ms 8316ms
Spring的BeanUtils 2ms 47ms 726ms 3676ms
Apache的BeanUtils 20ms 156ms 10658ms 52355ms
Apache的PropertyUtils 5ms 68ms 6767ms 30694ms

3 使用

依赖

<!-- MapStruct核心,包含了一些必要的注解-->
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
<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>
                   <!-- MapStruct编译,注解处理器,根据注解自动生成Mapper的实现 -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

定义转换接口

/**
 * 测试接口
 *
 * @author reboot
 */
@Mapper
public interface OrderConvertor {
 
    /**
     * 实例
     */
    OrderConvertor INSTANCE = Mappers.getMapper(OrderConvertor.class);
 
    /**
     * OrderDo -> OrderModel
     *
     * @param orderDo 订单实体
     * @return {@link OrderModel}
     */
    OrderModel toModel(OrderDo orderDo);
 
    /**
     * OrderDo -> OrderModel
     *
     * @param orderDos 订单实体
     * @return {@link OrderModel}
     */
    List<OrderModel> toModel(List<OrderDo> orderDos);
 
    /**
     * OrderModel -> OrderDo
     *
     * @param orderModel 订单模型
     * @return {@link OrderDo}
     */
    OrderDo toDo(OrderModel orderModel);
 
    /**
     * OrderModel -> OrderDo
     *
     * @param orderModels 订单模型
     * @return {@link OrderDo}
     */
    List<OrderDo> toDo(List<OrderModel> orderModels);
}

编译结果

图片

MapStruct会自动生成对应接口的实现,并自动完成属性映射关系,List会自动进行批量处理。

调用

/**
 * 订单服务
 *
 * @author reboot
 */
@Service
public class OrderService {
 
    /**
     * 获取订单列表
     *
     * @return {@link List}<{@link OrderModel}>
     */
    public List<OrderModel> getOrderList() {
        // 获取数据库数据DO
        List<OrderDo> result = selectOrderList();
        // 参数转换
        return OrderConvertor.INSTANCE.toModel(result);
    }
}

4 插件

图片

上边的使用方式虽然能够正常使用,但是在一些属性配置映射上和提示上,如果使用插件能够提升使用体验,IDEA中可以直接安装Mapstruct Support插件,当然Eclipse也有对应的插件。

特性

  • 突出显示目标属性和源属性。将目标属性和源属性转到声明的setter / getter中;

  • 错误和快速修复:


    • 缺少@Mapper或@MapperConfig注解检查;
    • 快速修复未映射的目标属性,添加未映射目标属性和忽略未映射目标属性;

图片

5 其他用法

更加详细的内容可以查看官方文档,发布文章时最新版本是 MapStruct 1.5.3.Final.html。

基础映射

@Mapper
public interface CarMapper {
 
    @Mapping(target = "manufacturer"source = "make")
    @Mapping(target = "seatCount"source = "numberOfSeats")
    CarDto carToCarDto(Car car);
 
    @Mapping(target = "fullName"source = "name")
    PersonDto personToPersonDto(Person person);
}

target表示目标属性名,source表示源属性名,一般在目标属性和源属性不同时使用,相同的属性名会自动进行映射。

映射器添加自定义方法

@Mapper
public interface CarMapper {
 
    @Mapping(...)
    ...
    CarDto carToCarDto(Car car);
 
    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

自定义方法personToPersonDto并实现,在生成的实现类中会进行覆盖使用。

多个源参数映射

@Mapper
public interface AddressMapper {
 
    @Mapping(target = "description"source = "person.description")
    @Mapping(target = "houseNumber"source = "address.houseNo")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
 
    @Mapping(target = "description"source = "person.description")
    @Mapping(target = "houseNumber"source = "hn")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

存在多个源参数,使用参数名.属性名的方式进行表示,也可以直接使用基础类型的属性名称。

嵌套属性映射到当前目标

@Mapper
 public interface CustomerMapper {
 
     @Mapping( target = "name"source = "record.name" )
     @Mapping( target = "."source = "record" )
     @Mapping( target = "."source = "account" )
     Customer customerDtoToCustomer(CustomerDto customerDto);
 }

当源参数中存在对象属性,可以手动进行映射,或者直接使用"."的方式将对象中的属性全部映射到当前目标对象。

表达式方式

@Mapper
public interface SourceTargetMapper {
 
    SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
 
    @Mapping(
        target = "timeAndFormat",
        expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )"
    )
    Target sourceToTarget(Source s);
}

支持使用java代码块进行转换,一般可以将静态方法处理的字段放到这里。

更新现有实例

@Mapper
public interface CarMapper {
 
    void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

@MappingTarget源参数,编译时会将carDto参数中的属性映射到car参数中。

Map映射

@Mapper
public interface CustomerMapper {
 
    @Mapping(target = "name"source = "customerName")
    Customer toCustomer(Map<String, String> map);
}

直接将map中的key进行映射。

更多用法

还有更多其他用法,比如:

  • 支持映射定义的public属性;
  • 支持映射参数Builder模式;
  • 使用注入方式引入转换器;
  • 数据类型字段转换,如枚举、日期,支持日期格式化,支持数字类型格式化
  • 集合类型自动转换;
  • 转换Stream;

6 总结

MapStruct还有很多其他高阶特性,限于篇幅文章仅仅列举部分示例,有兴趣的同学可以查看对应文档试试。使用适当的工具有效提高编程效率,在使用工具过程中我们也了解其实现原理,不断提高自身。

     
     

  

        
        

         
         

1、如何搭建一个拖垮公司的技术架构?【文末送书】

2、原来,这才是 JDK 推荐的线程关闭方式

3、MySQL到底是 join 性能好,还是in一下更快呢?

4、Next.js支持在前端代码中写SQL,开倒车还是遥遥领先?

5、相比高人气的Rust、Go,为何 Java、C 在工具层面进展缓慢?

6、让程序员早点下班的《技术写作指南》

点在看

浏览 959
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报