不同风格的实体类的转化

Shi Yi2025/07/17tooljavaspringboot

场景分析

有时候我们写外包的时候会遇到这么个情况,原系统已经有一套实体类了,但是因为某些原因又生成了一套实体类。那么我们应该怎么实现转化呢?很多聪明的小伙伴肯定一下就想到了BeanUtils.copyProperties(source, target),但是呢,这个方法只能实现属性名相同的转化,如果属性名不同,就会出现null的情况(比如原实体类是下划线风格,新实体类是驼峰风格)。这种情况如果我们想要和BeanUtils.copyProperties()方法一样,那么我们就需要自定义转化方法,再封装为一个工具类。

解决方法

public class BeanCopyUtils {

    /**
     * 驼峰命名转下划线命名并复制属性
     * 例如:basicdataUserId -> basicdata_user_id
     *
     * @param source 源对象
     * @param target 目标对象
     */
    public static void copyPropertiesCamelToUnderscore(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }

        BeanWrapper sourceBeanWrapper = new BeanWrapperImpl(source);
        BeanWrapper targetBeanWrapper = new BeanWrapperImpl(target);

        PropertyDescriptor[] sourceProperties = sourceBeanWrapper.getPropertyDescriptors();

        for (PropertyDescriptor sourcePd : sourceProperties) {
            String sourcePropertyName = sourcePd.getName();
            if ("class".equals(sourcePropertyName)) {
                continue;
            }

            // 将驼峰命名转为下划线命名
            String targetPropertyName = camelToUnderscore(sourcePropertyName);

            try {
                // 检查目标对象是否有对应的属性
                PropertyDescriptor targetPd = targetBeanWrapper.getPropertyDescriptor(targetPropertyName);
                if (targetPd != null && targetPd.getWriteMethod() != null) {
                    Object value = sourceBeanWrapper.getPropertyValue(sourcePropertyName);

                    // 处理类型转换,例如 Double 到 Float
                    if (value != null &&
                        value instanceof Double &&
                        targetPd.getPropertyType().equals(Float.class)) {
                        value = ((Double) value).floatValue();
                    }

                    targetBeanWrapper.setPropertyValue(targetPropertyName, value);
                }
            } catch (Exception e) {
                // 属性不存在或无法设置,忽略
            }
        }
    }

    /**
     * 驼峰命名转下划线命名
     * 例如:basicdataUserId -> basicdata_user_id
     */
    private static String camelToUnderscore(String camel) {
        if (camel == null || camel.isEmpty()) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        boolean prevUpperCase = false;

        for (int i = 0; i < camel.length(); i++) {
            char c = camel.charAt(i);

            // 如果是大写字母
            if (Character.isUpperCase(c)) {
                // 如果前一个字符不是大写,添加下划线
                if (!prevUpperCase && i > 0) {
                    sb.append('_');
                }
                // 将大写字母转为小写
                sb.append(Character.toLowerCase(c));
                prevUpperCase = true;
            } else {
                sb.append(c);
                prevUpperCase = false;
            }
        }

        return sb.toString();
    }

    /**
     * 下划线命名转驼峰命名并复制属性
     * 例如:basicdata_quilt_id -> basicdataQuiltId
     *
     * @param source 源对象
     * @param target 目标对象
     */
    public static void copyPropertiesUnderscoreToCamel(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }

        BeanWrapper sourceBeanWrapper = new BeanWrapperImpl(source);
        BeanWrapper targetBeanWrapper = new BeanWrapperImpl(target);

        PropertyDescriptor[] sourceProperties = sourceBeanWrapper.getPropertyDescriptors();

        for (PropertyDescriptor sourcePd : sourceProperties) {
            String sourcePropertyName = sourcePd.getName();
            if ("class".equals(sourcePropertyName)) {
                continue;
            }

            // 将下划线命名转为驼峰命名
            String targetPropertyName = underscoreToCamel(sourcePropertyName);

            try {
                // 检查目标对象是否有对应的属性
                PropertyDescriptor targetPd = targetBeanWrapper.getPropertyDescriptor(targetPropertyName);
                if (targetPd != null && targetPd.getWriteMethod() != null) {
                    Object value = sourceBeanWrapper.getPropertyValue(sourcePropertyName);

                    // 处理类型转换,例如 Double 到 Float
                    if (value != null &&
                        value instanceof Double &&
                        targetPd.getPropertyType().equals(Float.class)) {
                        value = ((Double) value).floatValue();
                    }

                    targetBeanWrapper.setPropertyValue(targetPropertyName, value);
                }
            } catch (Exception e) {
                // 属性不存在或无法设置,忽略
            }
        }
    }

    /**
     * 下划线命名转驼峰命名
     * 例如:basicdata_quilt_id -> basicdataQuiltId
     */
    private static String underscoreToCamel(String underscore) {
        if (underscore == null || underscore.isEmpty()) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        boolean nextUpperCase = false;

        for (int i = 0; i < underscore.length(); i++) {
            char c = underscore.charAt(i);

            if (c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(c);
                }
            }
        }

        return sb.toString();
    }

    /**
     * 获取对象中所有为null的属性名
     */
    public static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        return emptyNames.toArray(new String[0]);
    }

    /**
     * 复制属性,忽略null值
     */
    public static void copyPropertiesIgnoreNull(Object source, Object target) {
        BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
    }
}
Last Updated 9/4/2025, 2:28:31 PM