SpringBoot對象拷貝

来源:https://www.cnblogs.com/Naylor/archive/2023/12/25/17926055.html
-Advertisement-
Play Games

目錄概述定義實體類CarsizecarInfo造測試數據Spring BeanUtilsApache BeanUtilsCglib BeanCopierMapStruct性能測試深拷貝or淺拷貝 概述 眾所周知,java世界是由類構成的,各種各樣的類,提供各種各樣的作用,共同創造了一個個的java應 ...


目錄

概述

眾所周知,java世界是由構成的,各種各樣的類,提供各種各樣的作用,共同創造了一個個的java應用。對象是類的實例,在SpringBoot框架中,對象經常需要拷貝,例如資料庫實體拷貝成業務實體,導入實體轉換為業務實體,各種數據傳輸對象之間的拷貝等等。日常開發工作中用到的地方和頻率是相當的高。本文就圍繞對象拷貝來聊聊常用的姿勢(方式)和工具

定義實體類

為了演示對象拷貝將創建幾個實體類和幾個生成測試數據的方法。

Car

car描述了車輛這個業務對象, 其中包含一些常見的基本數據類型的屬性和一個 size類型 的屬性,即包含基本數據類型和引用類型,包含子實體。


import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;

@Data
@Accessors(chain = true)
public class Car implements Serializable {
    private Integer id;
    private String name;
    private String brand;
    private String address;
    private Size size;
    private Double cc;
    /**
     * 扭矩
     */
    private Double torque;
    /**
     * 廠商
     */
    private String manufacturer;
    /**
     * 上市時間
     */
    private LocalDate marketDate;
    /**
     * 售價
     */
    private BigDecimal price;
}

size

size描述了車輛大小,具體來說就是長寬高。


import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;

@Data
@Accessors(chain = true)
public class Size  implements Serializable {
    private Double length;
    private Double width;
    private Double height;
}

carInfo

car對象需要拷貝為carInfo對象,他們兩個大部分屬性都一樣,carInfo比car多了 color ,type


package com.ramble.demo.dto;
import com.ramble.demo.model.Size;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;

@Data
@Accessors(chain = true)
public class CarInfo implements Serializable {
    private Integer id;
    private String name;
    private String brand;
    private String address;
    private Size size;
    private Double cc;
    /**
     * 扭矩
     */
    private Double torque;
    /**
     * 廠商
     */
    private String manufacturer;
    /**
     * 上市時間
     */
    private LocalDate marketDate;
    /**
     * 售價
     */
    private BigDecimal price;
    private String color;
    private Integer type;
}

造測試數據

造測試數據用到了一個工具 javaFaker,通過實例化一個Faker 對象可以輕易的生成測試數據:人名、地址、網址、手機號。。。。。。

gav坐標為:


<dependency>
    <groupId>com.github.javafaker</groupId>
    <artifactId>javafaker</artifactId>
    <version>1.0.2</version>
</dependency>

造數據方法很簡單,一個批量一個單個,可以通過count來控製造的數據量。


    /**
     * 批量造數據
     *
     * @return
     */
    private List<Car> findCar() {
        Faker faker = new Faker(new Locale("zh-CN"));
        List<Car> list = new ArrayList<>();
        int count = 100000;
        for (int i = 0; i < count; i++) {
            list.add(getCar(faker));
        }
        return list;
    }
    
    /**
     * 造數據 - 單個
     *
     * @param faker
     * @return
     */
    private Car getCar(Faker faker) {
        Car car = new Car();
        car.setId(faker.number().numberBetween(1, 999999999));
        car.setName(faker.name().name());
        car.setBrand(faker.cat().breed());
        car.setAddress(faker.address().fullAddress());
        Size size = new Size();
        size.setLength(faker.number().randomDouble(7, 4000, 7000));
        size.setWidth(faker.number().randomDouble(7, 4000, 7000));
        size.setHeight(faker.number().randomDouble(7, 4000, 7000));
        car.setSize(size);
        car.setCc(faker.number().randomDouble(7, 1000, 12000));
        car.setTorque(faker.number().randomDouble(1, 100, 70000));
        car.setManufacturer(faker.name().name());
        Date date = faker.date().birthday();
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDate localDate = instant.atZone(zone).toLocalDate();
        car.setMarketDate(localDate);
        car.setPrice(BigDecimal.valueOf(faker.number().randomDigit()));
        return car;
    }


Spring BeanUtils

這麼常用的功能官方肯定已經集成了,對對對,就是BeanUtils.copyProperties了。順便說一句,遇到找工具類的時候不要盲目baidu,不妨先看看springframework.util這個包下麵的東西。

下麵看看Spring的BeanUtils.copyProperties用法舉例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = new CarInfo();
BeanUtils.copyProperties(car, carInfo);

  • 使用反射實現兩個對象的拷貝
  • 不會對類型進行轉換,如source對象有一個integer類型的id屬性,target對象有一個string類型的id屬性,拷貝之後,target對象為null;同理integer 無法拷貝到long
  • 官方出品,無需引入其他依賴,推薦指數8顆星

Apache BeanUtils

apache-common系列的東西,java開發多多少少會用到一些,它本身也積累了很多經驗提供一些有用並常用的工具和組件。

針對BeanUtils 使用前需要引入pom


<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

下麵看看Apache的BeanUtils.copyProperties 用法舉例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = new CarInfo();
try {
 org.apache.commons.beanutils.BeanUtils.copyProperties(carInfo, car);
} catch (IllegalAccessException e) {
    throw new RuntimeException(e);
} catch (InvocationTargetException e) {
    throw new RuntimeException(e);
}

  • 強制要處理異常
  • source對象和target對象相對Spring來說是顛倒的
  • 通過反射實現
  • 不會對類型進行轉換,如source對象有一個integer類型的id屬性,target對象有一個string類型的id屬性,拷貝之後,target對象為null;同理integer 無法拷貝到long
  • 性能稍微遜色與Spring,推薦指數7顆星

Cglib BeanCopier

BeanCopier使用起來稍微有點複雜,需要先實例化一個BeanCopier對象,然後再調用其copy方法完成對象拷貝

使用前需要引入如下pom


<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.0</version>
</dependency>

下麵看看 Cglib 的 BeanCopier 用法舉例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo item = new CarInfo();
inal BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
copier.copy(car, item, null);

  • BeanCopier雖然使用複雜,且要引入依賴,但是性能出眾
  • 對於數據量小的情況不沒有必要使用這個,推薦指數5顆星
  • 對於數據量大,比如5W個對象拷貝為另外一個對象,這種場景雖然少但是一旦發生了Spring BeanUtils處理起來就非常耗時,這種情況下推薦指數9顆星

MapStruct

MapStruct 是一個代碼生成器,它基於約定生成的映射代碼使用的是普通的方法調用,因此快速、類型安全且易於理解。MapStruct 本質沒有太多科技與狠活,就是幫助開發人員編寫getA,setB這樣的樣板代碼,並提供擴展點,比如將carType映射為type。

使用前需要引入如下pom


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

下麵看看MapStruct用法舉例

使用前需要先定義一個介面,編寫轉換邏輯,而後通過調用介面方法獲取轉換後的結果。

CarInfoMapper


import com.ramble.demo.dto.CarInfo;
import com.ramble.demo.model.Car;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface CarInfoMapper {
    CarInfoMapper INSTANCT = Mappers.getMapper(CarInfoMapper.class);
    CarInfo carToCarInfo(Car car);
}

  • Mapper:將此介面標註為用來轉換bean的介面,併在編譯過程中生成相應的實現類
  • carToCarInfo,聲明一個轉換方法,並將source作為方法入參,target作為方法出參
  • INSTANCT,按照約定,介面聲明一個INSTANCT成員,提供給訪問者消費
  • 一個介面可以有多個轉換方法
  • 可通過在方法上添加@Mapping註解,將不同屬性名子的屬性進行轉換
  • 可以將枚舉類型轉換為字元串
  • 性能出眾,術業有專攻,在處理複雜轉換和大數據量的時候很有必要
  • 因為要引入依賴並且需要單獨編寫介面,所以對於簡單的轉換還是推薦Spring,這種情況下推薦指數5顆星
  • 如果在大數據量的情況,並且轉換相對複雜,推薦指數10顆星

調用 CarInfoMapper 進行對象拷貝


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = CarInfoMapper.INSTANCT.carToCarInfo(car);

性能測試

原則上, 性能測試是多維度的,速度、CPU消耗、記憶體消耗等等,本文僅考慮速度,只為說明問題,不為得到測試數據

測試代碼邏輯,使用for結合faker生成一個大的List,然後用上述4種方式做屬性拷貝。使用StopWatch記錄程式運行時間。

測試代碼如下:


/**
 * 測試4種方式的處理速度
 */
@GetMapping("/test1")
public void test1() {
    Faker faker = new Faker(new Locale("zh-CN"));
    List<Car> carList = findCar();
    StopWatch sw = new StopWatch("對象拷貝速度測試");
    sw.start("方式1:Spring BeanUtils.copyProperties");
    List<CarInfo> list1 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        BeanUtils.copyProperties(x, item);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list1.add(item);
    });
    sw.stop();
    sw.start("方式2:apache BeanUtils.copyProperties");
    List<CarInfo> list2 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        try {
            org.apache.commons.beanutils.BeanUtils.copyProperties(item, x);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list2.add(item);
    });
    sw.stop();
    sw.start("方式3:BeanCopier");
    List<CarInfo> list3 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        final BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
        copier.copy(x, item, null);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list3.add(item);
    });
    sw.stop();
    sw.start("方式4:MapStruct");
    List<CarInfo> list4 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = CarInfoMapper.INSTANCT.carToCarInfo(x);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list4.add(item);
    });
    sw.stop();
    String s = sw.prettyPrint();
    System.out.println(s);
    double totalTimeSeconds = sw.getTotalTimeSeconds();
    System.out.println("總耗時:" + totalTimeSeconds);
}

測試結果如下

10W 數據量


StopWatch '對象拷貝速度測試': running time = 5382233200 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
2336099000  043%  方式1:Spring BeanUtils.copyProperties
2322316400  043%  方式2:apache BeanUtils.copyProperties
442981700  008%  方式3:BeanCopier
280836100  005%  方式4:MapStruct

總耗時:5.3822332

100W 數據量


StopWatch '對象拷貝速度測試': running time = 58689078300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
25199532100  043%  方式1:Spring BeanUtils.copyProperties
27179965900  046%  方式2:apache BeanUtils.copyProperties
3629756500  006%  方式3:BeanCopier
2679823800  005%  方式4:MapStruct

總耗時:58.6890783

深拷貝or淺拷貝

上述四種方式均為淺拷貝。可通過如下測試代碼驗證


 /**
 * 測試4種方式是淺拷貝還是深拷貝
 */
@GetMapping("/test2")
public void test2() {
    Faker faker = new Faker(new Locale("zh-CN"));
    Car car = getCar(faker);
    log.info("car={}", JSON.toJSONString(car));
    CarInfo carInfo = new CarInfo();
    //方式1
//        BeanUtils.copyProperties(car, carInfo);
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
////        Size s = new Size();
////        s.setHeight(10d);
////        car.setSize(s);
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式2
//        try {
//            org.apache.commons.beanutils.BeanUtils.copyProperties(carInfo, car);
//        } catch (IllegalAccessException e) {
//            throw new RuntimeException(e);
//        } catch (InvocationTargetException e) {
//            throw new RuntimeException(e);
//        }
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
//
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式3
//        final BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
//        copier.copy(car, carInfo, null);
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
//
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式4
        carInfo = CarInfoMapper.INSTANCT.carToCarInfo(car);
        Size sizeNew = car.getSize();
        sizeNew.setLength(0d);
        sizeNew.setWidth(0d);
        sizeNew.setHeight(0d);
        car.setName("新名字");
        log.info("carInfo={}", JSON.toJSONString(carInfo));
}

驗證邏輯為,在完成copy後修改car.size 的值,發現carInfo.size隨之改變,說明是淺拷貝。

郵箱:[email protected] 技術交流QQ群:1158377441 歡迎關註我的微信公眾號【TechnologyRamble】,後續博文將在公眾號首發: pSr8iCD.png
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹`TreeWidget`與`QCharts`的常用方法及靈活運用。在之前的文章中筆者介紹瞭如何使用`QCharts... ...
  • 在本文中,我們將介紹 IoC(控制反轉)和 DI(依賴註入)的概念,以及如何在 Spring 框架中實現它們。 什麼是控制反轉? 控制反轉是軟體工程中的一個原則,它將對象或程式的某些部分的控制權轉移給容器或框架。我們最常在面向對象編程的上下文中使用它。 與傳統編程相比,傳統編程中我們的自定義代碼調用 ...
  • 哈嘍大家好,我是鹹魚 我們在使用 sorted() 或 map() 函數的時候,都會看到裡面有一個 key 參數 其實這個 key 參數也存在於其他內置函數中(例如 min()、max() 等),那麼我們今天就來瞭解一下 key 參數的含義以及用途吧! 原文:https://www.thepytho ...
  • C 語言中的條件和 if...else 語句 您已經學習過 C 語言支持數學中的常見邏輯條件: 小於:a < b 小於或等於:a <= b 大於:a > b 大於或等於:a >= b 等於:a == b 不等於:a != b 您可以使用這些條件來根據不同的決策執行不同的操作。 C 語言具有以下條件語 ...
  • Spring 可能成為您的所有企業應用程式的一站式商店。但是,Spring 是模塊化的,允許您挑選適用於您的模塊,而無需引入其他模塊。下麵的部分提供了 Spring Framework 中所有可用模塊的詳細信息。Spring Framework 提供了大約20個模塊,可以根據應用程式要求使用。 核心 ...
  • Spring 是用於企業 Java 應用程式開發的最流行的應用程式開發框架。全球數百萬開發人員使用 Spring Framework 創建高性能、易於測試和可重用的代碼。Spring Framework 是一個開源的 Java 平臺。它最初由 Rod Johnson 編寫,並於 2003 年 6 月 ...
  • 前言 在日常工作和學習中,有很多地方都需要發送HTTP請求,本文以Java為例,總結髮送HTTP請求的多種方式 HTTP請求實現過程: GET 創建遠程連接 設置連接方式(get、post、put…) 設置連接超時時間 設置響應讀取時間 發起請求 獲取請求數據 關閉連接 POST 創建遠程連接 設置 ...
  • 基於php的服裝商城的設計與實現 1.引言 隨著互聯網的普及和電子商務的快速發展,網路購物已成為人們日常生活的一部分。網路購物商城網站作為電子商務的重要平臺,具有便捷性、高效性和不受時空限制等優勢,越來越受到消費者的青睞。本文旨在設計和實現一個功能完善、操作簡便的網路購物商城網站,以滿足用戶和商家的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...