久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    DTO、DO、VO,你是怎么轉換的?

     jacklopy 2025-09-03

    2023年,我剛從外包跳到一家新能源車企時,對DTO、DO、VO這些概念是真懵——光聽名字就繞,更別說實際用了。后來踩了幾次坑才慢慢明白,這些東西看著復雜,核心就倆字:省事

    先說說DTO、DO、VO到底解決啥問題

    剛開始寫代碼,我總喜歡一個實體類用到底:查數據庫用它,接口入參用它,返回給前端還?它。直到有一次,產品突然說要大改接口返回字段,我改著改著發現不對勁——數據庫實體(比如UserDO)里有密碼、創建時間這些敏感字段,之前直接返回給前端了!更坑的是,因為實體類被Service、Controller、前端共用,改一個字段得全鏈路檢查,差點沒改崩。

    后來才明白,DTO、DO、VO的分層,本質是給不同層劃清界限

    • DO(Data Object):和數據庫表一一對應,只在Dao層和Service層之間用,里面全是數據庫字段(比如user_idpasswordcreate_time)。
    • DTO(Data Transfer Object):前端傳給后端的參數載體,比如用戶登錄時傳的usernamepassword,只包含接口需要的字段,多余的一概不要。
    • VO(View Object):后端返回給前端的結果,會根據前端需求“裁剪”DO里的字段,比如隱藏密碼,只返回usernamenickname這些前端需要展示的。

    這么一分層,好處立馬就顯出來了。就像我之前遇到的產品大改:Service層邏輯全重寫了,查的表都換了,但因為Controller層只認DTO和VO,我只要保證入參DTO和返回VO的格式不變,前端完全不用改,Swagger文檔也沒動——這就是解耦的威力。

    其實不用死記BO、PO這些細分概念,日常開發里,把實體類分成這三類基本夠用了。咱們看個簡單例子:// 前端傳參:只需要name(DTO)
    public class  UserDTO {
        private  String name;
        // getter/setter
    }

    // 數據庫映射:包含id、name、password等(DO)
    public class  UserDO {
        private  Long id;
        private  String name;
        private  String password;
        // getter/setter
    }

    // 返回給前端:只包含id和name(VO)
    public class  UserVO {
        private  Long id;
        private  String name;
        // getter/setter
    }

    Controller接收DTO,Service把DTO轉成DO查庫,再把DO轉成VO返回——每層各司其職,改起來就不會牽一發而動全身。

    對象轉換的坑:深淺拷貝

    分層后繞不開一個問題:DO、DTO、VO之間總得轉換吧?比如把UserDO轉成UserVO,總不能手動一個個set字段(字段多了能寫哭)。最常用的就是Spring的BeanUtils.copyProperties,但這東西有倆坑,我踩過好幾次。

    先看個例子:如果UserDO里嵌套了一個Department對象,用BeanUtils拷貝會咋樣?// DO里有個子對象
    @Data
    public  class  UserDO {
        private  String name;
        private  Department dept; // 部門子對象
    }

    @Data
    public  class  Department {
        private  String deptName;
    }

    BeanUtils.copyProperties(do, vo)拷貝后,如果你改了原UserDO里的dept.deptName,會發現UserVO里的dept.deptName也跟著變了!這就是淺拷貝的問題:它只拷貝對象本身的字段,但對子對象(比如dept)只拷貝引用,原對象和新對象的子對象其實指向同一個內存地址。

    那啥是深拷貝?就是不僅拷貝主對象,連里面的子對象也一起復制一份,兩邊改了互不影響。比如用序列化的方式:把對象轉成字節流,再讀回來,相當于重新創建了一個完全獨立的對象。

    自己封裝個MyBeanUtils,解決這些破事

    既然Spring的BeanUtils不夠用,不如自己封裝一個工具類,既支持淺拷貝,也能搞定深拷貝,還能批量轉換List(日常開發里轉List的場景太多了)。

    1. 淺拷貝:應付簡單對象

    大部分時候,對象里沒有子對象,淺拷貝就夠用了。直接封裝一個copyBean方法,再擴展一個copyList批量轉換:public  finalclass  MyBeanUtils {
        private static final Logger log = LoggerFactory.getLogger(MyBeanUtils.class );

        // 單個對象淺拷貝
        public static  <T> copyBean(Object source, Class<T> targetClass) {
            try {
                T target = targetClass.newInstance(); // 新建目標對象
                BeanUtils.copyProperties(source, target); // 拷貝屬性
                return  target;
            } catch (Exception e) {
                log.error("對象拷貝失敗", e);
                throw new   RuntimeException("對象轉換出錯");
            }
        }

        // List批量淺拷貝(比如List<UserDO>轉List<UserVO>)
        public static  <T> List<T> copyList(List<?> sourceList, Class<T> targetClass) {
            if (sourceList == nullreturn null;

            List<T> targetList = new   ArrayList<>();
            for (Object source : sourceList) {
                targetList.add(copyBean(source, targetClass));
            }
            return  targetList;
        }
    }

    用起來賊簡單,一行代碼搞定:// 單個對象轉換
    UserVO vo = MyBeanUtils.copyBean(userDO, UserVO.class );

    // List轉換
    List<UserVO> voList = MyBeanUtils.copyList(doList, UserVO.class );

    2. 深拷貝:處理嵌套對象

    如果對象里有子對象,就得用深拷貝了。最常用的方式是序列化(要求對象實現Serializable):public  finalclass  MyBeanUtils {
        // 深拷貝(基于序列化)
        public static  <T> List<T> deepCopy(List<T> sourceList) {
            try {
                // 序列化:把對象轉成字節流
                ByteArrayOutputStream byteOut = new   ByteArrayOutputStream();
                ObjectOutputStream out = new   ObjectOutputStream(byteOut);
                out.writeObject(sourceList);

                // 反序列化:把字節流轉回對象(全新對象)
                ByteArrayInputStream byteIn = new   ByteArrayInputStream(byteOut.toByteArray());
                ObjectInputStream in = new   ObjectInputStream(byteIn);
                return  (List<T>) in.readObject();
            } catch (Exception e) {
                log.error("深拷貝失敗", e);
                throw new   RuntimeException("深拷貝出錯");
            }
        }
    }

    或者用JSON工具(比如Jackson)也能實現深拷貝,原理差不多:把對象轉成JSON字符串,再解析成新對象,缺點是性能比序列化稍差,但勝在不用實現Serializable// 用Jackson做深拷貝
    ObjectMapper mapper = new   ObjectMapper();
    String json = mapper.writeValueAsString(sourceList);
    List<UserVO> newList = mapper.readValue(json, new   TypeReference<List<UserVO>>() {});

    實際開發中常見的轉換場景

    除了簡單的DO轉VO,還有些場景也很常用,比如:

    1. 組合多個對象到一個TO里

    比如查商品列表時,需要把商品信息(ProductDO)和對應的用戶信息(UserDO)合并到ProductExtendsTO里返回:public  List<ProductExtendsTO> getProductList() {
        // 1. 查商品列表
        List<ProductDO> productList = productDao.list();
        if (CollectionUtils.isEmpty(productList)) {
            return  Collections.emptyList();
        }

        // 2. 批量查商品對應的用戶(避免N+1查詢)
        List<Long> userIds = productList.stream()
                .map(ProductDO::getUserId)
                .collect(Collectors.toList());
        List<UserDO> userList = userDao.listByIds(userIds);
        // 轉成Map,方便根據userId快速取用戶
        Map<Long, UserDO> userMap = userList.stream()
                .collect(Collectors.toMap(UserDO::getId, u -> u));

        // 3. 組合數據到TO
        return  productList.stream().map(product -> {
            ProductExtendsTO to = new   ProductExtendsTO();
            // 拷貝商品基本信息
            MyBeanUtils.copyBean(product, to);
            // 從userMap里取用戶信息,設置到TO里
            UserDO user = userMap.get(product.getUserId());
            if (user != null) {
                to.setUserName(user.getName());
                to.setUserAge(user.getAge());
            }
            return  to;
        }).collect(Collectors.toList());
    }

    2. 空集合處理

    轉List時一定要注意空指針!如果原List是null,直接遍歷會報錯。可以用Optional或者先判斷空:// 安全的List轉換
    public  List<UserVO> getUserVos(List<UserDO> doList) {
        // 用Optional避免null,空集合返回空List而非null
        return  Optional.ofNullable(doList)
                .map(list -> list.stream()
                        .map(doObj -> MyBeanUtils.copyBean(doObj, UserVO.class ))
                        .collect(Collectors.toList()))
                .orElse(Collections.emptyList())
    ;
    }

    最后說句大實話

    其實不用糾結工具類的性能——和數據庫查詢、網絡請求比起來,對象轉換的耗時幾乎可以忽略。日常開發里,能少寫重復代碼、少踩坑才是關鍵。

    如果團隊里沒有統一的轉換工具,Spring的BeanUtils+自己封裝的copyList就夠用了;如果字段映射復雜(比如字段名不一樣),可以試試MapStruct(編譯時生成轉換代碼,性能好還支持自定義映射)。

    說到底,DTO、DO、VO這些“O”和轉換工具,都是為了讓代碼更規整、改起來更省心。剛開始可能覺得麻煩,但用熟了就會發現:真香!


      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 国内综合精品午夜久久资源| 东北女人毛多水多牲交视频| 国产在线高清视频无码| 亚洲国产AV无码一区二区三区| 亚洲JIZZJIZZ中国少妇中文| 2020年最新国产精品正在播放| 狠狠婷婷色五月中文字幕| 红杏亚洲影院一区二区三区| 亚洲国产欧美一区二区好看电影| 国产在线高清视频无码| 公喝错春药让我高潮| 韩国青草无码自慰直播专区| 97在线视频免费人妻| 小嫩批日出水无码视频免费| 国产乱子影视频上线免费观看| 免费国产一区二区不卡| 亚洲性色AV一区二区三区| 日韩中文字幕高清有码| 18禁网站免费无遮挡无码中文 | 国产在线午夜不卡精品影院| 少妇又色又紧又爽又刺激视频| 欧美不卡无线在线一二三区观| 无码人妻一区二区三区免费N鬼沢| gogogo高清在线观看视频中文 | 亚洲高清最新AV网站| 97视频精品全国免费观看| 国产三级精品三级| 国产超高清麻豆精品传媒麻豆精品| 丰满少妇被猛男猛烈进入久久| 漂亮人妻中文字幕丝袜| 国产丝袜视频一区二区三区| 色九月亚洲综合网| 亚洲国产精品综合久久20| 99久久国产综合精品女图图等你| 日本亚洲一区二区精品| 国产乱女乱子视频在线播放| 最新国产AV最新国产在钱| 性奴sm虐辱暴力视频网站| 不卡高清AV手机在线观看| 国产亚洲精品AA片在线播放天| 中文字幕亚洲无线码在线一区|