Java8 的字符串list分组、排序和SQL的to_char函数使用

Java 同时被 3 个专栏收录
9 篇文章 0 订阅
5 篇文章 0 订阅
10 篇文章 0 订阅

一、背景

本篇出自最近笔者所负责项目的一个需求。开发之余,就此次功能实现过程,结合最近所学所感,笔者做了些记录,留下此篇。

这是笔者此次需要实现的需求效果图:

首先就是这个当前页的时间轴数据展示,于前端讲就是个数组,于后端讲就是个list数据结构。

本次项目,前端采用Vue,后端则为spring boot+Mybatis。

二、数据格式

经过和前端反复扯皮,基本确定了返回数据格式(当然,这是笔者胡说的,事实上此后还经过两次变动)。格式如下:

data: [{
     year: '2020 年',
     area: [{ data: '06 - 20', num: 9, fileList: { fileId: 12, fullName: '农政改发[2020] 6 号', size: '200 kb' }, arealist: ['安徽省'] }]
    }]

三、表设计和字段关联

笔者在原有基础上新增了一张表,字段如下:

以此表为主表,去关联另一张表,关联字段为area_id。

后面要用到的字段其实就是省份名称,试验区名称和批复日期。

四、业务逻辑和实现过程

开发是什么?大公司的是怎么样的,笔者不太清楚。但我本人的感受就是,开局一张图(PPT),其余全靠编。让笔者感慨的是,从小时候上学开始,一路走来,经历了多少看图写作文,命题作为,半命题作文。没想到,工作后也是如此。

之前提到需要list结构返回数据,但是list内部的数据结构如何组织并没有确定。仔细分析以后发现这就是一个套娃结构,里里外外套了5层。如果是单表的话还好说,可这是需要跨表关联查询。按照前端的要求,要按照年份分组;而分析需求,结合表结构和字段关联,内部还需要按照批复文件id即file_id分组;而后来前端又补充,同一省份的不同试验区也应该归为一组。总结一下,就是至少需要三次分组。年份字段需要从表字段approve_date截取,还有内层的日期字段包含月日。

谈到分组和字段的拼接,其实笔者最初是倾向于尽量用SQL实现,那样的话代码层就简简单单,几行了事。当然,这也是笔者的思维惯性:能SQL解决就尽量不写代码,还是怕麻烦,尤其是对脏数据和空值处理。

可惜没能如我所愿。SQL实现的话,有些复杂,而且项目负责人和同事也不建议那么做。那就上代码,如图:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.fw.reform.mapper.ApproveFileMapper">

    <!-- 查询获取批复文件信息列表 -->
    <select id="getAllApproveFile" resultType="com.fw.reform.vo.response.developmentprocess.ApproveFileResVo">
        SELECT to_char(af.approve_date, 'yyyy年') AS year, to_char(af.approve_date, 'mm-dd') AS approve_date,
        af.file_id, f.full_name, f.size, rpa.province_region_name AS  province_name, rpa.name AS area_name
        FROM approve_file af LEFT JOIN reform_pilot_area rpa ON af.area_id = rpa.id
        LEFT JOIN file f ON af.file_id = f.id WHERE af.id IS NOT NULL ORDER BY to_char(af.approve_date, 'yyyy') DESC
    </select>
</mapper>

项目采用的是PG数据库,在Mybatis中采用to_char字段处理localDate类型数据,将其转换为字符串类型数据时完全可以做到的。有关to_char这个函数的应用相关技巧,网上资料多的是,笔者也不再赘述。这里要提一点最后order by函数排序,其实就最终要返回的数据而言,并没有什么作用。因为经过后续多次分组、遍历以后,最终拼接得到的数据早已无法按照原始数据那样排序。

关键代码,如图:

 @Override
    public List<DevProcessResVo> getDataList() {
        List<ApproveFileResVo> approveFileResVoList = getAllApproveFile();
        // 对数据源列表进行测试,如果判断为空,返回一个新建ArrayList列表
        if (approveFileResVoList == null || approveFileResVoList.size() == 0)
            return new ArrayList<>();

        // 根据年份和批复文件id对批复文件信息列表分组
        Map<String, Map<Long, List<ApproveFileResVo>>> approveFileMap = approveFileResVoList.stream().collect(Collectors
                .groupingBy(ApproveFileResVo::getYear, Collectors.groupingBy(ApproveFileResVo::getFileId)));
        List<DevProcessResVo> devProcessResVoList = new ArrayList<>();
        // 遍历approveFileMap,拼接出所需要的数据结构
        // 一次遍历,以批复文件年份为key,对应值为同一年份下的所有数据
        approveFileMap.forEach((year, map) -> {
            DevProcessResVo devProcessResVo = new DevProcessResVo();
            devProcessResVo.setYear(year);
            List<AreaResVo> areaResVoList = new ArrayList<>();
            // 二次遍历,以批复文件id为key,对应值为同年份同批复文件id下的所有数据
            map.forEach((key2, value) -> {
                AreaResVo areaResVo = new AreaResVo();
                areaResVo.setFileId(key2);
                List<SimplePilotAreaResVo> simplePilotAreaResVos = new ArrayList<>();
                value.forEach(v -> {
                    areaResVo.setApprovalDate(v.getApproveDate());
                    areaResVo.setFullName(v.getFullName());
                    areaResVo.setSize(v.getSize());

                    SimplePilotAreaResVo simplePilotAreaResVo = new SimplePilotAreaResVo();
                    simplePilotAreaResVo.setProvinceName(v.getProvinceName());
                    simplePilotAreaResVo.setAreaName(v.getAreaName());
                    simplePilotAreaResVos.add(simplePilotAreaResVo);
                });
                areaResVo.setNum(simplePilotAreaResVos.size());

                List<AreaGroupByProvinceResVo> group = new ArrayList<>();
                // 根据省份名称将试验区列表分组
                Map<String, List<SimplePilotAreaResVo>> areaMap = simplePilotAreaResVos.stream().collect(Collectors.groupingBy(SimplePilotAreaResVo::getProvinceName));
                areaMap.forEach((province, list) -> {
                    AreaGroupByProvinceResVo areaGroupByProvinceResVo = new AreaGroupByProvinceResVo();
                    areaGroupByProvinceResVo.setProvinceName(province);
                    List<String> areaNameList = new ArrayList<>();
                    list.forEach(t -> {
                        String name = t.getAreaName();
                        areaNameList.add(name);
                    });
                    areaGroupByProvinceResVo.setAreaNameList(areaNameList);
                    group.add(areaGroupByProvinceResVo);
                });
                areaResVo.setAreaList(group);
                areaResVoList.add(areaResVo);
            });
            devProcessResVo.setArea(areaResVoList);
            devProcessResVoList.add(devProcessResVo);
        });

        // 将拼接后获取的发展历程数据列表进行降序排序
        Collections.sort(devProcessResVoList, Comparator.comparing(DevProcessResVo::getYear).reversed());

        return devProcessResVoList;
    }

在Java8的stream相关API中,对于list的处理方法很丰富,本次用到的单条件分组和多条件分组只是其中之一。需要指出的是,Stream流分组处理数据时遇到null值会抛异常并中断程序。所以,笔者在拿到原始数据后进行测试,判空后直接返回新建ArrayList。提到空值和空指针异常,我们都知道Java中这是最常见的情况。最近笔者查看了一部分Jdk1.8的源码,发现源码中很重视边界检查和测试。笔者是十分赞同这样的做法的。笔者认为,任何从数据库中拿到的原始数据,哪怕是自己本身十分确定数据的可靠性,但还是应该对原始数据做测试,加一道保险。

前面已经提到过,经过拼接后得到的devProcessResVoList不再是降序排序的,所以要经过一层排序处理然后才能得到所需要的数据。这里排序,笔者用了Collections.sort()。当然有其他的实现方式,但此方法笔者比较熟悉。这里引发了本人的一个思考,那就是能得到正确结果的排序方式有几种?各自的效率如何?哪个最优?哪个最适合大数据量的处理?由于时间问题,笔者只是简单的尝试了一下stream的sort方法,似乎不太理想。stream的sort方法直接提供了int、double、Long类型版本的Comparator.comparaing(),并没有string类型的直接方法,而并行流似乎也并没有提供。也许是限于笔者的stream相关技能的水平吧,并没有很快的做出对比。出于时间和代码简洁度的考虑,最后选择了Collections.sort()。

最后再提一点,就是关于ArrayList的采用。之前笔者测试过,不过测试数据是整型。在相同且相当大的数据量情况下,ArrayList的add方法效果要比LinkedList实际要差点,但是都在一个量级。其实理论上两者的add()方法都是默认加到最后一项,应该为O(N)。但如果是涉及到get方法和set方法,其差距就很明显。LinkedList所花时间是随着数据量增长而成平方增长的。即使是add(index, int),虽然LinkedList所需时间比ArrayList的少,大约差一到两个数量级,但是综合考虑,只要有get的调用,还是比较推荐ArrayList。当然,过大的数据集不在此考虑范围内。

五、总结

本篇并没有高大上的技能分享,也没有真知灼见。是笔者结合自己的项目经历,和最近的学习吸收,写了一些总结。关于最后的字符串list排序问题,如果大家有更高效,更优雅的代码实现,还请不吝赐教。如果能出一篇各方法对比和大数据量测试文章,那就更好了。

  • 0
    点赞
  • 4
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值