前面在00-MapStruct使用文档中,我们已经简单的介绍了如何使用MapStruct来进行类型转换。
本文主要介绍一些常用的映射方法。
1. 默认转换
对于Source类与Target类中相同名称的属性,如果它们的类型也相同,则该类属性是会默认转换的。即:我们在编写Mapper的时候,不用针对该类属性配置@Mapping注解。
下面给出具体的代码示例:
1.1 Source类定义
@Data
public class Source {
// 属性名 和 类型 都相同
// 原生类型
private int sameTypeSameName;
// 属性名 和 类型 都相同
// 原生类型的包装类型
private Long longWrapper;
// 属性名 和 类型 都相同
// 字符串类型
private String str;
// 属性名 和 类型 都相同
// 对象或引用类型
private SomeObject obj;
// 属性名 和 类型 都相同
// 集合类型
private List<SomeObject> objList;
}
@Data
public class SomeObject {
private int value;
}
1.2 Target类定义
@Data
public class Target {
// 属性名 和 类型 都相同
// 原生类型
private int sameTypeSameName;
// 属性名 和 类型 都相同
// 原生类型的包装类型
private Long longWrapper;
// 属性名 和 类型 都相同
// 字符串类型
private String str;
// 属性名 和 类型 都相同
// 对象或引用类型
private SomeObject obj;
// 属性名 和 类型 都相同
// 集合类型
private List<SomeObject> objList;
}
1.3 Mapper
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
Target s2t(Source source);
Source t2s(Target target);
}
这样就可以完成Source和Target对象之间的转换了。
1.4 测试
编写测试程序:
public class Main {
public static void main(String[] args) {
Long longWrapper = Long.valueOf(167989);
SomeObject obj = new SomeObject();
obj.setValue(17);
Source source = new Source();
source.setSameTypeSameName(3);
source.setLongWrapper(longWrapper);
source.setStr("Hello World");
source.setObj(obj);
source.setObjList(Arrays.asList(obj));
Target target = SourceTargetMapper.INSTANCE.s2t(source);
System.out.println("Target: " + target);
System.out.println("包装类型是否相等:" + (source.getLongWrapper() == target.getLongWrapper()));
System.out.println("String类型是否相等:" + (source.getStr() == target.getStr()));
System.out.println("对象类型是否相等:" + (source.getObj() == target.getObj()));
System.out.println("集合类型是否相等:" + (source.getObjList() == target.getObjList()));
Source newSource = SourceTargetMapper.INSTANCE.t2s(target);
System.out.println(newSource);
}
}
程序的输出结果如下:
Target: Target(sameTypeSameName=3, longWrapper=167989, str=Hello World, obj=SomeObject(value=17), objList=[SomeObject(value=17)])
包装类型是否相等:true
String类型是否相等:true
对象类型是否相等:true
集合类型是否相等:false
Source(sameTypeSameName=3, longWrapper=167989, str=Hello World, obj=SomeObject(value=17), objList=[SomeObject(value=17)])
1.5 小结
生成的Mapper实现类的代码如下:
public class SourceTargetMapperImpl implements SourceTargetMapper {
@Override
public Target s2t(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setSameTypeSameName( source.getSameTypeSameName() );
target.setLongWrapper( source.getLongWrapper() );
target.setStr( source.getStr() );
target.setObj( source.getObj() );
// 集合类型的转换
List<SomeObject> list = source.getObjList();
if ( list != null ) {
target.setObjList( new ArrayList<SomeObject>( list ) );
}
return target;
}
// 省略其他代码 ......
}
总结:
- 原生类型、对象类型都是值拷贝;
- 集合类型是重新创建一个集合,但是里面的元素还是之前的对象;
2. 隐式转换
对于Source类与Target类中相同名称的属性,如果它们的类型不相同,但类型之间符合MapStruct支持的隐式类型转换规则,那么我们也是不需要对这类属性进行单独配置的。
下面给出具体的代码示例:
2.1 Source类定义
@Data
public class Source {
// 属性名相同,类型不相同
// Source: int; Target: Integer
private int sameName1;
// 属性名相同,类型不相同
// Source: int; Target: Long
private int sameName2;
// 属性名相同,类型不相同
// Source: long; Target: Integer
// 损失精度的转换
private long sameName3;
// 属性名相同,类型不相同
// Source: double; Target: String
private double sameName4;
// 属性名相同,类型不相同
// Source: SomeType(枚举); Target: String
private SomeType type;
// 属性名相同,类型不相同
// Source: Date(日期); Target: String
private Date date;
// 属性名相同,类型不相同
// Source: LocalDateTime(日期); Target: String
private LocalDateTime localDateTime;
// 属性名相同,类型不相同
// Source: long; Target: BigInteger
private long count;
// 属性名相同,类型不相同
// Source: double; Target: BigDecimal
private double price;
}
// 枚举类
public enum SomeType {
A, B;
}
2.2 Target类定义
@Data
public class Target {
// 属性名相同,类型不相同
// Source: int; Target: Integer
private Integer sameName1;
// 属性名相同,类型不相同
// Source: int; Target: Long
private Long sameName2;
// 属性名相同,类型不相同
// Source: long; Target: Integer
// 损失精度的转换
private Integer sameName3;
// 属性名相同,类型不相同
// Source: double; Target: String
private String sameName4;
// 属性名相同,类型不相同
// Source: SomeType(枚举); Target: String
private String type;
// 属性名相同,类型不相同
// Source: Date(日期); Target: String
private String date;
// 属性名相同,类型不相同
// Source: LocalDateTime(日期); Target: String
private String localDateTime;
// 属性名相同,类型不相同
// Source: long; Target: BigInteger
private BigInteger count;
// 属性名相同,类型不相同
// Source: double; Target: BigDecimal
private BigDecimal price;
}
2.3 Mapper
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
Target s2t(Source source);
Source t2s(Target target);
}
这样就可以完成Source和Target对象之间的转换了。
2.4 测试
编写测试程序:
public class Main {
public static void main(String[] args) {
Source source = new Source();
source.setSameName1(1);
source.setSameName2(2);
source.setSameName3(3L);
source.setSameName4(4.56);
source.setType(SomeType.A);
source.setDate(new Date());
source.setLocalDateTime(LocalDateTime.now());
source.setCount(10L);
source.setPrice(10.23);
Target target = SourceTargetMapper.INSTANCE.s2t(source);
System.out.println(target);
Source newSource = SourceTargetMapper.INSTANCE.t2s(target);
System.out.println(newSource);
}
}
程序的输出结果如下:
Target(sameName1=1, sameName2=2, sameName3=3, sameName4=4.56, type=A, date=22-11-3 下午2:55, localDateTime=2022-11-03T14:55:51.635, count=10, price=10.23)
Source(sameName1=1, sameName2=2, sameName3=3, sameName4=4.56, type=A, date=Thu Nov 03 14:55:00 CST 2022, localDateTime=2022-11-03T14:55:51.635, count=10, price=10.23)
2.5 小结
生成的Mapper实现类的代码如下:
public class SourceTargetMapperImpl implements SourceTargetMapper {
@Override
public Target s2t(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setSameName1( source.getSameName1() );
target.setSameName2( (long) source.getSameName2() );
target.setSameName3( (int) source.getSameName3() );
target.setSameName4( String.valueOf( source.getSameName4() ) );
if ( source.getType() != null ) {
target.setType( source.getType().name() );
}
if ( source.getDate() != null ) {
target.setDate( new SimpleDateFormat().format( source.getDate() ) );
}
if ( source.getLocalDateTime() != null ) {
target.setLocalDateTime( DateTimeFormatter.ISO_LOCAL_DATE_TIME.format( source.getLocalDateTime() ) );
}
target.setCount( BigInteger.valueOf( source.getCount() ) );
target.setPrice( BigDecimal.valueOf( source.getPrice() ) );
return target;
}
@Override
public Source t2s(Target target) {
if ( target == null ) {
return null;
}
Source source = new Source();
if ( target.getSameName1() != null ) {
source.setSameName1( target.getSameName1() );
}
if ( target.getSameName2() != null ) {
source.setSameName2( target.getSameName2().intValue() );
}
if ( target.getSameName3() != null ) {
source.setSameName3( target.getSameName3() );
}
if ( target.getSameName4() != null ) {
source.setSameName4( Double.parseDouble( target.getSameName4() ) );
}
if ( target.getType() != null ) {
source.setType( Enum.valueOf( SomeType.class, target.getType() ) );
}
try {
if ( target.getDate() != null ) {
source.setDate( new SimpleDateFormat().parse( target.getDate() ) );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
if ( target.getLocalDateTime() != null ) {
source.setLocalDateTime( LocalDateTime.parse( target.getLocalDateTime() ) );
}
if ( target.getCount() != null ) {
source.setCount( target.getCount().longValue() );
}
if ( target.getPrice() != null ) {
source.setPrice( target.getPrice().doubleValue() );
}
return source;
}
}
从代码实现上,我们可以看到:MapStruct支持很多类型之间的隐式转换。例如:
-
Java原生数据类型 <-> 相应的包装类型,例如:int -> Integer、boolean -> Boolean
-
注意:包装类型 -> 原生类型之前,是会有null值判断的;
-
-
Java原生数值类型之间,原生数值类型 <-> 数值包装类型,例如:int -> long、int -> Long
-
Java原生数据类型 or 包装类型 <-> String,例如:double -> String、Double -> String
-
枚举 <-> String
-
日期 <-> String,例如:Date -> String、LocalDateTime -> String
-
Date <-> String之间的转换,是使用SimpleDateFormat进行转换的;
-
LocalDateTime -> String之间的转换,是使用DateTimeFormatter进行转换的;
-
更多隐式的类型转换,可参考官方文档:MapStruct支持的隐式类型转换
2.6 扩展
对于Java原生数据类型 or 包装类型 <-> String之间的转换,我们可以自定义其转换的格式。
当我们指定了转换的格式后,MapStruct会使用DecimalFormat进行类型之间转换。
更多DecimalFormat的用法可自行百度。这里仅帖个链接:DecimalFormat用法举例
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
// numberFormat 指定了转换后的字符串格式
@Mapping(target = "sameName4", numberFormat = "#.##元")
Target s2t(Source source);
// @InheritInverseConfiguration 注解表示 继承并反转Target s2t(Source source)的转换规则
@InheritInverseConfiguration
Source t2s(Target target);
}
上面测试程序的输入如下:
Target(sameName1=1, sameName2=2, sameName3=3, sameName4=4.56元, type=A, date=22-11-3 下午3:26, localDateTime=2022-11-03T15:26:18.641, count=10, price=10.23)
Source(sameName1=1, sameName2=2, sameName3=3, sameName4=4.56, type=A, date=Thu Nov 03 15:26:00 CST 2022, localDateTime=2022-11-03T15:26:18.641, count=10, price=10.23)
对于日期 <-> String之间的转换,我们可以自定义其转换的格式。
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
// dateFormat 指定了日期转换成字符串后的格式
@Mapping(target = "sameName4", numberFormat = "这是格式化的数字#.##")
@Mapping(target = "date", dateFormat = "yyyy-MM-dd")
@Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
Target s2t(Source source);
// @InheritInverseConfiguration 注解表示 继承并反转Target s2t(Source source)的转换规则
@InheritInverseConfiguration
Source t2s(Target target);
}
上面测试程序的输入如下:
Target(sameName1=1, sameName2=2, sameName3=3, sameName4=这是格式化的数字4.56, type=A, date=2022-11-03, localDateTime=2022-11-03 15:29:29, count=10, price=10.23)
Source(sameName1=1, sameName2=2, sameName3=3, sameName4=4.56, type=A, date=Thu Nov 03 00:00:00 CST 2022, localDateTime=2022-11-03T15:29:29, count=10, price=10.23)
这里贴一下Mapper实现类的代码,大家可以结合代码理解下。
public class SourceTargetMapperImpl implements SourceTargetMapper {
private final DateTimeFormatter dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168 = DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" );
@Override
public Target s2t(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setSameName4( new DecimalFormat( "这是格式化的数字#.##" ).format( source.getSameName4() ) );
if ( source.getDate() != null ) {
target.setDate( new SimpleDateFormat( "yyyy-MM-dd" ).format( source.getDate() ) );
}
if ( source.getLocalDateTime() != null ) {
target.setLocalDateTime( dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168.format( source.getLocalDateTime() ) );
}
target.setSameName1( source.getSameName1() );
target.setSameName2( (long) source.getSameName2() );
target.setSameName3( (int) source.getSameName3() );
if ( source.getType() != null ) {
target.setType( source.getType().name() );
}
target.setCount( BigInteger.valueOf( source.getCount() ) );
target.setPrice( BigDecimal.valueOf( source.getPrice() ) );
return target;
}
@Override
public Source t2s(Target target) {
if ( target == null ) {
return null;
}
Source source = new Source();
try {
if ( target.getSameName4() != null ) {
source.setSameName4( new DecimalFormat( "这是格式化的数字#.##" ).parse( target.getSameName4() ).doubleValue() );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
try {
if ( target.getDate() != null ) {
source.setDate( new SimpleDateFormat( "yyyy-MM-dd" ).parse( target.getDate() ) );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
if ( target.getLocalDateTime() != null ) {
source.setLocalDateTime( LocalDateTime.parse( target.getLocalDateTime(), dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168 ) );
}
if ( target.getSameName1() != null ) {
source.setSameName1( target.getSameName1() );
}
if ( target.getSameName2() != null ) {
source.setSameName2( target.getSameName2().intValue() );
}
if ( target.getSameName3() != null ) {
source.setSameName3( target.getSameName3() );
}
if ( target.getType() != null ) {
source.setType( Enum.valueOf( SomeType.class, target.getType() ) );
}
if ( target.getCount() != null ) {
source.setCount( target.getCount().longValue() );
}
if ( target.getPrice() != null ) {
source.setPrice( target.getPrice().doubleValue() );
}
return source;
}
}
3. 通过@Mapping指定转换规则
当Source类和Target类中,对应的字段名不一样时,我们需要告诉MapStruct,哪两个属性之间是对应的。我们可以通过@Mapping注解指定属性之间的对应关系。
3.1 Source类定义
@Data
public class Source {
// Source: s1; Target: t1
private int s1;
// Source: s2; Target: t2
private double s2;
// Source: s3; Target: t3
private Date s3;
// Source: s4; Target: t4
private SomeType s4;
// Source: s5; Target: t5
private SomeSourceObject s5;
}
public enum SomeType {
A, B;
}
@Data
public class SomeSourceObject {
private int value;
}
3.2 Target类定义
@Data
public class Target {
// Source: s1; Target: t1
private int t1;
// Source: s2; Target: t2
private String t2;
// Source: s3; Target: t3
private String t3;
// Source: s4; Target: t4
private String t4;
// Source: s5; Target: t5
private SomeSourceObject t5;
// Source: SomeSourceObject s5; Target: SomeTargetObject t6
private SomeTargetObject t6;
}
@Data
public class SomeTargetObject {
private int targetValue;
}
3.3 Mapper
上述代码的注释中已经指定了Source与Target中属性的对应关系。
我们可以通过@Mapping注解的source()指定源类型中的属性名,target()指定目标类型中的属性名。
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
@Mapping(source = "s1", target = "t1")
@Mapping(source = "s2", target = "t2", numberFormat = "格式化的数字#.##")
@Mapping(source = "s3", target = "t3", dateFormat = "yyyy-MM-dd")
@Mapping(source = "s4", target = "t4")
@Mapping(source = "s5", target = "t5")
@Mapping(source = "s5", target = "t6")
Target s2t(Source source);
/**
* 注解@InheritInverseConfiguration表示:继承并反转Target s2t(Source source)的转换规则
*
* @param target
* @return
*/
@InheritInverseConfiguration
Source t2s(Target target);
/**
* 用于转换 SomeSourceObject -> SomeTargetObject
* @param some
* @return
*/
@Mapping(source = "value", target = "targetValue")
SomeTargetObject someS2t(SomeSourceObject some);
@InheritInverseConfiguration
SomeSourceObject someT2s(SomeTargetObject some);
}
对于Source类中的属性s5 与 Target类中的属性t5,MapStruct直接使用值拷贝的方式进行转换;
对于Source类中的属性s5 与 Target类中的属性t6,由于它们是对象类型,并且类型是不同的,MapStruct是不知道它们之间是如何转换的,所以我们需要单独定义它们之间的转换规则,如:
-
方法SomeTargetObject someS2t(SomeSourceObject some)
-
方法SomeSourceObject someT2s(SomeTargetObject some)
3.4 测试
编写测试程序:
public class Main {
public static void main(String[] args) {
SomeSourceObject someObj = new SomeSourceObject();
someObj.setValue(123);
Source source = new Source();
source.setS1(1);
source.setS2(5.46);
source.setS3(new Date());
source.setS4(SomeType.A);
source.setS5(someObj);
Target target = SourceTargetMapper.INSTANCE.s2t(source);
System.out.println(target);
Source newSource = SourceTargetMapper.INSTANCE.t2s(target);
System.out.println(newSource);
}
}
程序的输出结果如下:
Target(t1=1, t2=格式化的数字5.46, t3=2022-11-03, t4=A, t5=SomeSourceObject(value=123), t6=SomeTargetObject(targetValue=123))
Source(s1=1, s2=5.46, s3=Thu Nov 03 00:00:00 CST 2022, s4=A, s5=SomeSourceObject(value=123))
3.5 小结
生成的Mapper实现类的代码如下:
public class SourceTargetMapperImpl implements SourceTargetMapper {
@Override
public Target s2t(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.setT1( source.getS1() );
target.setT2( new DecimalFormat( "格式化的数字#.##" ).format( source.getS2() ) );
if ( source.getS3() != null ) {
target.setT3( new SimpleDateFormat( "yyyy-MM-dd" ).format( source.getS3() ) );
}
if ( source.getS4() != null ) {
target.setT4( source.getS4().name() );
}
target.setT5( source.getS5() );
target.setT6( someS2t( source.getS5() ) );
return target;
}
@Override
public Source t2s(Target target) {
if ( target == null ) {
return null;
}
Source source = new Source();
source.setS1( target.getT1() );
try {
if ( target.getT2() != null ) {
source.setS2( new DecimalFormat( "格式化的数字#.##" ).parse( target.getT2() ).doubleValue() );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
try {
if ( target.getT3() != null ) {
source.setS3( new SimpleDateFormat( "yyyy-MM-dd" ).parse( target.getT3() ) );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
if ( target.getT4() != null ) {
source.setS4( Enum.valueOf( SomeType.class, target.getT4() ) );
}
source.setS5( target.getT5() );
return source;
}
@Override
public SomeTargetObject someS2t(SomeSourceObject some) {
if ( some == null ) {
return null;
}
SomeTargetObject someTargetObject = new SomeTargetObject();
someTargetObject.setTargetValue( some.getValue() );
return someTargetObject;
}
@Override
public SomeSourceObject someT2s(SomeTargetObject some) {
if ( some == null ) {
return null;
}
SomeSourceObject someSourceObject = new SomeSourceObject();
someSourceObject.setValue( some.getTargetValue() );
return someSourceObject;
}
}