抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

banner

数据类型转换

自动类型转换

在平常的对象映射中,总会有很多类型不完全一致的情况,例如IntegerLongStringInteger或者两个自定义对象之间的转换等。在MapStruct中定义了一些类型的自动类型转换或者说是隐形类型转换。

例如在上节中提到的基本数据类型和其包装类型之间的转换,String和数字类型的转换,String和时间类型的转换等。在@Mapping注解中还可以自定义转换格式。

numberFormat属性

例如数字类型转String类型,使用numberFormat属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//Goods.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {

private BigDecimal price;
}

//GoodsDto.java
@Data
public class GoodsDto {

private String price;
}

//GoodsMapper.java

@Mapper
public interface GoodsMapper {

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

@Mapping(source = "price",target = "price",numberFormat = "¥#.00")
GoodsDto toGoodsDto(Goods goods);
}


@Test
void testNumberFormat(){
Goods goods = new Goods(BigDecimal.valueOf(19.9));
GoodsDto goodsDto = GoodsMapper.INSTANCE.toGoodsDto(goods);
log.info("goodsDto:{}",goodsDto);
}

//输出结果:goodsDto:GoodsDto(price=¥19.90)

使用numberFormat属性本质上就是使用java.text.DecimalFormat来实现转换的。

下面是MapStruct生成的toGoodsDto方法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GoodsMapperImpl implements GoodsMapper {

@Override
public GoodsDto toGoodsDto(Goods goods) {
if ( goods == null ) {
return null;
}

GoodsDto goodsDto = new GoodsDto();

if ( goods.getPrice() != null ) {
goodsDto.setPrice( createDecimalFormat( "¥#.00" ).format( goods.getPrice() ) );
}

return goodsDto;
}

private DecimalFormat createDecimalFormat( String numberFormat ) {

DecimalFormat df = new DecimalFormat( numberFormat );
df.setParseBigDecimal( true );
return df;
}
}

@IterableMapping注解中也有numberFormat属性,效果跟上面是一样的。

1
2
@IterableMapping(numberFormat = "¥#.00")
List<String> toPricesStr(List<Integer> price);
dateFormat属性

时间类型字段例如DateLocalDateLocalDateTime等转String类型,可以使用dateFormat指定转换格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//Goods.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {

private BigDecimal price;

private Date createTime;
}

//GoodsDto.java
@Data
public class GoodsDto {

private String price;

private String createTime;

}

//GoodsMapper.java

@Mapper
public interface GoodsMapper {

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

@Mapping(source = "price",target = "price",numberFormat = "¥#.00")
@Mapping(source = "createTime",target = "createTime",dateFormat = "YYYY-MM-dd")
GoodsDto toGoodsDto(Goods goods);
}


@Test
void testFormat(){
Goods goods = new Goods(BigDecimal.valueOf(19.9),new Date());
GoodsDto goodsDto = GoodsMapper.INSTANCE.toGoodsDto(goods);
log.info("goodsDto:{}",goodsDto);
}

//输出结果:goodsDto:GoodsDto(price=¥19.90, createTime=2022-06-06)
  • 如果是Date类型和String互相转换就是使用SimpleDateFormat类来实现的。
  • 如果是LocalDateLocalDateTime互相转换就是使用DateTimeFormatter来实现的
其他类型转换

MapStruct中还支持其他类的自动类型相互转换,常用的如下:

  • java.sql.Datejava.util.Date

  • java.sql.Timejava.util.Date

  • java.sql.Timestampjava.util.Date

  • StringStringBuilder

  • java.net.URLString.

  • java.util.UUID and String

具体的可以参考[官方文档](MapStruct 1.5.1.Final Reference Guide)

映射引用对象

在平常的开发中,一个对象除了引用基本属性类型或者包装类型之外,还会引用其他对象,那么不同引用类型之间应该如何去转换?

例如下面的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Car.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {

private Integer id;

private String carName;

private LocalDateTime createTime;

private List<String> carInfos;

private Person person;
}

// Person.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

private Integer id;

private Integer age;

private String name;

private LocalDateTime createTime;
}




// CarDto.java
@Data
public class CarDto {

private String carName;

private String publishTime;

private List<String> carInfos;

private PersonDto personDto;
}


// PersonDto.java
@Data
public class PersonDto {

private Integer age;

private String name;

private String birthday;
}



// CarMapper.java
@Mapping(source = "createTime",target = "publishTime",dateFormat = "YYYY-MM-dd hh:mm:ss")
@Mapping(source = "person",target = "personDto")
CarDto carToCarDto(Car car);

在上面的例子中,要将Car转换为CarDto类,两边分别引用着Person类型和PersonDto类型。我们编译一下代码,就会神奇的发现多了一个personToPersonDto的方法,这是MapStruct自动生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
public class CarMapperImpl implements CarMapper {

private final DateTimeFormatter dateTimeFormatter_YYYY_MM_dd_hh_mm_ss_11499725216 = DateTimeFormatter.ofPattern( "YYYY-MM-dd hh:mm:ss" );

@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}

CarDto carDto = new CarDto();

if ( car.getCreateTime() != null ) {
carDto.setPublishTime( dateTimeFormatter_YYYY_MM_dd_hh_mm_ss_11499725216.format( car.getCreateTime() ) );
}
carDto.setPersonDto( personToPersonDto( car.getPerson() ) );
carDto.setCarName( car.getCarName() );
List<String> list = car.getCarInfos();
if ( list != null ) {
carDto.setCarInfos( new ArrayList<String>( list ) );
}

return carDto;
}

protected PersonDto personToPersonDto(Person person) {
if ( person == null ) {
return null;
}

PersonDto personDto = new PersonDto();

personDto.setAge( person.getAge() );
personDto.setName( person.getName() );

return personDto;
}
}

MapStruct在生成源对象和目标对象的映射方法时会遵循下列特点:

  1. 如果源属性和目标属性具有相同的类型,值将被简单地从源属性直接复制到目标属性。如果属性是一个集合(例如:List),那么集合的副本将被设置到目标属性中。

    例如上面List<String>类型字段之间的映射,直接new了一个ArrayList出来并把源对象的属性作为构造参数传了进去。

  2. 如果源属性和目标属性的类型不同,那么就会检查是否存在另一个映射方法,方法参数是源属性的类型返回类型是目标的属性类型。如果存在这样的方法,那么他会在映射方法中被调用。

    例如在CarMapper接口中这样定义一个方法,那么在实现类中就会调用它。

    1
    2
    @Mapping(source = "createTime",target = "birthday")
    PersonDto toPersonDto(Person person);
  3. 如果不存在这样的方法,MapStruct将会检查目标类型的属性和源对象的属性是否可以自动转换。如果可以的话,那么他会在映射方法中被调用。

  4. 如果没有找到这样的方法,MapStruct将尝试生成一个自动的映射方法,在源和目标属性之间进行映射。像上面的personToPersonDto方法就是自动生成的映射方法。

  5. 如果MapStruct无法创建一个该映射方法,那么在编译时将会报出错误信息。

自定义映射方法

一般情况下MapStruct自动生成的代码就能够使用了,但在大部分情况下还是需要自定义映射方法,例如目标属性名和源属性名不匹配等情况。例如上面PersonPersonDto中的createTimebirthday字段一样。子映射方法和普通的映射方法使用是一样的,都是定义@Mapping注解。

1
2
@Mapping(source = "createTime",target = "birthday")
PersonDto toPersonDto(Person person);

当然你也可以在源方法上定义@Mapping方法,不过官方还是建议在子映射方法上来定义@Mapping注解

1
2
3
4
@Mapping(source = "createTime",target = "publishTime",dateFormat = "YYYY-MM-dd hh:mm:ss")
@Mapping(source = "person",target = "personDto")
@Mapping(source = "person.createTime",target = "personDto.birthday")
CarDto carToCarDto(Car car);

在一些情况下我们并不是要单纯的映射复制属性,而是需要通过一些自定义逻辑来生成一些属性值,这个时候就可以自定义方法逻辑体。例如下面我们给CarDto加上一个CarInfoDto类型的字段,我们需要通过车辆的发布时间来动态判断他是新品还是热销的车型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//CarInfoDto.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CarInfoDto {

private String desc;
}


//CarDto.java
@Data
public class CarDto {

private String carName;

private String publishTime;

private List<String> carInfos;

// 新增字段
private CarInfoDto carInfoDto;

private PersonDto personDto;
}

我们在原来的CarMapper上新增方法mapToCarInfoDtoMapStruct生成的实现类将会调用此方法来生成CarInfoDto的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface CarMapper {

@Mapping(source = "createTime",target = "publishTime",dateFormat = "YYYY-MM-dd hh:mm:ss")
@Mapping(source = "person",target = "personDto")
@Mapping(source = "person.createTime",target = "personDto.birthday")
@Mapping(source = "car",target = "carInfoDto")
CarDto carToCarDto(Car car);

//
@Mapping(source = "createTime",target = "birthday")
PersonDto toPersonDto(Person person);

// 新增方法
default CarInfoDto mapToCarInfoDto(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getYear()==createTime.getYear()?"新品":"热销";
return new CarInfoDto(desc);
}

}

调用其他类的映射方法

在上面的dateFormat的例子中,我们使用该属性定义时间类型和字符串类型之间的转换格式。如果在每个类中都定义一遍那就有点繁琐了,在MapStruct中还支持调用其他类的映射方法。如下我们定义了一个DateMapper类,定义了LocalDateTimeString类之间的转换方法,需要注意一点,如果调用该类的Mapper被CI所管理的话,那么该类也需要被CI管理,例如加个@Component注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// DateMapper.java 
@Component
public class DateMapper {

private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( "yyyy年MM月dd日 hh:mm:ss" );

public String toString(LocalDateTime localDateTime){
String dateTimeStr = dateTimeFormatter.format(localDateTime);
return dateTimeStr;
}

public LocalDateTime toLocalDateTime(String dateTimeStr){
LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, dateTimeFormatter);
return localDateTime;
}
}

定义完转换类之后,修改CarMapper方法,在@Mapper注解添加uses属性,效果跟在@Mapping中定义dateFormat效果是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,uses = DateMapper.class)
public interface CarMapper {

@Mapping(source = "createTime",target = "publishTime")
@Mapping(source = "person",target = "personDto")
@Mapping(source = "person.createTime",target = "personDto.birthday")
@Mapping(source = "car",target = "carInfoDto")
CarDto carToCarDto(Car car);

//
@Mapping(source = "createTime",target = "birthday")
PersonDto toPersonDto(Person person);


default CarInfoDto mapToCarInfoDto(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getYear()==createTime.getYear()?"新品":"热销";
return new CarInfoDto(desc);
}

}

方法冲突

在有些时候会出现多个映射方法签名相同的情况,例如下面的例子,双方的入参和返回参数都相同的时候。根据MapStruct定义的映射规则,这时候会编译出错,因为不知道具体选择那个方法来作为映射。这个时候MapStruct提供了@Named注解来标识方法在通过@Mapping注解的qualifiedByName属性来解决。

1
2
3
4
5
6
7
8
9
10
11
12
default CarInfoDto mapToCarInfoDtoByYear(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getYear()==createTime.getYear()?"新品":"热销";
return new CarInfoDto(desc);
}


default CarInfoDto toCarInfoDtoByMonth(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getMonthValue()==createTime.getMonthValue()?"新品":"热销";
return new CarInfoDto(desc);
}

具体使用代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,uses = DateMapper.class)
public interface CarMapper {

@Mapping(source = "createTime",target = "publishTime")
@Mapping(source = "person",target = "personDto")
@Mapping(source = "person.createTime",target = "personDto.birthday")
@Mapping(source = "car",target = "carInfoDto",qualifiedByName = "toCarInfoDtoByMonth")
CarDto carToCarDto(Car car);

//
@Mapping(source = "createTime",target = "birthday")
PersonDto toPersonDto(Person person);


@Named("mapToCarInfoDtoByYear")
default CarInfoDto mapToCarInfoDtoByYear(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getYear()==createTime.getYear()?"新品":"热销";
return new CarInfoDto(desc);
}

@Named("toCarInfoDtoByMonth")
default CarInfoDto toCarInfoDtoByMonth(Car car){
LocalDateTime createTime = car.getCreateTime();
String desc = LocalDateTime.now().getMonthValue()==createTime.getMonthValue()?"新品":"热销";
return new CarInfoDto(desc);
}

}

小憩一下

在本章中介绍了MapStruct中的数据类型的自动转换,numberFormatdateFormat两个格式属性,以及相关的映射规则等。在下一节将会继续介绍MapStruct的相关知识~。

img