當前位置: 妍妍網 > 碼農

強烈推薦 MapperStruct,不要用 BeanUtils.copyProperties 拷貝數據

2024-05-24碼農

作者:radient
連結:https://juejin.cn/post/7333458486435987494

為啥建議用MapperStruct,不建議用BeanUtils.copyProperties拷貝數據?

在實際的業務開發中,我們經常會碰到BO、PO、DTO等物件內容之間的賦值,當內容較多的時候我們使用get,set的方式進行賦值的工作量相對較大,因此很多人會選擇使用spring提供的拷貝工具BeanUtils的copyProperties方法完成物件之間內容的拷貝。

透過這種方式可以很大程度上降低我們手動編寫物件內容賦值程式碼的工作量,既然它那麽方便為什麽還不建議使用呢?

我總結為以下幾點:

內容型別不一致導致拷貝失敗

在實際開發中,很可能會出現同一欄位在不同的類中定義的型別不一致,例如Id,可能在A類中定義的型別為Long,在B類中定義的型別為String,此時如果使用BeanUtils.copyProperties進行拷貝,就會出現拷貝失敗的現象,導致對應的欄位為null

同一欄位分別使用包裝型別和基本型別

如果同一個欄位分別使用包裝類和基本型別,在沒有傳遞實際值的時候,會出現異常

註意,如果一個布爾型別的內容分別使用了基本型別和包裝型別,且內容名如果使用is開頭,例如isSuccess,也會導致拷貝失敗

null值覆蓋導致數據異常

在業務開發時,我們可能會有部份欄位拷貝的需求,被拷貝的數據裏面如果某些欄位有null值存在,但是對應的需要被拷貝過去的數據的相同欄位的值並不為null,如果直接使用 BeanUtils.copyProperties 進行數據拷貝,就會出現被拷貝數據的null值覆蓋拷貝目標數據的欄位,導致原有的數據失效

簡單舉個例子,我有一個BO(已經存在了A內容且有值),需要拷貝DTO中除了A內容的其他內容(DTO中的A內容為null),此時使用BeanUtils.copyProperties就會導致BO中的A內容的值被DTO中A的null給覆蓋了

雖然可以使用 BeanUtils.copyProperties 的多載方法,配合自訂的 ConvertUtilsBean 來實作部份欄位的拷貝,但是這麽做本身也比較復雜,也就失去了使用BeanUtils.copyProperties 拷貝數據的意義,因此也不推薦這麽做。

底層實作為反射拷貝效率低

導包錯誤導致拷貝數據異常

# 為什麽使用MapperStruct

為什麽使用MapperStruct? ---> 快,對就是因為很快,效能比BeanUtils.copyProperties快很多很多

放張參考圖給大家參考

那麽為什麽MapperStruct比BeanUtils.copyProperties快?

  • 避免了反射操作的效能開銷

  • 預編譯的高效程式碼執行更快 (看到這裏就懵了,預編譯?咋回事啊到底)

  • 什麽是預編譯? --- 在計畫啟動的時候,自動幫你生成物件拷貝的程式碼,拒絕使用的時候才去透過反射呼叫get/set...

    具體的可以往下看

    @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2024-02-07T00:50:05+0800", comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)")public classXXXDTOConverterImplimplementsXXXDTOConverter{@Overridepublic XXXBO convertDtoToBo(XXXDTO XXXDTO){if ( XXXDTO == null ) {returnnull; } XXXBO XXXBO = new XXXBO(); XXXBO.setId( XXXDTO.getId() ); XXXBO.setLabelName( XXXDTO.getLabelName() ); XXXBO.setSortNum( XXXDTO.getSortNum() ); XXXBO.setCategoryId( XXXDTO.getCategoryId() );return XXXBO; }}

    # 怎麽使用MapperStruct

    Maven引入依賴

    <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version></dependency>

    編寫程式碼

    @Datapublic classSubjectLabelDTOimplementsSerializable{private String labelName;}@Datapublic classSubjectLabelBOimplementsSerializable{private String labelName;}

    @MapperpublicinterfaceSubjectLabelDTOConverter{ SubjectLabelDTOConverter INSTANCE = Mappers.getMapper(SubjectLabelDTOConverter. class);SubjectLabelBO convertDtoToBo(SubjectLabelDTO subjectLabelDTO);}

    public Result<Boolean> add( SubjectLabelDTO subjectLabelDTO){ SubjectLabelBO subjectLabelBO = SubjectLabelDTOConverter.INSTANCE .convertDtoToBo(subjectLabelDTO);}

    # 總結

    Mapper-struct同樣也是淺拷貝,需要進行深拷貝,就寫多個Converter把需要深拷貝的再轉一次吧,如果要配置深拷貝太麻煩了

    Mapper-struct也存在一點小問題,跟lombok在配合的時候會出現問題,如果mapperStruct的依賴放在lombok的依賴上面的話 就會出現在物件復制的時候,會將原有數據全變為null的情況,所以一定要將lombok的依賴放mapperStruct上面 xml

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version></dependency>

    熱門推薦