学习笔记 - MapStruct 映射工具
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记 - MapStruct 映射工具相关的知识,希望对你有一定的参考价值。
学习笔记 - MapStruct 映射工具
简介
-
MapStruct是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。
生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。 -
多层应用程序通常需要在不同对象模型(例如实体和dto)之间进行映射。编写这样的映射代码是一项乏味且容易出错的任务。MapStruct的目标是通过尽可能地自动化来简化这项工作。
与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。 -
MapStruct是一个插入到Java编译器中的注解处理器,可以在命令行构建(Maven, Gradle等)中使用,也可以在您首选的IDE中使用。
MapStruct使用合理的默认值,但当涉及到配置或实现特殊行为时,它也不会妨碍你。
Maven 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jerry</groupId>
<artifactId>mapstruct-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.20</org.projectlombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>$org.mapstruct.version</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>$org.projectlombok.version</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.13.1</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<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>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>$org.mapstruct.version</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>$org.projectlombok.version</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>$lombok-mapstruct-binding.version</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
IDEA 插件
MapStruct Support
安装插件后,将光标放这里,按智能补全
热键 Alt + /
可以列出所有字段
供选择。
我这里用的是 Alt + /
我自定义过。因为默认的 Ctrl + 空格
跟我们切输入法冲突了。
@Mapper
实体类 Entity
@lombok.Data
@lombok.AllArgsConstructor
public class Hero
private Integer id;
private String name;
private Integer gender;
private java.time.LocalDate createDate;
private String createBy;
数据传输对象 DTO
@lombok.Data
public class HeroDTO
private String name;
private Integer sex;
映射接口
package com.jerry;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface HeroMapper
HeroMapper INSTANCE = Mappers.getMapper( HeroMapper.class );
@Mapping(source ="gender", target = "sex")
HeroDTO hero2HeroDTO(Hero hero);
测试类
package com.jerry;
import org.junit.Test;
import java.util.Date;
public class HeroMapperTest
@Test
public void hero2HeroDTO()
Hero hero = new Hero(111, "笑虾", 1, new Date("Thu Dec 22 00:00:00 CST 2022"), "刘大");
HeroDTO heroDto = HeroMapper.INSTANCE.hero2HeroDTO( hero );
System.out.println(hero);
System.out.println(heroDto);
- 输出
Hero(id=111, name=笑虾, gender=1, createDate=2022-12-21, createBy=刘大)
HeroDTO(name=笑虾, sex=1)
@Mapping 作为元注解,实现重用
我们用 @Mapping
作为元注解,自定义一个注解 @CommonMapping
来配置一些需要复用的映射关系。
实体类 Entity
@Data
@AllArgsConstructor
public class HeroA
private Integer id;
private String name;
private Integer gender;
private java.time.LocalDate createDate;
private String createBy;
private String a; // 独有字段(上面几个和 Hero 一样)
@Data
@AllArgsConstructor
public class HeroB
private Integer id;
private String name;
private Integer gender;
private java.time.LocalDate createDate;
private String createBy;
private String b; // 独有字段(上面几个和 Hero 一样)
数据传输对象 DTO
@Data
public class HeroADTO
private Integer id;
private String name;
private Integer sex; // 名字不同的字段
private java.time.LocalDate createDate;
private String user;
private String tagA; // 名字不同的字段
@Data
public class HeroBDTO
private Integer id;
private String name;
private Integer sex; // 名字不同的字段
private java.time.LocalDate createDate;
private String user;
private String tagB; // 名字不同的字段
自定义注解
package com.jerry;
import org.mapstruct.Mapping;
// 忽略
@Mapping(target = "id", ignore = true)
// 用 LocalDate.now() 来填充目标
@Mapping(target = "createDate", expression = "java(java.time.LocalDate.now())")
// user 映射到 createBy
@Mapping(source = "user", target = "createBy")
public @interface CommonMapping
映射接口
@Mapper
public interface HeroMapper
@CommonMapping
@Mapping(source ="a", target = "tagA")
HeroADTO a2DTO(HeroA hero);
@CommonMapping
@Mapping(source ="b", target = "tagB")
HeroBDTO b2DTO(HeroB hero);
测试类
@Test
public void h2DTOTest()
HeroA heroa = new HeroA(888, "格尼", 1, LocalDate.now().plusDays(1), "关二", "AAA");
HeroADTO heroADTO = HeroMapper.INSTANCE.a2DTO(heroa);
System.out.println(heroa);
System.out.println(heroADTO);
HeroB herob = new HeroB(999, "薇儿", 0, LocalDate.now().minusDays(1), "张三", "BBB");
HeroBDTO heroBDTO = HeroMapper.INSTANCE.b2DTO(herob);
System.out.println(herob);
System.out.println(heroBDTO);
- 输出
HeroA(id=888, name=格尼, gender=1, createDate=2022-12-23, createBy=关二, a=AAA)
HeroADTO(id=null, name=格尼, sex=1, createDate=2022-12-22, user=关二, tagA=AAA)
HeroB(id=999, name=薇儿, gender=0, createDate=2022-12-21, createBy=张三, b=BBB)
HeroBDTO(id=null, name=薇儿, sex=0, createDate=2022-12-22, user=张三, tagB=BBB)
自定义映射方法
当一些特殊的需求无法由 MapStruct 生成时,可以自定义映射方法。
比如从 JDK8 开始,我们可以在接口中声明默认方法来实现:
接口
@Mapper
public interface HeroMapper
default HeroDTO hero2DtoCustom(Hero hero)
HeroDTO heroDTO = new HeroDTO();
heroDTO.setName("【"+ hero.getName() +"】");
heroDTO.setSex(hero.getGender());
return heroDTO;
@Test
public void hero2DtoCustomTest()
Hero hero = new Hero(111, "笑虾", 1, LocalDate.now().minusDays(1), "刘大");
HeroDTO heroDto = HeroMapper.INSTANCE.hero2DtoCustom( hero );
System.out.println(heroDto);
HeroDTO(name=【笑虾】, sex=1)
抽象类
除了接口外,也可以用抽象类来定义。比如想声明额外的字段。
@Mapper
public abstract class HeroMapper
public HeroDTO hero2DtoCustom(Hero hero)
...
多个源
多个象
@Mapper
public interface HeroMapper
@Mapping(target = "name", source = "heroa.name")
@Mapping(target = "sex", source = "herob.gender")
HeroDTO Heroab2Dto (HeroA heroa, HeroB herob);
基础类型
@Mapper
public interface HeroMapper
@Mapping(target = "name", source = "heroa.name")
@Mapping(target = "sex", source = "gender")
HeroDTO Heroab2Dto (HeroA heroa, Integer gender);
更多
1. 支持嵌套关系映射
@Mapping(target = "name", source = "hero.name")
HeroDTO hero2Dto(Hero hero);
2. 映射到当前
支持将嵌套bean属性
映射到当前
目标
@Mapper
public interface HeroMapper
@Mapping( target = "属性x", source = "属性a.子属性a" )
@Mapping( target = ".", source = "属性a" )
@Mapping( target = ".", source = "属性b" )
HeroDTO hero2Dto(Hero hero);
3. 更新现有目标
映射结果是更新现有目标,而非创建新实例。
@Mapper
public interface HeroMapper
// 用 hero 更新 heroDTO 的内容
void updateHero2Dto(Hero hero , @MappingTarget HeroDTO heroDTO);
4. 声明反向转换的方法
想声明反向转换的方法,只需一个注解搞定
@CommonMapping
@Mapping(source ="a", target = "tagA")
HeroADTO hero2Dto(HeroA hero);
@InheritInverseConfiguration
HeroA dto2Hero(HeroADTO heroADTO);
6. Map 转 Bean
Map 转 Bean 也是支持的
@Mapper
public interface HeroMapper
@Mapping(source ="性别", target = "sex") // 和前面一样,名字相同的不用管,只需要告诉它名字不同的
Hero map2Bean(Map<String, String> map);
7. 类型转换
格式化数字
@Mapper
public interface CarMapper
@Mapping(source = "price", numberFormat = "$#.00")
CarDto carToCarDto(Car car);
@IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
格式化日期
@Mapper
public interface CarMapper
@Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
CarDto carToCarDto(Car car);
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> datesMapStruct 映射工具