03-MapStruct-基本的映射方法

前面在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;
    }
    
    // 省略其他代码 ......
}

总结:

  1. 原生类型、对象类型都是值拷贝;
  2. 集合类型是重新创建一个集合,但是里面的元素还是之前的对象;

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;
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值