1. 原始轉換
提起物件轉換,每個程式設計師都不陌生,比如專案中經常涉及到的DO、DTO、VO之間的轉換,舉個例子,假設現在有個OrderDTO,定義如下所示:
public class OrderDTO {
private long id;
private Long userId;
private String orderNo;
private Date gmtCreated;
// 省略get、set方法
}
有個OrderVO,定義如下所示:
public class OrderVO {
private long id;
private long userId;
private String orderNo;
private Date gmtCreated;
// 省略get、set方法
}
如果不使用任何轉換工具,程式碼是下面這樣的:
public static void main(String[] args) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(1L);
orderDTO.setUserId(123L);
orderDTO.setOrderNo("20210518000001");
orderDTO.setGmtCreated(new Date());
OrderVO orderVO = new OrderVO();
orderVO.setId(orderDTO.getId());
orderVO.setUserId(orderDTO.getUserId());
orderVO.setOrderNo(orderDTO.getOrderNo());
orderVO.setGmtCreated(orderDTO.getGmtCreated());
System.out.println(orderVO.getId());
System.out.println(orderVO.getUserId());
System.out.println(orderVO.getOrderNo());
System.out.println(orderVO.getGmtCreated());
}
執行結果:
2. 使用BeanUtils.copyProperties轉換
因為專案中類似上面的轉換多而繁瑣,所以很多公司的專案中會使用Spring框架裡的BeanUtils.copyProperties來做物件轉換,程式碼如下所示:
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);
一行程式碼搞定,很方便,執行結果也和原來一模一樣。
不過這個工具帶來便利的同時,也帶來了很多問題,稍微不注意就會踩坑,接下來就總結下使用這個工具常見的幾個坑。
3. 踩坑經歷
3.1 包裝型別轉基本型別問題
java.lang.IllegalArgumentException
細心的你可能會發現,OrderDTO中的userId欄位,我定義的是Long型別:
而OrderVO中的userId欄位,我定義的是long型別:
然後我們執行下下面所示的程式碼:
public static void main(String[] args) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(1L);
orderDTO.setUserId(null);
orderDTO.setOrderNo("20210518000001");
orderDTO.setGmtCreated(new Date());
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);
}
會看到程式碼拋了java.lang.IllegalArgumentException
異常:
3.2 空格問題
假設OrderVO的orderNo欄位,是使用者自定義的,使用者不小心輸入了空格,使用BeanUtils.copyProperties後,空格會帶入到OrderDTO的orderNo欄位,如果不小心,就會把髒資料落到資料庫(而我們希望的是去除空格再落庫的),造成一系列後續問題:
public static void main(String[] args) {
OrderVO orderVO = new OrderVO();
orderVO.setId(1L);
orderVO.setUserId(123L);
// 模擬空格場景
orderVO.setOrderNo(" 20210518000001 ");
orderVO.setGmtCreated(new Date());
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderVO, orderDTO);
System.out.println(orderDTO.getOrderNo());
}
執行結果:
3.3 查詢不到欄位引用
使用BeanUtils.copyProperties後,會看到欄位並沒有引用,其實是有用到的,如下圖所示:
有些小夥伴在看程式碼時,看到欄位沒有地方引用,可能就忍不住想刪掉,結果就導致真正使用該欄位的地方取不到值,產生bug。
3.4 前端誤傳欄位,直接把資料庫覆蓋了
如果介面定義的比較嚴謹,理論上是不應該存在這種情況的,不過凡事總有特殊,這裡舉個介面不嚴謹導致資料被覆蓋的例子。
假如OrderVO和OrderDTO有如下2個欄位:
/**
* 已收金額
* 單位:分
*/
private Long receivedAmount;
/**
* 備註
*/
private String remark;
正常情況下,後端只應該使用前端傳遞的remark欄位,receivedAmount欄位不應該使用,但假如使用者修改訂單備註時,前端不小心傳遞了receivedAmount欄位,並且賦值為null,這時使用BeanUtils.copyProperties後,OrderDTO裡的receivedAmount欄位就也為null,如果後端不知道前端傳遞了這個欄位並且操作DB不夠嚴謹,就會導致訂單的已收金額被清空,很恐怖,而且不好排查原因。
4. 外掛推薦
雖然BeanUtils.copyProperties
工具提供了便利,但帶來的問題也很多,因此很多公司(包含我現在所在的公司)都禁止在專案中使用該工具。
但重複的寫物件轉換,實在是太繁瑣,效率太低了,這裡推薦一個IDEA的外掛GenerateAllSetter,可以一鍵生成物件的set方法,非常方便,如下圖所示:
外掛使用:
在需要生成set方法的物件上,按快捷鍵Option+Enter(Windows是Alt+Enter),會看到下圖所示的選項:
點選後會自動生成所有欄位(沒有預設值)的賦值語句:
如果生成賦值語句時想帶預設值,可以使用另一個選項:
效果如下所示: