dao层和dal层_反射实现批量验证DAO层接口

本文介绍了如何使用Java反射来批量验证DAO层接口的可用性,避免手动构造参数的繁琐过程。通过分析方法的参数类型生成对应值,利用反射调用接口,简化接口测试。文中还分享了在实际项目中遇到的问题以及解决方案,如添加SQL日志记录,以方便追踪验证结果。
摘要由CSDN通过智能技术生成

所有原创©文章禁止任何形式转载,复用,修改转发,违者追究。

场景描述

之前写过一篇通过AOP在DAO层做分表插件的文章,最近要将其实际用在生产中。因为目前项目的DAO层是从之前的项目框架中整体迁移过来的,有很大的变化,为了避免疏漏,所以要对新mapper的接口做测试验证以及问题修复。首先想到的是部署测试环境,逐个验证请求接口,再一一处理有问题的部分。目前也确实是如此做的,好在之前迁移是使用自己写的程序统一转换的,错误相对来说比较少,主要是一些遗漏的补充,如resultMap等,所以验证和修复效率比较高,没遇到特别棘手的问题。

回到正题,以前有想法是通过反射来构造默认参数和值,来统一的验证单个调用的可用性,恰好最近也有时间,所以做了个初步的实现,这里简单介绍下这种方式,懂行大佬轻拍。

思路描述

一个调用或者称为方法,都有几个要素:返回值、参数、方法名称。比如: var func(param a, param b)。我们这里只关注参数,而参数要关注的,就是参数的类型和具体值,且这里的参数类型和值是严格相关的。举个例子:对象的值,就是个新对象;基本类型如int,long 可以用Integer代替;泛型List还得关注包装的具体类型List,其他如boolean,enum,也需要不同的处理。总结来说,就是根据方法中参数类型生成对应的值,然后方法用这些参数值,来进行调用,最终可以验证方法的可用性。

那为什么要这么做呢? 在工作中,对接口,方法的测试和验证是非常常见的内容之一,除非要验证接口的逻辑功能或者关注返回值,没必要每个接口都自己手动构造有效参数来验证,非常繁琐。在项目中就遇到过对近70个接口做迁移后的使用验证,这非常考验耐性。所以,如果能自动生成默认值,至少对这个接口方法来说参数是有效的,写一个公共工具,就可以批量进行不同接口的验证。目前这个实现只是一个初步的构建,是对偏自动化测试的尝试,可以基于此做延伸,实现更复杂的功能。

49f5841618ba9774cc41abf627deb3d4.png

代码和处理过程解析

//由于项目是springboot引入测试@RunWith(SpringRunner.class)@SpringBootTestpublic class MapperTest {    //这里是对应的mapper类,因为有很多,所以从idea复制引用出来    //可以直接在包名上右键批量复制引用(References)    String mappers = "com.xxx.xxx.xxx.mapper.CashXxxMapper";    List names = Lists.newArrayList(mappers.split(""));        @Resource    private ApplicationContext context;//用来获取bean,即DAO层的各种mapper    @Test    public void mapperTest() {        for (String s : names) {            try {                Class> clazz = Class.forName(s);                Method[] methods = clazz.getMethods();                Object obj = context.getBean(clazz);                for (Method method : methods) {                //用来存储参数的值                    List values = Lists.newArrayList();                    //拿到方法                    for (Parameter parameter : method.getParameters()) {                        String type = parameter.getType().getName();                        Object o;                        //基本类型和数字                        if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {                            o = new Integer(2);                            //列表泛型的处理                        } else if (type.contains("util.List")) {                            String rawType = parameter.getParameterizedType().getTypeName().replace("java.util.List", "");                            Object raw;                            //封装类型处理                            if (rawType.contains("lang.Integer") || rawType.contains("lang.Long") || rawType.equals("long") || rawType.equals("int")) {                                raw = new Integer(3);                            } else {                                raw = Class.forName(rawType).getDeclaredConstructor().newInstance();                            }                            ArrayList list = new ArrayList();                            list.add(raw);                            o = list;                        } else if (type.contains("java.util.Date")) {                            //项目里边用到                            o = new Date();                        } else {                            o = Class.forName(type).getDeclaredConstructor().newInstance();                            //过滤其他元类型                            if (!type.contains("java.lang")) {                                try {                                    //对象参数                                    fieldsFill(o);                                } catch (Exception e) {                                    System.err.println(type);                                }                            }                        }                        values.add(o);                    }                    try {                        method.invoke(obj, values.toArray());                    } catch (Exception e) {                        System.out.println(e.getMessage());                    }                }            } catch (Exception e) {                e.printStackTrace();            }        }        //        context.getBean()    }    //填充对象参数,可以看到和上面比较重复    private void fieldsFill(Object object) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {        Class clazz = object.getClass();        Field[] fields = clazz.getDeclaredFields();        for (Field f : fields) {            int modifier = f.getModifiers();            //exclude static final            //前缀修饰符,很好理解是一个数字            if (Modifier.isFinal(modifier) && Modifier.isStatic(modifier)) {                continue;            }            String type = f.getType().getName();            //业务字段特殊需要,基于用thrift生成的对象            if (type.contains("_") || type.contains("metaDataMap") || type.contains("Enum")) {                continue;            }            f.setAccessible(true);            Object fo;            if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {                fo = new Integer(2);            } else if (type.contains("List")) {                fo = new ArrayList<>();            } else if (type.equals("boolean")) {                continue;            } else {                fo = Class.forName(type).getDeclaredConstructor().newInstance();            }            f.set(object, fo);        }    }}

补充内容

因为这个实现是和数据库mapper操作有关的,为了方便验证结果,在本地运行中,对执行SQL加了日志,可以很方便的追踪结果。由于项目使用的druid连接数据源,之前在jdbc连接串后面加&profileSQL=true参数失败,所以在application.yml配置文件添加了

mybatis:configuration:  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

也能起到记录执行SQL的作用。结果如下:

SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4] was not registered for synchronization because synchronization is not activeJDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f36c191] will not be managed by Spring==>  Preparing: select `cash`, `event`, `create_time` from `xxx_record_2` WHERE `biz_type` = ? AND `user_id` = ? ORDER BY `create_time` DESC LIMIT ?, ? ==> Parameters: 2(Integer), 2(Long), 2(Integer), 2(Integer)<==      Total: 0Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4]

总结

如上,通过这个实现,对Java反射有了更深的了解,功能确实非常强大,而且连private static final 修饰的字段也可以修改。总体来说,实现了最开始要求的目标,不过中间生成默认值的部分还是不够优雅,可以看到有些情况是类似递归的,而且if条件也可以优化下,提高可读性。再者后续最好能找些优秀的框架代码多看看,反射相关在通用框架里使用还是非常多的,当然也不只这一点,也可以用来学习更好的实现方式和代码规范。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值