DTO轉VO工具

柒墨轩發表於2024-07-18

data工具,實現了物件複製 DTO -> VO 只需要實現一個類即可

data-utils

data工具,實現了物件複製DTO —> VO

解決的問題

Mapstruct需要安外掛!!!!很多雲桌面等會很不方便
org.springframework.beans.BeanUtils有一個 copyProperties的方法

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO  {

    private Long id;
    private String username;
    private String password;
    private String gender;
    private String email;
    private String role;
    private Date registerTime;
    private Integer isDelete;
}
@Data
public class AccountVO {
    private String username;
    private String gender;
    private String role;
    //男 1 女 0
    private String genderNum;

    private Integer isDelete;
}

比如我們有兩個類 DTO 和 VO 在給前端VO的時候,需要把DTO 轉換成VO 我們需要這樣寫 遇到genderNumDTO類裡沒有的這種情況,我們需要手動set的時候,是下面這種寫法

import com.example.entity.AccountVO;

@Data
public class DataTest {
    @Test
    void contextLoads1() {
        AccountDTO accountDTO = new AccountDTO(1L, "test", "123456", "男", "112@qq.com", "user", new Date(), 1);
        AccountVO accountVO =  new AccountVO();
        BeanUtils.copyProperties(accountDTO, accountVO);
        accountVO.setGender(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0");
        System.out.println(accountVO);
    }
}

有兩個缺點:
1.VO需要手動new一個出來
2.如果VO裡面有很多欄位需要手動set的時候,會把程式碼寫的很長
3.如果遇到集合裡面想要轉換, sonar還會報不讓在迴圈中建立物件的問題

BaseData的作用

BaseData程式碼(專案裡只要有這個就可以)

import com.example.common.Constants;
import com.example.common.ErrorCode;
import com.example.exception.BusinessException;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.function.Consumer;

public interface BaseData {

    /**
     * 預設方法,根據傳入的Class3型別將當前物件轉換為目標物件並執行操作
     *
     * @param clazz    目標類
     * @param consumer 可以寫lambda表示式比如
     *  accountDTO.asTargetObject(AccountVO.class,v->{
     *      v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0");
     *          });
     *  consumer是這段
     *  v->{
     *      v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0");
     *  }
     */
    default <V> V asTargetObject(Class<V> clazz, Consumer<V> consumer) {
        //        呼叫 asTargetObject 方法將當前物件轉換為目標物件
        V v = this.asTargetObject(clazz);
        //        執行傳入的Consumer操作
        consumer.accept(v);
        return v;
    }

    /**
     * 預設方法 將當前物件轉換為目標物件
     *
     * @param clazz 目標類
     * @param <V>   目標類型別 如AccountVO
     * @return 轉換完的目標類
     */
    default <V> V asTargetObject(Class<V> clazz) {
        try {
            //            獲取目標類的所有欄位
            Field[] declaredFields = clazz.getDeclaredFields();
            //            獲取目標類的建構函式
            Constructor<V> constructor = clazz.getConstructor();
            //            根據建構函式例項化目標物件
            V v = constructor.newInstance();
            //            遍歷目標類的每個欄位,並進行轉換試值
            Arrays.stream(declaredFields).forEach(declaredField -> convert(declaredField, v));
            return v;
        } catch (ReflectiveOperationException e) {
            //            //捕獲ReflectiveOperationException異常,丟擲自定義的BusinessException
            throw new BusinessException(ErrorCode.CAST_OBJECT_ERROR);
        }

    }

    /**
     * 預設方法,將欄位轉換並賦值給目標物件
     * @param field VO剩餘的欄位,自定義
     * @param vo    要轉換的VO
     */
    default void convert(Field field, Object vo) {

        try {
            //            獲取當前物件中與目標欄位同名的欄位
            Field source = this.getClass().getDeclaredField(field.getName());
            //            設定欄位可訪問
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.makeAccessible(source);
            //            獲取當前物件中獲取欄位值的方法和目標物件中設定欄位值的方法,並進行轉換賦值
            Method sourceGetter = this.getClass().getMethod(Constants.GET + capitalize(field.getName()));
            Method targetSetter = vo.getClass().getMethod(Constants.SET + capitalize(field.getName()), field.getType());
            Object value = sourceGetter.invoke(this);
            targetSetter.invoke(vo, value);
        } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException |
                 NoSuchMethodException ignored) {
            //              這裡ignored 原因是
            //              兩個類的欄位數量不一樣的時候,會報 java.lang.NoSuchFieldException
            //              但是多出來的欄位我們是可以處理的
        }
    }

    /**
     * 預設方法,將字串首字母大寫
     * @param str   比如欄位名 name
     * @return 返回 Name
     */
    default String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }
}

1.實現物件深複製

@Data
public class DataTest {
    @Test
    void contextLoads1() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1);
        AccountVO accountVO = accountDTO.asTargetObject(AccountVO.class,v->{
            v.setGenderNum(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0");
        });
        System.out.println(accountVO);
    }
}

1.實現物件Collection深複製(List Set...)

@Data
public class DataTest {
    @Test
    void contextLoads2() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),0);
        AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),1);
        List<AccountDTO> accountDTOList = new ArrayList<>();
        accountDTOList.add(accountDTO);
        accountDTOList.add(accountDTO2);
        List<AccountVO> list = accountDTOList.stream().map(source -> source.asTargetObject(AccountVO.class, v-> {
            v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0");
        })).collect(Collectors.toList());
        list.forEach(System.out::println);
    }

    @Test
    void contextLoads3() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1);
        AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),0);
        Set<AccountDTO> accountDTOSet = new HashSet<>();
        accountDTOSet.add(accountDTO);
        accountDTOSet.add(accountDTO2);
        Set<AccountVO> set = accountDTOSet.stream().map(source -> source.asTargetObject(AccountVO.class, v-> {
            v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0");
        })).collect(Collectors.toSet());
        set.forEach(System.out::println);
    }
}

實現步驟

1. dto 實現 BaseData介面
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO implements BaseData {

    private Long id;
    private String username;
    private String password;
    private String gender;
    private String email;
    private String role;
    private Date registerTime;
    private Integer isDelete;
}
2. dto.asViewObject(Target.class);
3. 如果 Target 還有其他欄位 也可以自定義,例如測試用例中的genderNum(只是簡單舉的例子,按照專案實際來)
4. `isDelete` 這種is開頭的也支援
1. 如果有問題,看下lombok版本是否有問題 此專案用的版本是 1.18.28 沒問題
2. 如果沒用lombok 手動加上getIsDelete() 用這個格式就可以了

注意

兩個類 相同的欄位名的欄位型別 必須完全一樣!!!

相關文章