MapStruct文件(四)——轉換
目錄
3.1.1、列舉、BigDecimal、Date、Timestamp
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.class、DeepClone.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修改為,使用DeepClone的mappingControl,這樣我們就只能進行MappingControl.Use.MAPPING_METHOD映射了。
@Mapper(mappingControl = DeepClone.class)
public interface TestMapper {
@Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH")
TransformBO toTransformBO(TransformPO transformPO);
}
編譯結果,TransformPO中的localDateTime是String沒法轉換TransformBO中LocalDateTime的localDateTime了,不滿足MappingControl.Use.DIRECT,所有進行MappingControl.Use.MAPPING_METHOD判斷,也不滿足,又不支援MappingControl.Use.BUILT_IN_CONVERSION和MappingControl.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;
}
}
注意:若類上註解了@Named,mapper中引入了此類,要想使用引入類中自定義的對映方法,對映欄位時就必須指定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選擇不是欄位對映方法,而是物件的工廠方法。