你還在用BeanUtils作對象拷貝嗎?

說十遍不如作一次 Better do it once than say it ten times.前端

背景:

最近開始負責一個數據量比較大的業務模塊,要求把相關數據所有查出來,不分頁,要組樹結構,數據從dao層到service由entity對象到Vo對象給前端展現。那麼就涉及到對象拷貝,開始的時候用的Spring的BeanUtils作對象轉換,並無什麼問題,後來到了測試那裏,加大數據量,發現接口愈來愈慢,開始覺得數據庫查詢問題,把sql搬到數據庫運行,發現並不慢,由於關鍵字段基本都走了索引,不會很慢,後來一步一步找,發現是BeanUtils耗時引發的,而後就有了下面的關於三種對象拷貝方式的實踐spring

實踐:Apache的BeanUtils、Spring的BeanUtils、Mapstruct

這裏可能不少小夥伴只用過Spring的BeanUtils,其他的兩種沒用過,不過不要緊,接下來來個簡單的測試sql

  • 引入maven依賴,爲了測試方便這裏直接建立的SpringBoot項目,用的junit測試
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Spring的BeanUtils -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- Apache的BeanUtils依賴 -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.3</version>
        </dependency>
        <!-- mapstruct依賴 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>1.2.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.2.0.Final</version>
        </dependency>
    </dependencies>

複製代碼
  • 類結構

  • 4個簡單類
/**
 * @description user 實體
 * @author Wanm
 * @date 2020/7/8 21:37
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String id;

    private String name;

    private Integer age;

    private String address;

    private String sex;

}

/**
 * @description userVo
 * @author Wanm
 * @date 2020/7/8 21:37
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVo {

    private String id;

    private String name;

    private Integer age;

    private String address;

    private String sex;

}


/**
 * @description UserTransfer
 * @author Wanm
 * @date 2020/7/8 21:37
 */
@Mapper
public interface UserTransfer {

    /**
     * entity轉vo
     * @param user
     * @return
     */
    List<UserVo> entityToVo(List<User> user);
}


/**
 * 測試類
 */
@SpringBootTest
class MapstructApplicationTests {


    @Test
    void contextLoads() {
        //這裏拿100w數據作數據初始化
        List<User> userList = new ArrayList<User>();
        for (int i = 0; i < 1000000; i++) {
            User user = new User(UUID.randomUUID().toString(),UUID.randomUUID().toString(),i,UUID.randomUUID().toString(),UUID.randomUUID().toString());
            userList.add(user);
        }
        System.out.println(userList.get(0));
        System.out.println("開始拷貝---------------------------------------");
        testBeanUtils(userList);
        testSpringBeanUtils(userList);
        testMapStruct(userList);
    }

    /**
     *  Apache的BeanUtils
     * @param userList
     */
    public static void testBeanUtils(List<User> userList){
        long start = System.currentTimeMillis();
        List<UserVo> userVos = new ArrayList<>();
        userList.forEach(item->{
            UserVo userVo  = new UserVo();
            try {
                BeanUtils.copyProperties(userVo,item);
                userVos.add(userVo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        System.out.println(userVos.get(0));
        System.out.println("集合大小參數驗證"+userVos.size()+"Apache的BeanUtils耗時:"+(end-start)+"ms");
    }

    /**
     * Spring的BeanUtils
     * @param userList
     */
    public static void testSpringBeanUtils(List<User> userList){
        long start = System.currentTimeMillis();
        List<UserVo> userVos = new ArrayList<>();
        userList.forEach(item->{
            UserVo userVo  = new UserVo();
            try {
                org.springframework.beans.BeanUtils.copyProperties(item,userVo);
                userVos.add(userVo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        System.out.println(userVos.get(0));
        System.out.println("集合大小參數驗證"+userVos.size()+"Spring的BeanUtils耗時:"+(end-start)+"ms");
    }

    /**
     * mapStruct拷貝
     * @param userList
     */
    public  void testMapStruct(List<User> userList){
        long start = System.currentTimeMillis();
        List<UserVo> userVos = Mappers.getMapper(UserTransfer.class).entityToVo(userList);
        long end = System.currentTimeMillis();
        System.out.println(userVos.get(0));
        System.out.println("集合大小參數驗證"+userVos.size()+"mapStruct耗時:"+(end-start)+"ms");
    }

}
複製代碼
  • 實際開發中vo類屬性字段會比實體類少不少。這裏只作測試,下面看測試結果

  • 有沒有看到MapStruct的耗時41ms,完勝有木有,數據量越大越能看到差別,下圖是針對不一樣數據量的耗時作具體分析:

Apache Spring MapStruct
1000 67ms 10ms 2ms
1w 174ms 35ms 3ms
10w 808ms 69ms 9ms
100w 5620ms 336ms 42ms

由此能夠看出數據量越大MapStruct>Spring>Apache,這個性能優點愈來愈明顯,平常開發中對象拷貝只是代碼中的一小部分邏輯,若是數據量大的話仍是建議你們使用MapStruct的方式,提升接口的性能。數據量不大的話Spring的BeanUtils也行,仍是看實際業務場景!!!


原理:MapStruct使用註解處理器生成實現類,實現類內部是原生的new對象,而後SetXxx/getXxx方式賦值進行數據拷貝的,相似lombok,看實現類的.class

public class UserTransferImpl implements UserTransfer {
    public UserTransferImpl() {
    }

    public List<UserVo> entityToVo(List<User> user) {
        if (user == null) {
            return null;
        } else {
            List<UserVo> list = new ArrayList(user.size());
            Iterator var3 = user.iterator();

            while(var3.hasNext()) {
                User user1 = (User)var3.next();
                list.add(this.userToUserVo(user1));
            }

            return list;
        }
    }

    protected UserVo userToUserVo(User user) {
        if (user == null) {
            return null;
        } else {
            UserVo userVo = new UserVo();
            userVo.setId(user.getId());
            userVo.setName(user.getName());
            userVo.setAge(user.getAge());
            userVo.setAddress(user.getAddress());
            userVo.setSex(user.getSex());
            return userVo;
        }
    }
}
複製代碼

Spring和Apache的BeanUtils則是用到了反射機制,內部實現也是有區別的,特別校驗這塊,至於他們之間的詳細區別,交給小夥伴們本身去探尋啦~


總結:經過此次簡單的測試,掌握了三種拷貝方式的實現方式和性能差別,不過使這塊仍是建議小夥伴們根據實際場景使用,MapStruct的性能確實好,當是須要引入第三方依賴,若是數據量這塊不大Spring自帶的BeanUtils夠用。



Ps:寫着寫着不知不覺零點了,不行了趕忙睡覺,狗命要緊,小夥伴們三連哦!!!


我是顏青,不是顏真卿,一個奮鬥在互聯網一線的小開發仔
相關文章
相關標籤/搜索