MapStruct入门

110 阅读4分钟

简介

mapstruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。

官网:mapstruct.org/

maven引入

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
​
    <dependencies>
​
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
​
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.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>
                        <!-- Lombok插件 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <!-- MapStruct插件 -->
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

代码示例

先定义我们想要转换的实体类,假设我们想实现将User类转换为People类。People中大部分属性和User中相同,差异的部分为:性别的枚举类Sex和Gender、num和count(一个是包装类Integer,一个是int)、People中多出了一个updateTime字段

@Data
public class People {
​
    private Long id;
​
    private String name;
​
    private Sex sex;
​
    private int num;
​
    private String desc;
​
    private String createTime;
​
    private LocalDateTime updateTime;
​
    private Car car;
}
​
@Data
public class User {
​
    private Long id;
​
    private String name;
​
    private Gender gender;
​
    private Integer count;
​
    private String desc;
​
    private Car car;
​
    private LocalDateTime createTime;
​
}
​
@Data
public class Car {
​
    private String name;
​
    private LocalDateTime createTime;
}
​
public enum Gender {
​
    male("male", "男"),
    female("female", "女"),
    unknown("unknown", "未知");
}    
​
public enum Sex {
​
    man("man", "男"),
    woman("woman", "女"),
    unknown("unknown", "未知");
}    
// 定义一个MapStruct接口
@Mapper(builder = @Builder(disableBuilder = true))
public interface MapStruct {
​
    MapStruct INSTANCE = Mappers.getMapper(MapStruct.class);
​
    /**
     * 枚举类到枚举类映射
     */
    @ValueMappings({
            @ValueMapping(target = "man", source = "male"),
            @ValueMapping(target = "woman", source = "female"),
            @ValueMapping(target = "unknown", source = "unknown")
    })
    Sex genderToSex(Gender gender);
​
     /**
     * user实体转成people实体
     */
    @Mapping(target = "num", source = "count")
    @Mapping(target = "sex", source = "gender")
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "desc", defaultValue = "特殊说明")
    @Mapping(target = "updateTime", expression = "java(LocalDateTime.now())")
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    People toPeople(User user);
​
     /**
     * map转成people实体,map需指定类型
     */
    @Mapping(target = "car", ignore = true)
    People toPeople(Map<String, String> userMap);
​
     /**
     * 集合转集合
     */
    List<People> toPeoples(List<User> users);
​
}
​

使用

定义好上面的mapstruct接口后,想实现实体类到实体类的转换就非常简单了

public static void main(String[] args) {  
  
    Car car = new Car();  
    car.setName("BYD");  
    car.setCreateTime(LocalDateTime.now().minusYears(5));  

    User user = new User();  
    user.setId(93002614785301L);  
    user.setName("Han Si");  
    user.setCount(15);  
    user.setCreateTime(LocalDateTime.now().minusMonths(36));  
    user.setCar(car);  
    user.setGender(Gender.male);  

    People people = MapStruct.INSTANCE.toPeople(user);  
    System.out.println("user to people : " + people);  
    // 输出 user to people : People(id=null, name=Han Si, sex=man, num=15, desc=特殊说明, createTime=2020-12-05 09:47:44, updateTime=2023-12-05T09:47:44.280, car=Car(name=BYD, createTime=2018-12-05T09:47:44.234))


    Map<String, String> map = new HashMap<>();  
    map.put("id", "93002614785301");  
    map.put("name", "Han Si");  
    map.put("num", "15");  
    map.put("desc", "desc123");  
    map.put("createTime", "2021-10-12T19:15:30");  
    people = MapStruct.INSTANCE.toPeople(map);  
    System.out.println("map to people : " + people);  
    // 输出 map to people : People(id=93002614785301, name=Han Si, sex=null, num=15, desc=desc123, createTime=2021-10-12T19:15:30, updateTime=null, car=null)

}

通过@Mapping注解我们可以非常简单的定义从源字段到目标字段的转换

@Mapping 注解

  • target用于标注目标实体类中的具体字段名,source表示源实体类中的具体字段名
  • @Mapping(target = "id", ignore = true) :两个实体类中有相同的id属性,但是不想转到目标实体,可以使用ignore = true
  • @Mapping(target = "num", source = "count") :两个实体类中有相同含义的字段,但是字段不同,可以告诉编译器目标实体类的字段名是什么
  • @Mapping(target = "desc", defaultValue = "特殊说明") :defaultValue 可以定义默认值,如果给的源实体中该字段为空
  • @Mapping(target = "updateTime", expression = "java(LocalDateTime.now()) ") :expression可以使用Java代码,用于生成具体的值,需要指定类的全路径或者在类上使用imports导入 eg : @Mapper(builder = @Builder(disableBuilder = true), imports = {AxxStatusEnum.class, BxxStatusEnum.class}) 。 类似的还有defaultExpression
  • @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") : dateFormat可以用于时间格式化,默认使用的是SimpleDateFormat

@ValueMappings 注解

可以实现enum到enum的转换

@ValueMappings({ @ValueMapping(target = "man", source = "male"), @ValueMapping(target = "woman", source = "female"), @ValueMapping(target = "unknown", source = "unknown") }) Sex genderToSex(Gender gender);

然后使用 @Mapping(target = "sex", source = "gender")映射

@SubclassMapping

 @SubclassMapping( source = BananaDto.class, target = Banana.class )
 Fruit map( FruitDto source );

其实定义好上面的mapstruct接口后,编译过后,在target/classes下会自动生成如下代码,这种自动编译的,会比使用反射实现的实体类转换工具类性能更好。

// 编译器自动生成
@Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2023-11-10T15:23:35+0800",
        comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_371 (Oracle Corporation)"
)
public class MapStructImpl implements MapStruct {
​
    private final DateTimeFormatter dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
​
    @Override
    public Sex genderToSex(Gender gender) {
        if (gender == null) {
            return null;
        }
​
        Sex sex;
​
        switch (gender) {
            case male:
                sex = Sex.man;
                break;
            case female:
                sex = Sex.woman;
                break;
            case unknown:
                sex = Sex.unknown;
                break;
            default:
                throw new IllegalArgumentException("Unexpected enum constant: " + gender);
        }
​
        return sex;
    }
​
    @Override
    public People toPeople(User user) {
        if (user == null) {
            return null;
        }
​
        People people = new People();
​
        if (user.getCount() != null) {
            people.setNum(user.getCount());
        }
        people.setSex(genderToSex(user.getGender()));
        if (user.getDesc() != null) {
            people.setDesc(user.getDesc());
        } else {
            people.setDesc("特殊说明");
        }
        if (user.getCreateTime() != null) {
            people.setCreateTime(dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168.format(user.getCreateTime()));
        }
        people.setName(user.getName());
        people.setCar(user.getCar());
​
        people.setUpdateTime(LocalDateTime.now());
​
        return people;
    }
​
    @Override
    public People toPeople(Map<String, String> userMap) {
        if (userMap == null) {
            return null;
        }
​
        People people = new People();
​
        if (userMap.containsKey("id")) {
            people.setId(Long.parseLong(userMap.get("id")));
        }
        if (userMap.containsKey("name")) {
            people.setName(userMap.get("name"));
        }
        if (userMap.containsKey("sex")) {
            people.setSex(Enum.valueOf(Sex.class, userMap.get("sex")));
        }
        if (userMap.containsKey("num")) {
            people.setNum(Integer.parseInt(userMap.get("num")));
        }
        if (userMap.containsKey("desc")) {
            people.setDesc(userMap.get("desc"));
        }
        if (userMap.containsKey("createTime")) {
            people.setCreateTime(userMap.get("createTime"));
        }
        if (userMap.containsKey("updateTime")) {
            people.setUpdateTime(LocalDateTime.parse(userMap.get("updateTime")));
        }
​
        return people;
    }
​
    @Override
    public List<People> toPeoples(List<User> users) {
        if (users == null) {
            return null;
        }
​
        List<People> list = new ArrayList<People>(users.size());
        for (User user : users) {
            list.add(toPeople(user));
        }
​
        return list;
    }
}