【实际开发03】- dto + vo - 先处理 dto , 后处理 vo ( 通常少一注解 )

文章讨论了在Java编程中减少继承使用,重视组合的策略。提到了@EqualsAndHashCode注解在处理父类属性比较中的作用,以及序列化ID(serialVersionUID)在保持版本兼容性中的重要性。还介绍了如何在DTO和VO层添加Swagger注释以便于前端查看,以及使用反射和注解实现Entity与Dto之间的高效转换。此外,文章提醒了在处理VO数据赋值时要注意父类私有属性的访问问题。
摘要由CSDN通过智能技术生成

目录

0. 建议 : 多用组合 , 少用继承

1. @EqualsAndHashCode(callSuper = true) - 解决允许调用父类

2. 序列化 ID : private static final long serialVersionUID = 1L;

1. serialVersionUID 作用 : 序列化时为了保持版本的兼容性

3. 数据概览 ( 统计 ) : XxxxProfileVO

1. 统计数据添加默认值 0 - ( 避免返回 null )

4. dto-vo 层添加 Swagger 注释 - 方便前端查看 - ★

5. Java 反射 + 注解 : 实现 Entity 类与 Dto 类相互转换

1. BeanUtils.copyProperties(iotHouseDTO,iotHouse);

6. 警告 : VO 数据 赋值 问题

1. 通过继承得到的父类私有 ( private ) 属性数据 , 需 get / set 访问

/**
 * @author menghuan
 * @since 2020-04-20
 */
@Data
@ApiModel(value = "会议信息传输对象")
@EqualsAndHashCode(callSuper = true)
public class IotMeetingDTO extends IotMeeting implements Serializable {

    private static final long serialVersionUID = 1L;

}

DTO 修改为 VO 的操作:

1、删除 @EqualsAndHashCode(callSuper = true)

2、将传输对象 - 改为 - 展示对象

/**
 * @author menghuan
 * @since 2020-04-20
 */
@Data
@ApiModel(value = "会议信息展示对象")
public class IotMeetingVO extends IotMeeting implements Serializable {

    private static final long serialVersionUID = 1L;

}


0. 建议 : 多用组合 , 少用继承


1. @EqualsAndHashCode(callSuper = true) - 解决允许调用父类

在类是继承父类的情况下:

EqualsAndHashCode 实则就是在比较两个对象的属性;

当 @EqualsAndHashCode(callSuper = false) 时,不会比较其继承的父类的属性可能会导致错误判断;

当 @EqualsAndHashCode(callSuper = true) 时,会比较其继承的父类的属性;

1、此注解会生成equals(Object other) 和 hashCode()方法。

2、它默认使用非静态 , 非瞬态的属性

3、可通过参数 exclude 排除一些属性

4、可通过参数 of 指定仅使用哪些属性

5、它默认仅使用该类中定义的属性 , 且不调用父类的方法

6、可通过 callSuper=true 解决上一点问题。让其生成的方法中调用父类的方法。

@Data 相当于 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode 这5个注解的合集


2. 序列化 ID : private static final long serialVersionUID = 1L;

在 java 对象序列化时 , 如果没有设置 SerialversionUID , 他会给一个默认的值 , 但是通常是建议设置默认的建议自定义一个serialVersionUID ,

因为默认的 serialVersinUID 对于 class 的细节非常敏感 , 反序列化时可能会导致 InvalidClassException 这个异常。

如果没有设置 SerialversionUID 值的话 , 在序列化后反序列化前对类进行了修改 , 类对应的 SerialversionUID 也会变化 , 而序列化和反序列化就是通过对比其SerialversionUID 来进行的 , 一旦 SerialversionUID 不匹配 , 反序列化就无法成功。

详见《Java primary concepts (概念)》


1. serialVersionUID 作用 : 序列化时为了保持版本的兼容性

serialVersionUID 作用 :

序列化时为了保持版本的兼容性 , 即在版本升级时反序列化仍保持对象的唯一性。

serialVersionUID 用作 Serializable 类中的版本控件。

如果您没有显式声明 serialVersionUID , JVM 将根据您的 Serializable 类的各个方面自动为您执行此操作 ,

如 : Java(TM)对象序列化规范中所述


3. 数据概览 ( 统计 ) : XxxxProfileVO

/**
 * 应急事件概览展示对象
 *
 * @author makejava
 * @since 2020-10-13 09:35:04
 */
@Data
@ApiModel(value = "应急事件概览展示对象")
public class EventProfileVO {

    private static final long serialVersionUID = 1L;

    /** 应急事件总数统计 */
    private Integer sumNumber;

    /** 未处理数量统计 */
    private Integer waitDealCount;

    /** 已处理数量统计 */
    private Integer hasDealCount;

    /** 待核实数量统计 */
    // private Integer waitVerifyCount;

    /** 处理中数量统计 */
    // private Integer dealingCount;

    /** 已结束数量统计 */
    // private Integer finishCount;

}
/**
 * @Description 资源概览
 * @Author: menghuan
 * @Date: 2020/9/10 10:27
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ResourceProfileVO {

    private static final long serialVersionUID = 1L;


    /** 资源(对应状态)统计 */
    private Map<String ,  Integer> resourceCount;

    /** 资源(对应状态)占比统计 */
    private List resourcePercent;

    /** 资源上架趋势统计分析 */
    private Map<String ,  ArrayList> resourceTrend;

    /** 资源租售势统计分析 */
    private Map<String ,  ArrayList> rentTrend;

    /** 设施设备/资源类型 柱状图 */
    private Map<String ,  ArrayList> facilitiesCount;

}


1. 统计数据添加默认值 0 - ( 避免返回 null )

思路 1 : 实体类 引用 (new XxxEntity) 时 , 便为指定属性添加自定义默认值

思路 2 : 对结果进行 if else 判null 赋值 0 处理

优化 : 使用三元表达式

    for (ResourceMapVO resourceMapVO : resourceMapVOS) {
        /*resourceMapVO.setWaitCheckCount(waitCheckCount.get(resourceMapVO.getStructureIdentity()));
                resourceMapVO.setWaitRentCount(waitRentCount.get(resourceMapVO.getStructureIdentity()));
                resourceMapVO.setHasRentCount(hasRentCount.get(resourceMapVO.getStructureIdentity()));
                resourceMapVO.setHasRemoveCount(hasRemoveCount.get(resourceMapVO.getStructureIdentity()));
                resourceMapVO.setAddressPath(addressPathInfo.get(resourceMapVO.getStructureIdentity()));*/
        resourceMapVO.setWaitCheckCount(null != waitCheckCount.get(resourceMapVO.getStructureIdentity()) ? waitCheckCount.get(resourceMapVO.getStructureIdentity()) : 0);
        resourceMapVO.setWaitRentCount(null != waitRentCount.get(resourceMapVO.getStructureIdentity()) ? waitRentCount.get(resourceMapVO.getStructureIdentity()) : 0);
        resourceMapVO.setHasRentCount(null != hasRentCount.get(resourceMapVO.getStructureIdentity()) ? hasRentCount.get(resourceMapVO.getStructureIdentity()) : 0);
        resourceMapVO.setHasRemoveCount(null != hasRemoveCount.get(resourceMapVO.getStructureIdentity()) ? hasRemoveCount.get(resourceMapVO.getStructureIdentity()) : 0);
        resourceMapVO.setAddressPath(addressPathInfo.get(resourceMapVO.getStructureIdentity()));
        if (gisInfo.get(resourceMapVO.getStructureIdentity()) != null) {
            resourceMapVO.setGisMap(gisInfo.get(resourceMapVO.getStructureIdentity()));
        }else{
            resourceMapVO.setGisMap("[]");
        }
    }

思路 3 : 执行统计 sql 直接添加 默认值 : 0


4. dto-vo 层添加 Swagger 注释 - 方便前端查看 - ★

@ApiModelProperty(value = "应急事件总数统计" , name = "sumNumber")


5. Java 反射 + 注解 : 实现 Entity 类与 Dto 类相互转换

序言

近期在工作中管理代码时发现,在项目中从 Dao 层到 Service 层数据传递中通过大量的 get() , set() 方法去一个一个的去拿值去赋值,导致代码篇幅过长,对此甚是讨厌,并且严重消耗开发时间。

起初找过些关于这块的资料,现在大部分都是 Entity 类和 Dto 类的属性名相同的前提下,利用反射实现,太局限了,如果要改成同名,按目前项目的程度去整改工作量太大,不现实。

后面看了 Spring 注解的实现,然后结合找到反射实现资料,突想奇发尝试着用自定义注解+反射方式的去实现,事实证明这方法是可行的。

整体实现三步骤:

1.自定义注解

2.工具类方法实现反射

3.使用(测试)

1、自定义注解

import java.lang.annotation.*;

@Target({ElementType.FIELD,ElementType.TYPE}) // Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) // Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME 非常关键
@Documented
public @interface RelMapper {
    //自定义属性
    String value() default "";
    String type() default "";  // value : status(标记属性值为Y/N的属性) / date(标记属性类型为时间)
}

自定义属性,大家可以根据自己项目中的需求增加不同的属性

2、工具类方法实现

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;


/**
 * Java反射+注解实现Entity类与Dto类相互转换
 *      情景:在项目中从Dao层到Service层数据传递中通过大量的get(),set()方法去一个一个的去拿值去赋值,导致代码篇幅过长,并且严重消耗开发时间;
 *      尝试:现在大部分都是Entity类和Dto类的属性名相同的前提下,利用反射实现,太局限了,如果要改成同名,按目前项目的程度去整改工作量太大,不现实;
 *      解决:看了Spring注解的实现,然后结合找到反射实现资料,突想奇发尝试着用自定义注解+反射方式的去实现,事实证明这方法是可行的。
 * @Author: menghuan
 * @Date: 2021/6/28 14:41
 */
public class RelationMapperUtils {

    public static Object entryAndDtoMapper(Object entity, Object dto) throws Exception{
        return EnAndDtoMapper(entity, dto,true);
    }

    public static Object entryAndDtoMapper(Object entity, Object dto,boolean enToDto) throws Exception{
        return EnAndDtoMapper(entity, dto,false);
    }
    /**
     * Entity and Dto Mapper
     * @param entry
     * @param dto
     * @param enToDto
     *             ture  : Entity To Dto (defult)
     *             false : Dto To Entry
     *     Rule:
     *         实现相互转换前提: Dto field name(dto和entry的field name相同并且 类上有@RelMapper) 或 field的@RelMapper(value="Entity field name") 满足其一即可转换
     * @return
     * @throws Exception
     */
    // last version
    public static Object EnAndDtoMapper(Object entry, Object dto,boolean enToDto) throws Exception{
        if(enToDto == true ? entry == null : dto == null){ return null;}
        Class<? extends Object> entryclazz = entry.getClass();    //获取entity类
        Class<? extends Object> dtoclazz = dto.getClass();    //获取dto类
        boolean dtoExistAnno = dtoclazz.isAnnotationPresent(RelMapper.class);    //判断类上面是否有自定义注解
        Field[] dtofds = dtoclazz.getDeclaredFields();    //dto fields
        Field [] entryfds = entryclazz.getDeclaredFields();    //entity fields
        Method entrys[] = entryclazz.getDeclaredMethods();    //entity methods
        Method dtos[] = dtoclazz.getDeclaredMethods();    //dto methods
        String mName,fieldName,dtoFieldType=null,entFieldType=null,dtoMapName = null,dtoFieldName =null;Object value = null;
        for(Method m : (enToDto ? dtos : entrys)) {    //当 enToDto=true 此时是Entity转为Dto,遍历dto的属性
            if((mName=m.getName()).startsWith("set")) {    //只进set方法
                fieldName = mName.toLowerCase().charAt(3) + mName.substring(4,mName.length());  //通过set方法获得dto的属性名
                tohere:
                for(Field fd: dtofds) {
                    fd.setAccessible(true);    //setAccessible是启用和禁用访问安全检查的开关
                    if(fd.isAnnotationPresent(RelMapper.class)||dtoExistAnno){    //判断field上注解或类上面注解是否存在
                        //获取与Entity属性相匹配的映射值(两种情况:1.该field上注解的value值(Entity的field name 和Dto 的field name 不同)  2.该field本身(本身则是Entity的field name 和Dto 的field name 相同))
                        dtoMapName = fd.isAnnotationPresent(RelMapper.class) ? (fd.getAnnotation(RelMapper.class).value().toString().equals("")?fd.getName().toString():fd.getAnnotation(RelMapper.class).value().toString()):fd.getName().toString();
                        if(((enToDto ? fd.getName() : dtoMapName)).toString().equals(fieldName)) {
                            dtoFieldType = fd.getGenericType().toString().substring(fd.getGenericType().toString().lastIndexOf(".") + 1); // 获取dto属性的类型(如 private String field 结果 = String)
                            for(Field fe : entryfds) {
                                fe.setAccessible(true);
                                if(fe.getName().toString().equals(enToDto ? dtoMapName : fieldName) ) {//遍历Entity类的属性与dto属性注解中的value值匹配
                                    entFieldType = fe.getGenericType().toString().substring(fe.getGenericType().toString().lastIndexOf(".") + 1); //获取Entity类属性类型
                                    dtoFieldName = enToDto ? dtoMapName : fd.getName().toString();
                                    break tohere;
                                }
                            }
                        }
                    }
                }
                if(dtoFieldName!= null && !dtoFieldName.equals("null")) {
                    for(Method md : (enToDto ? entrys : dtos)) {
                        if(md.getName().toUpperCase().equals("GET"+dtoFieldName.toUpperCase())){
                            dtoFieldName = null;
                            if(md.invoke(enToDto ? entry : dto) == null) { break;} //去空操作
                            //Entity类field 与Dto类field类型不一致通过TypeProcessor处理转换
                            value = (entFieldType.equals(dtoFieldType))? md.invoke(enToDto ? entry : dto) :TypeProcessor(entFieldType, dtoFieldType,md.invoke(enToDto ? entry : dto),enToDto ? true : false);
                            m.invoke(enToDto ? dto : entry, value); //得到field的值 通过invoke()赋值给要转换类的对应属性
                            value = null;
                            break;
                        }
                    }
                }
            }
        }
        return enToDto ? dto : entry;
    }

    // 类型转换处理
    public static Object TypeProcessor(String entFieldType,String dtoFieldType, Object obj,boolean enToDto) {
        if(entFieldType.equals(dtoFieldType)) return obj;

        if(!entFieldType.equals(dtoFieldType)) {
            switch(entFieldType) {
                case "Date":
                    return (enToDto)?TypeConverter.dateToString((Date) obj):TypeConverter.stringToDate(obj.toString());
                case "Timestamp":
                    return TypeConverter.timestampToTimestampString((Timestamp)obj);
                case "Integer":
                    return (enToDto) ? obj.toString() : Integer.parseInt((String)obj) ;
            }
        }
        return obj;
    }

}

上面 EnAndDtoMapper() 方法的实现是 Entity 和 Dto 之间互相转换结合在一起,enToDto = true 表示的是 Entity 转 Dto 实现,false 则相反。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * TypeConverter 转换工具类
 * @Author: menghuan
 * @Date: 2021/6/30 11:13
 */
public class TypeConverter {

    private static Pattern linePattern = Pattern.compile("_([a-z])");

    private static Pattern humpPattern = Pattern.compile("\\B(\\p{Upper})(\\p{Lower}*)");

    /**
     * 将Date转换为String类型
     * 提示:Object类中为我们提供了toString方法,然而该方法对Date类进行转换时,往往达不到我们想要的效果
     * @param var 入参
     * @return
     */
    public static String dateToString(Date var){
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        return sf.format(var);
    }

    /**
     * 将String转换为Date类型
     * @param var 时间字符串
     * @return
     */
    public static Date stringToDate(String var){
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            //使用SimpleDateFormat的parse()方法生成Date
            Date date = sf.parse(var);
            return date;
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将Timestamp转换为指定格式的String
     * @param var 时间戳
     * @return
     */
    public static String timestampToTimestampString(Timestamp var){
        SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//定义日期显示格式
        return df.format(var);
    }

    /**
     * 将String转换为指定格式的Timestamp
     * @param var 字符串,格式必须为:"yyyy-MM-dd HH:mm:ss"
     * @return
     */
    public static Timestamp timestampStringToTimestamp(String var){
        Timestamp t_time = Timestamp.valueOf(var);
        // String-->Timestamp:2010-08-08 06:06:06.0
        // System.out.println("String-->Timestamp:"+t_time);
        return t_time;
    }

    /**
     * 实体对象转成Map
     *
     * @param obj 实体对象
     * @return
     */
    public static Map<String, Object> object2Map(Object obj) {
        Map<String, Object> map = new HashMap<>();
        if (obj == null) {
            return map;
        }
        @SuppressWarnings("rawtypes")
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(obj));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * Map转成实体对象
     *
     * @param map   map实体对象包含属性
     * @param clazz 实体对象类型
     * @return
     */
    public static Object map2Object(Map<String, Object> map, Class<?> clazz) {
        if (map == null) {
            return null;
        }
        Object obj = null;
        try {
            obj = clazz.newInstance();

            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                int mod = field.getModifiers();
                if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                    continue;
                }
                field.setAccessible(true);
                field.set(obj, map.get(field.getName()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }


    /**
     * 下划线转驼峰
     *
     * @param str
     * @return
     */
    public static String lineToHump(String str) {
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }


    /**
     * 驼峰转下划线(Map)
     *
     * @param str
     * @return
     */
    public static String humpToLine(String str) {
        Map<String, Object> map = JSONObject.parseObject(str);
        Map<String, Object> newMap = Maps.newHashMap();
        Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            StringBuffer sb = new StringBuffer();
            Map.Entry<String, Object> entry = it.next();
            String key = entry.getKey();
            Matcher matcher = humpPattern.matcher(key);
            while (matcher.find()) {
                matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
            }
            matcher.appendTail(sb);
            newMap.put(sb.toString(), entry.getValue());
        }
        return JSON.toJSONString(newMap);

    }

    /**
     * 驼峰转下划线(List)
     *
     * @param str
     * @return
     */
    @SuppressWarnings("all")
    public static String humpToLineList(String str) {
        List<Map> list = JSONObject.parseArray(str, Map.class);
        List<Map<String, Object>> res = new ArrayList<>();
        list.stream().forEach(p -> {
            Map<String, Object> newMap = Maps.newHashMap();
            Iterator<Map.Entry<String, Object>> it = p.entrySet().iterator();
            while (it.hasNext()) {
                StringBuffer sb = new StringBuffer();
                Map.Entry<String, Object> entry = it.next();
                String key = entry.getKey();
                Matcher matcher = humpPattern.matcher(key);
                while (matcher.find()) {
                    matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
                }
                matcher.appendTail(sb);
                newMap.put(sb.toString(), entry.getValue());
            }
            res.add(newMap);
        });
        return JSON.toJSONString(res);
    }

}

3、如何使用

Entity 类 与 Dto 类对应


1. BeanUtils.copyProperties(iotHouseDTO,iotHouse);


6. 警告 : VO 数据 赋值 问题


1. 通过继承得到的父类私有 ( private ) 属性数据 , 需 get / set 访问

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值