MapStruct文档(四)——转换

目录

 

3.1、隐式转换

3.1.1、枚举、BigDecimal、Date、Timestamp

3.1.2、LocalDateTime

3.1.3、Boolean

3.2、*对象属性转换

3.3、将映射目标类型传递给自定义映射器

3.4、将上下文或状态对象传递给自定义方法

3.5、区别相同参数类型和返回类型的映射方法


3.1、隐式转换

  • enum和String
  • BigDecimal(等)、基本数据类(包括包装类)和String
  • java.time.LocalDateTime(等)和String
  • java.util.Date、和String
  • java.sql.Timestamp和java.util.Date

3.1.1、枚举、BigDecimal、Date、Timestamp


@Data
public class TransformPO {

    private LevelEnum level;

    private BigDecimal price;

    private Date date;

    private Timestamp timestamp;

}
 
@Data
public class TransformBO {

    private String level;

    private int price;

    private String date;

    private Date timestamp;
}
 
@Mapper
public interface TestMapper {

    TransformBO toTransformBO(TransformPO transformPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TransformBO toTransformBO(TransformPO transformPO) {
        if ( transformPO == null ) {
            return null;
        }

        TransformBO transformBO = new TransformBO();

		// enum->String
        if ( transformPO.getLevel() != null ) {
            transformBO.setLevel( transformPO.getLevel().name() );
        }
		// BigDecimal->int
        if ( transformPO.getPrice() != null ) {
            transformBO.setPrice( transformPO.getPrice().intValue() );
        }
		// Date->String
        if ( transformPO.getDate() != null ) {
            transformBO.setDate( new SimpleDateFormat().format( transformPO.getDate() ) );
        }
		// Timestamp->Date
        transformBO.setTimestamp( transformPO.getTimestamp() );

        return transformBO;
    }
}
 
TransformPO transformPO = new TransformPO();
transformPO.setLevel(LevelEnum.PERFECT);
transformPO.setPrice(new BigDecimal("12.4"));
transformPO.setDate(new Date(System.currentTimeMillis()));
transformPO.setTimestamp(new Timestamp(System.currentTimeMillis()));

System.out.println(testMapper.toTransformBO(transformPO));

结果

Date转String默认使用SimpleDateFormat格式化,@Mapping#dateFormat可以指定格式。


@Mapper
public interface TestMapper {

    @Mapping(target = "date", dateFormat = "yyyy-MM-dd HH:mm:ss")
    TransformBO toTransformBO(TransformPO transformPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TransformBO toTransformBO(TransformPO transformPO) {
        if ( transformPO == null ) {
            return null;
        }

        TransformBO transformBO = new TransformBO();

        if ( transformPO.getDate() != null ) {
			
            transformBO.setDate( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( transformPO.getDate() ) );
        }
        if ( transformPO.getLevel() != null ) {
            transformBO.setLevel( transformPO.getLevel().name() );
        }
        if ( transformPO.getPrice() != null ) {
            transformBO.setPrice( transformPO.getPrice().intValue() );
        }
        transformBO.setTimestamp( transformPO.getTimestamp() );

        return transformBO;
    }
}

结果

 

3.1.2、LocalDateTime


@Data
public class TransformPO {

    private String level;

    private float price;

    private String date;

    private LocalDateTime localDateTime;

    private Date timestamp;

}
 
@Data
public class TransformBO {

    private LevelEnum level;

    private String price;

    private Date date;

    private String localDateTime;

    private Timestamp timestamp;
}
 
@Mapper
public interface TestMapper {

    @Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
	@Mapping(target = "date", dateFormat = "yyyy-MM-dd HH:mm")
    @Mapping(target = "price", numberFormat = "$#.00")
    TransformBO toTransformBO(TransformPO transformPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TransformBO toTransformBO(TransformPO transformPO) {
        if ( transformPO == null ) {
            return null;
        }

        TransformBO transformBO = new TransformBO();

		// LocalDateTime->String
        if ( transformPO.getLocalDateTime() != null ) {
            transformBO.setLocalDateTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( transformPO.getLocalDateTime() ) );
        }
		
		// String->Date
        try {
            if ( transformPO.getDate() != null ) {
                transformBO.setDate( new SimpleDateFormat( "yyyy-MM-dd HH:mm" ).parse( transformPO.getDate() ) );
            }
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
		
		// 基本类型->String
        transformBO.setPrice( new DecimalFormat( "$#.00" ).format( transformPO.getPrice() ) );
 
		// String->enum
        if ( transformPO.getLevel() != null ) {
            transformBO.setLevel( Enum.valueOf( LevelEnum.class, transformPO.getLevel() ) );
        }
 
		// Date->Timestamp
        if ( transformPO.getTimestamp() != null ) {
            transformBO.setTimestamp( new Timestamp( transformPO.getTimestamp().getTime() ) );
        }

        return transformBO;
    }
}
 
TransformPO transformPO = new TransformPO();
transformPO.setLevel("PERFECT");
transformPO.setPrice(12.4f);
transformPO.setDate("2020-10-29 13:56");
transformPO.setLocalDateTime(LocalDateTime.now());
transformPO.setTimestamp(new Date(System.currentTimeMillis()));
System.out.println(testMapper.toTransformBO(transformPO));

结果

基本类型、包装类、BigDecimal转String默认使用DecimalFormat格式化,@Mapping#numberFormat可以指定格式。

 

3.1.3、Boolean


@Data
public class TransformPO {

    private String price;

    private Float discount;

    private Long count;

    private Boolean delete;

    private String localDateTime;

}
 
@Data
public class TransformBO {

    private BigDecimal price;

    private BigDecimal discount;

    private long count;

    private String delete;

    private LocalDateTime localDateTime;

}
 
@Mapper
public interface TestMapper {

    @Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH")
    TransformBO toTransformBO(TransformPO transformPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TransformBO toTransformBO(TransformPO transformPO) {
        if ( transformPO == null ) {
            return null;
        }

        TransformBO transformBO = new TransformBO();

        if ( transformPO.getLocalDateTime() != null ) {
            transformBO.setLocalDateTime( LocalDateTime.parse( transformPO.getLocalDateTime(), DateTimeFormatter.ofPattern( "yyyy-MM-dd HH" ) ) );
        }
        if ( transformPO.getPrice() != null ) {
            transformBO.setPrice( new BigDecimal( transformPO.getPrice() ) );
        }
        if ( transformPO.getDiscount() != null ) {
            transformBO.setDiscount( BigDecimal.valueOf( transformPO.getDiscount() ) );
        }
        if ( transformPO.getCount() != null ) {
            transformBO.setCount( transformPO.getCount() );
        }
        if ( transformPO.getDelete() != null ) {
            transformBO.setDelete( String.valueOf( transformPO.getDelete() ) );
        }

        return transformBO;
    }
}
 
TransformPO transformPO = new TransformPO();
transformPO.setPrice("12.3");
transformPO.setDiscount(20.3f);
transformPO.setDelete(false);
transformPO.setLocalDateTime("2020-10-29 14");
System.out.println(testMapper.toTransformBO(transformPO));

结果

 

float转BigDecimal也是有精度丢失问题,还是建议String转BigDecimal。

更详细的请参考mapstruct类型转换


3.2、*对象属性转换


@Mapper
public interface TestMapper {

    TestBO toTestBO(TestPO testPO);

    TestThreeBO toTestThreeBO(TestThreePO testThreePO);
}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TestBO toTestBO(TestPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestBO testBO = new TestBO();

        testBO.setId( testPO.getId() );
        testBO.setName( testPO.getName() );
        testBO.setPrice( testPO.getPrice() );
        testBO.setCreteTime( testPO.getCreteTime() );

        return testBO;
    }

    @Override
    public TestThreeBO toTestThreeBO(TestThreePO testThreePO) {
        if ( testThreePO == null ) {
            return null;
        }

        TestThreeBO testThreeBO = new TestThreeBO();

        testThreeBO.setTest( toTestBO( testThreePO.getTest() ) );

        return testThreeBO;
    }
}
 
TestPO testPO = new TestPO();
testPO.setId(1L);
testPO.setName("haru");
testPO.setCreteTime(new Date(System.currentTimeMillis()));
TestThreePO testThreePO = new TestThreePO();
testThreePO.setTest(testPO);
System.out.println(testMapper.toTestThreeBO(testThreePO));

结果

 

@MappingConfig,@Mapper,@BeanMapping,@Mapping注解可以设置mappingControl,越往后的优先级越高,默认值是MappingControl.class,还有NoComplexMapping.classDeepClone.class,用于控制每个字段属性映射的方式;MappingControl.class支持

1、MappingControl.Use.DIRECT——直接复制具有相同属性类型的字段,但如果有自定义的映射配置@Mapping和导入了其他Mapper的自定义的映射,会将直接赋值覆盖;自定义的映射配置@Mapping优先级高于导入了其他Mapper的自定义的映射

2、MappingControl.Use.MAPPING_METHOD——属性类型不同的字段,会去查找将源属性的类型作为参数,将目标属性的类型作为返回的映射方法(包括自定义方法、抽象类实现方法和接口默认方法)。

3、MappingControl.Use.BUILT_IN_CONVERSION——如果不存在上述方法,查找内置转换方法,就像BigDecimal转String。

4、MappingControl.Use.COMPLEX_MAPPING——如果内置转换方法不存在,将进行一些复杂转换,也就是结合现有的映射方法和内置转换,互相嵌套生成一个能够映射的方法。

5、如果都没有,则会自动生成子映射方法,就像上面的例子若是不指定TestPO到TestBO的接口方法,则也会主动生成。

6、若这也没法生成则编译就会报错了。


@Mapper
public interface TestMapper {

    TestThreeBO toTestThreeBO(TestThreePO testThreePO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public TestThreeBO toTestThreeBO(TestThreePO testThreePO) {
        if ( testThreePO == null ) {
            return null;
        }

        TestThreeBO testThreeBO = new TestThreeBO();

        testThreeBO.setTest( testPOToTestBO( testThreePO.getTest() ) );

        return testThreeBO;
    }

	// 自动生成
    protected TestBO testPOToTestBO(TestPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestBO testBO = new TestBO();

        testBO.setId( testPO.getId() );
        testBO.setName( testPO.getName() );
        testBO.setPrice( testPO.getPrice() );
        testBO.setCreteTime( testPO.getCreteTime() );

        return testBO;
    }
}

NoComplexMapping.class支持除MappingControl.Use.COMPLEX_MAPPING其他三种,DeepClone.class只支持MappingControl.Use.MAPPING_METHOD

通常我们不需要修改这个属性。

举个例子

引用3.1.3、DEMO3,将其mapper修改为,使用DeepClonemappingControl,这样我们就只能进行MappingControl.Use.MAPPING_METHOD映射了。


@Mapper(mappingControl = DeepClone.class)
public interface TestMapper {

    @Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH")
    TransformBO toTransformBO(TransformPO transformPO);
}

编译结果,TransformPO中的localDateTimeString没法转换TransformBOLocalDateTimelocalDateTime了,不满足MappingControl.Use.DIRECT,所有进行MappingControl.Use.MAPPING_METHOD判断,也不满足,又不支持MappingControl.Use.BUILT_IN_CONVERSIONMappingControl.Use.COMPLEX_MAPPING,也无法生成子映射,只能报错了。

NoComplexMapping.class、DeepClone.class还在试验阶段。


3.3、将映射目标类型传递给自定义映射器

就是说在使用@Mapper#uses()时,使用的自定义映射器中的方法可以接受Class<T>对象,要使用 @TargetType标注。


@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    TestSevenBO testToBO(TestFivePO testPO);

}
 
@Mapper
public class BaseMapper {

    public <T extends BaseBO> T testToBO(BasePO basePO, @TargetType Class<T> baseBOClass)  {
        try {
            T t = baseBOClass.newInstance();
            t.setId(basePO.getId());
            return t;
        } catch (Exception e) {
            return null;
        }
    }
}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public TestSevenBO testToBO(TestFivePO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestSevenBO testSevenBO = new TestSevenBO();

        testSevenBO.setTest( baseMapper.testToBO( testPO.getTest(), TestSixBO.class ) );

        return testSevenBO;
    }
}
 
TestFivePO testFivePO = new TestFivePO();
TestFourPO testFourPO = new TestFourPO();
testFourPO.setId(1L);
testFivePO.setTest(testFourPO);
System.out.println(testMapper.testToBO(testFivePO));

结果


3.4、将上下文或状态对象传递给自定义方法

@Context标注是上下文或状态对象,这样在生成映射方法时就会调用@Context标注的参数的方法,映射方法和自定义方法参数都需要@Context标注参数。


@Data
public class ThreadLocalContext {

    private ThreadLocal<Object> threadLocal;

    public ThreadLocalContext() {
        threadLocal = new ThreadLocal<>();
    }
}
 
@Mapper
public class BaseMapper {

    public TestSixBO toTestSixBOWithContext(TestFourPO testFourPO, @Context ThreadLocalContext threadLocalContext) {
        TestSixBO testBO = new TestSixBO();
        testBO.setId(testFourPO.getId());
        testBO.setName((String) threadLocalContext.getThreadLocal().get());
        return testBO;
    }

}
 
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    TestSevenBO testToBO(TestFivePO testPO, @Context ThreadLocalContext threadLocalContext);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public TestSevenBO testToBO(TestFivePO testPO, ThreadLocalContext threadLocalContext) {
        if ( testPO == null ) {
            return null;
        }

        TestSevenBO testSevenBO = new TestSevenBO();

        testSevenBO.setTest( baseMapper.toTestSixBOWithContext( testPO.getTest(), threadLocalContext ) );

        return testSevenBO;
    }
}
 
ThreadLocalContext threadLocalContext = new ThreadLocalContext();
threadLocalContext.getThreadLocal().set("xx");

TestFivePO testFivePO = new TestFivePO();
TestFourPO testFourPO = new TestFourPO();
testFourPO.setId(1L);
testFivePO.setTest(testFourPO);
System.out.println(testMapper.testToBO(testFivePO, threadLocalContext));

结果


3.5、区别相同参数类型和返回类型的映射方法


@Mapper
public class BaseMapper {

    public String toString1(String name) {
        return name + "1";
    }

    public String toString2(String name) {
        return name + "2";
    }
}
 
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    TestSixBO testToBO(TestFourPO testPO);

}

toString1、toString2方法的参数类型和返回类型相同,编译直接报错。

所以可以使用@Qualifier来区分。


@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString1 {
}


@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString2 {
}
 
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    @Mapping(target = "name", qualifiedBy = {StringToString1.class})
    TestSixBO testToBO(TestFourPO testPO);

}
 
@Mapper
public class BaseMapper {

    @StringToString1
    public String toString1(String name) {
        return name + "1";
    }

    @StringToString2
    public String toString2(String name) {
        return name + "2";
    }
}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public TestSixBO testToBO(TestFourPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestSixBO testSixBO = new TestSixBO();

		// 使用qualifiedBy中设置的注解的方法
        testSixBO.setName( baseMapper.toString1( testPO.getName() ) );
        testSixBO.setId( testPO.getId() );

        return testSixBO;
    }
}

自定义的@Qualifier@Target还可以设为TYPE


@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString1 {
}
 
@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString2 {
}


@Mapper
@StringToString1
public class BaseMapper {

    public String toString1(String name) {
        return name + "1";
    }

}
 
@Mapper
@StringToString2
public class BaseMapper2 {

    public String toString2(String name) {
        return name + "2";
    }
}
 
@Mapper(uses = {BaseMapper.class, BaseMapper2.class})
public interface TestMapper {

    @Mapping(target = "name", qualifiedBy = {StringToString2.class})
    TestSixBO testToBO(TestFourPO testPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper2 baseMapper2;

    @Override
    public TestSixBO testToBO(TestFourPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestSixBO testSixBO = new TestSixBO();

		// 只使用qualifiedBy中设置的注解的类中的方法
        testSixBO.setName( baseMapper2.toString2( testPO.getName() ) );
        testSixBO.setId( testPO.getId() );

        return testSixBO;
    }
}

还可以使用@Named取代定义一个@Qualifier的接口。


@Mapper
@Named("StringToString1")
public class BaseMapper {

    public String toString1(String name) {
        return name + "1";
    }

}
 
@Mapper
@Named("StringToString2")
public class BaseMapper2 {

    public String toString2(String name) {
        return name + "2";
    }
}
 
@Mapper(uses = {BaseMapper.class, BaseMapper2.class})
public interface TestMapper {

    @Mapping(target = "name", qualifiedByName = {"StringToString2"})
    TestSixBO testToBO(TestFourPO testPO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper2 baseMapper2;

    @Override
    public TestSixBO testToBO(TestFourPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestSixBO testSixBO = new TestSixBO();

        testSixBO.setName( baseMapper2.toString2( testPO.getName() ) );
        testSixBO.setId( testPO.getId() );

        return testSixBO;
    }
}

注意:若类上注解了@Namedmapper中引入了此类,要想使用引入类中自定义的映射方法,映射字段时就必须指定qualifiedByName


@Mapper
@Named("baseMapper")
public class BaseMapper {

    public String toString(String s) {
        return s;
    }

    public String toDateString(Date date) {
        return date.toString();
    }
}
 
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    BaseBO toTestBO(BasePO basePO);

}


@Component
public class TestMapperImpl implements TestMapper {

    @Override
    public BaseBO toTestBO(BasePO basePO) {
        if ( basePO == null ) {
            return null;
        }

        BaseBO baseBO = new BaseBO();

        baseBO.setId( basePO.getId() );
        baseBO.setName( basePO.getName() );
        if ( basePO.getCreateTime() != null ) {
            baseBO.setCreateTime( new SimpleDateFormat().format( basePO.getCreateTime() ) );
        }

        return baseBO;
    }
}

结果没有使用BaseMapper中的自定义映射。


@Mapper(uses = {BaseMapper.class})
public interface TestMapper {

    @Mapping(target = "name", qualifiedByName = {"baseMapper"})
    BaseBO toTestBO(BasePO basePO);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public BaseBO toTestBO(BasePO basePO) {
        if ( basePO == null ) {
            return null;
        }

        BaseBO baseBO = new BaseBO();

        baseBO.setName( baseMapper.toString( basePO.getName() ) );
        baseBO.setId( basePO.getId() );
        if ( basePO.getCreateTime() != null ) {
            baseBO.setCreateTime( new SimpleDateFormat().format( basePO.getCreateTime() ) );
        }

        return baseBO;
    }
}

@BeanMapping#qualifiedBy选择不是字段映射方法,而是对象的工厂方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值