对象转换神器 MapStruct

Mapstruct

一、基本使用

1、Maven 引入

<properties>
    <lombok.version>1.18.30</lombok.version>
    <mapstruct.version>1.4.1.Final</mapstruct.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>

    <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>
    </dependency>
</dependencies>

2、基本使用

待转换的类

@Data
@Builder
public class UserDTO {
    private Long id;
    private Integer age;
    private String name;
}

转换目标类

@Data
public class UserVO {
    private Long id;
    private Integer age;
    private String name;
}

转换器

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface Converter {
    // 后续通过 INSTANT 调用转换方法
    Converter INSTANT = Mappers.getMapper(Converter.class);

    UserVO convert(UserDTO userDTO);
}

使用示例

public class Test {
    public static void main(String[] args) {
        UserDTO userDTO = UserDTO.builder().id(1L).age(18).name("scj").build();
        UserVO userVO = Converter.INSTANT.convert(userDTO);
        System.out.println(userVO);
    }
}

输出结果

UserVO(id=1, age=18, name=scj)

可以看到在 target 生成 转换器的实现类,并使用 get 和 set 进行转换,所以性能很高。

在这里插入图片描述

3、Spring Bean

MapStruct提供了依赖注入的机制,让我们能够在Spring的环境下,更优雅的获得Converter。

转换器

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface Converter {
    UserVO convert(UserDTO userDTO);
}

使用示例

@RestController
public class TestController {
    
    // 直接注入使用
    @Autowired
    private Converter converter;

    @GetMapping("/test")
    public void mapstructTest() {
        UserDTO userDTO = UserDTO.builder().id(1L).age(18).name("scj").build();
        UserVO userVO = converter.convert(userDTO);
        System.out.println(userVO);
    }
}

输出结果

UserVO(id=1, age=18, name=scj)

可以看到 target 中生成的实现类交给 Spring容器 管理了

在这里插入图片描述

二、类型一致的场景

1、字段名不一致

字段名不同,通过 targetsource 属性指定映射关系(字段名一致可以省略)

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    @Mapping(target = "userName", source = "name")
    UserVO convert(UserDTO userDTO);
}

输出结果

UserVO(id=1, age=18, username=scj)

2、ignore

不需要映射的字段,可以设置 ignore = true

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    // 不需要映射的字段,指定 ignore = true
    @Mapping(target = "name", ignore = true)
    @Mapping(target = "age", ignore = true)
    UserVO convert(UserDTO userDTO);
}

输出结果

UserVO(id=1, age=null, name=null)

3、defaultValue

通过 defaultValue 设置默认值(仅当源对象的该属性值为 null 时)

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    // 只有为 null 的才会生效
    @Mapping(target = "id", defaultValue = "1")
    @Mapping(target = "age", defaultValue = "66")
    @Mapping(target = "name", defaultValue = "zs")
    UserVO convert(UserDTO userDTO);
}

使用示例

public class Test {
    public static void main(String[] args) {
        // id = null
        UserDTO userDTO = UserDTO.builder().age(18).name("scj").build();
        UserVO userVO = Converter.INSTANT.convert(userDTO);
        System.out.println(userVO);
    }
}

输出结果

UserVO(id=1, age=18, name=scj)

4、constant

通过 constant 为属性设置常量值,无论源对象的该属性值是否为 null

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    // 只有为 null 的才会生效
    @Mapping(target = "id", constant = "6")
    @Mapping(target = "age", constant = "66")
    @Mapping(target = "name", constant = "zs")
    UserVO convert(UserDTO userDTO);
}

使用示例

public class Test {
    public static void main(String[] args) {
        UserDTO userDTO = UserDTO.builder().id(1).age(18).name("scj").build();
        UserVO userVO = Converter.INSTANT.convert(userDTO);
        System.out.println(userVO);
    }
}

输出结果

UserVO(id=6, age=66, name=zs)

三、类型不一致的场景

1、默认转换

对于部分数据类型,如果属性类型不一致,会做默认的的转换并赋值。

包装类 相关:基本数据类型、字符串、其他包装类

// Integer -> int
target.setIntegerValue( source.getIntegerValue() );

// Integer -> Long
target.setIntegerValue( source.getIntegerValue().longValue() );

// Integer -> Double
target.setIntegerValue( source.getIntegerValue().doubleValue() );

// Integer -> String
target.setIntegerValue( String.valueOf( source.getIntegerValue() ) );

BigDecimal 相关:

// BigDecimal -> Integer/int
target.setDecimalValue( source.getDecimalValue().intValue() );

// BigDecimal -> Double/double
target.setDecimalValue( source.getDecimalValue().doubleValue() );

// BigDecimal -> String
target.setDecimalValue( source.getDecimalValue().toString() );

时间日期 相关

// Date -> LocalDateTime
target.setDateValue( LocalDateTime.ofInstant( source.getDateValue().toInstant(), ZoneId.of( "UTC" ) ) );

// LocalDateTime -> Date
target.setDateValue( Date.from( source.getDateValue().toInstant( ZoneOffset.UTC ) ) );

// Date -> String
target.setDateValue( new SimpleDateFormat().format( source.getDateValue() ) );

// LocalDateTime -> String
target.setDateValue( DateTimeFormatter.ISO_LOCAL_DATE_TIME.format( source.getDateValue() ) );

枚举 相关

// Enum -> String
target.setEnumValue( source.getEnumValue().name() );

String 相关

// String -> Long/long
target.setStringValue( Long.parseLong( source.getStringValue() ) );

// String -> BigDecimal
target.setStringValue( new BigDecimal( source.getStringValue() ) );

// String -> Date
target.setStringValue( new SimpleDateFormat().parse( source.getStringValue() ) );

// String -> LocalDateTime
target.setStringValue( LocalDateTime.parse( source.getStringValue() ) );

// String -> Enum
target.setEnumValue( Enum.valueOf( Source.CustomEnum.class, source.getEnumValue() ) );

2、numberFormat

数字类型 转换为 字符串 时,可以通过 numberFormat 属性指定 格式

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    @Mapping(target = "integerValue", source = "integerValue", numberFormat = "#,##0")
    @Mapping(target = "doubleValue", source = "doubleValue", numberFormat = "#0.00")
    @Mapping(target = "decimalValue", source = "decimalValue", numberFormat = "#%")
    Target convert(Source source);
}
  • #,##0:表示数字使用千位分隔符。例如,1234567 将被格式化为 1,234,567
  • #0.00:表示数字保留两位小数。例如,666.666 将被格式化为 666.67
  • #%:表示将数字视为百分比。例如,0.75 将被格式化为 75%

3、dateFormat

时间日期 转换为 字符串 时,可以通过 dateFormat 属性指定 pattern

待转换的类

@Data
public class Source {
    private Date dateValue = new Date();
    private LocalDateTime localDateTimeValue = LocalDateTime.now();
}

转换目标类

@Data
public class Target {
    private String dateValue;
    private String localDateTimeValue;
}

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    @Mapping(target = "dateValue", source = "dateValue", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(target = "localDateTimeValue", source = "localDateTimeValue", dateFormat = "yyyy-MM-dd HH:mm:ss")
    Target convert(Source source);
}

输出结果

Target(dateValue=2024-09-26 19:27:41, localDateTimeValue=2024-09-26 19:27:41)

4、枚举值处理

待转换的类

@Data
public class Source {
    private SexEnum sexEnum = SexEnum.MAN;
}

转换目标类

@Data
public class Target {
    private String sex;
}

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    @Mapping(target = "sex", source = "sexEnum.desc")
    Target convert(Source source);
}

四、其他场景

1、对象嵌套(字段一致)

如果字段完全一致,会自动生成嵌套对象的 convert 方法。

待转换的类

@Data
public class Source {
    private Long id = 1L;
    private SourceInnerClass1 inner = new SourceInnerClass1();
}

@Data
class SourceInnerClass1 {
    private Long id = 11L;
    private List<SourceInnerClass2> innerList = Collections.singletonList(new SourceInnerClass2());
}

@Data
class SourceInnerClass2 {
    private Long id = 111L;
    private String value = "inner2";
}

转换目标类

@Data
public class Target {
    private Long id;
    private TargetInnerClass1 inner;
}

@Data
class TargetInnerClass1 {
    private Long id;
    private List<TargetInnerClass2> innerList;
}

@Data
class TargetInnerClass2 {
    private Long id;
    private String value;
}

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    Target convert(Source source);
}

输出

Target(id=1, inner=TargetInnerClass1(id=11, innerList=[TargetInnerClass2(id=111, value=inner2)]))

可以看到,所有嵌套对象的属性都做了自动映射

在这里插入图片描述

在这里插入图片描述

2、对象嵌套(字段不一致)

如果字段不一致,需要自己编写对应嵌套对象的 convert 方法,底层会自动调用。

待转换的类

@Data
public class Source {
    private Long id;
    private SourceInnerClass1 inner;
}

@Data
class SourceInnerClass1 {
    private Long id;
    private List<SourceInnerClass2> innerList;
}

@Data
class SourceInnerClass2 {
    private Long id;
    private String value;
}

转换目标类

@Data
public class Target {
    private Long id0;
    private TargetInnerClass1 inner0;
}

@Data
class TargetInnerClass1 {
    private Long id1;
    private List<TargetInnerClass2> innerList1;
}

@Data
class TargetInnerClass2 {
    private Long id2;
    private String value2;
}

转换器

@Mapper
public interface Converter {
    Converter INSTANT = Mappers.getMapper(Converter.class);

    @Mapping(target = "id0", source = "id")
    @Mapping(target = "inner0", source = "inner")
    Target convert(Source source);

    @Mapping(target = "id1", source = "id")
    @Mapping(target = "innerList1", source = "innerList")
    TargetInnerClass1 convert(SourceInnerClass1 source);

    @Mapping(target = "id2", source = "id")
    @Mapping(target = "value2", source = "value")
    TargetInnerClass2 convert(SourceInnerClass2 source);
}

使用示例

public class Test {
    public static void main(String[] args) {
        final Source source = new Source(1L, 18L, "scj", new SourceInner(1L, 18));
        final Target target = Converter.INSTANT.convert(source);
        System.out.println(target);
    }
}

输出

Target(id0=1, inner0=TargetInnerClass1(id1=11, innerList1=[TargetInnerClass2(id2=111, value2=inner2)]))

会按需调用对应对象的 convert 方法

在这里插入图片描述

3、自定义转换方法

使用 @Named 自定义转换方法,通过 qualifiedByName 属性指定,要和 @Namedvalue 对应

待转换的类

@Data
public class Target {
    private UserDTO targetUserDTO;
    private String targetUserJson;
}

转换目标类

@Data
public class Source {
    private String sourceUserJson;
    private UserDTO sourceUserDTO;
}

转换器

@Mapper
public interface Convert {

    Convert INSTANCE = Mappers.getMapper(Convert.class);

    @Mapping(target = "targetUserDTO", source = "sourceUserJson", qualifiedByName = "json2DTO")
    @Mapping(target = "targetUserJson", source = "sourceUserDTO", qualifiedByName = "dto2Json")
    Target convert(Source source);

    @Named("json2DTO")
    default UserDTO json2DTO(String userJson) {
        return JSON.parseObject(userJson, UserDTO.class);
    }

    @Named("dto2Json")
    default String dto2Json(UserDTO userDTO) {
        return JSON.toJSONString(userDTO);
    }
}

转换结果

Target(targetUserDTO=UserDTO(name=scj, age=18), targetUserJson={"age":66,"name":"zs"})

4、expression

通过 expression 可以指定 java 表达式,可以直接调用对应的方法。

转换器

@Mapper
public interface Convert {

    Convert INSTANCE = Mappers.getMapper(Convert.class);
    
    // 本类方法
    @Mapping(target = "targetUserDTO",  expression = "java(json2DTO(source.getSourceUserJson()))")
    // 非本类方法(类全名调用即可)
    @Mapping(target = "targetUserJson", expression = "java(com.alibaba.fastjson.JSON.toJSONString(source.getSourceUserDTO()))")
    // 枚举
    @Mapping(target = "sex", expression = "java(source.getSexEnum().getDesc())")
    Target convert(Source source);

    default UserDTO json2DTO(String userJson) {
        return JSON.parseObject(userJson, UserDTO.class);
    }
}

转换结果

Target(targetUserDTO=UserDTO(name=scj, age=18), targetUserJson={"age":66,"name":"zs"}, sex=)

ConvertImpl

public class ConvertImpl implements Convert {
    @Override
    public Target convert(Source source) {
        if ( source == null ) {
            return null;
        }

        Target target = new Target();

        target.setTargetUserDTO( json2DTO(source.getSourceUserJson()) );
        target.setTargetUserJson( com.alibaba.fastjson.JSON.toJSONString(source.getSourceUserDTO()) );
        target.setSex( source.getSexEnum().getDesc() );

        return target;
    }
}

5、多个参数

待转换的类

@Data
public class Target {

    // source1

    private String strValue1;
    private Integer intValue1;

    // source2

    private String strValue2;
    private Integer intValue2;

    // field

    private String strValue;
    private Integer intValue;

}

转换目标类

@Data
public class Source1 {
	private String strValue = "s1";
    private Integer intValue = 1;
}

@Data
public class Source2 {
    private String strValue = "s2";
    private Integer intValue = 2;
}

转换器

@Mapper
public interface Convert {

    Convert INSTANCE = Mappers.getMapper(Convert.class);

    @Mapping(target = "strValue1", source = "s1.strValue")
    @Mapping(target = "intValue1", source = "s1.intValue")
    @Mapping(target = "strValue2", source = "s2.strValue")
    @Mapping(target = "intValue2", source = "s2.intValue")
    @Mapping(target = "strValue", source = "strValue")
    @Mapping(target = "intValue", source = "intValue")
    Target convert(Source1 s1, Source2 s2, String strValue, Integer intValue);
}

注意事项:如果字段名全部没有冲突,不需要 @Mapping,会自动对应。

五、推荐的IDEA插件

IDEA 搜索 MapStruct Support 安装即可,可以在使用MapStruct时获得更加丰富代码提示。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scj1022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值