省市县三级数据联动 -Java

省市县三级数据联动

需求:实现省市县三级联动问题,责任继续划分,需要Java后端封装省市县三层数据返回给前端。于是乎开始探索之旅。

首先获取国家地区编码和名称对应表,可以自行前往国家统计局或者github上查询获取。这里就直接上表(本表使用的是2019年的国家行政区地区信息表)
资源链接

链接:https://pan.baidu.com/s/16ISH7MJbwx-Fa1ACZA3oww 
提取码:l8dq 
--来自百度网盘超级会员V5的分享

下面进行我的探索以及我踩得坑

1、第一步: 新建一个SpringBoot项目并集成MyBatis(这里就不概述了)
2、第二步:编写Xml和接收数据的实体类信息
<!-- mybatis设置别名后,可以直接使用实体类名返回数据信息-->
<select id="selectList" resultType="DomesticRegionResp ">
        select CODEID codeId,
               PARENTID parentId,
               CITYNAME cityName
        from `DOMESTIC_REGION`
</select>
@Data
@ToString
public class DomesticRegionResp {
    private Integer codeId;    //ID
    private Integer parentId;  //父级ID
    private String cityName;  //名称
    private List<DomesticRegionResp> child;  //子级元素集合
}
第三步:后端实现代码数据的封装

尝试一:

思路:首先查询所有的省份和直辖市信息,最后根据省份的ID和直辖市的ID查询它的第二级和第三级。思索一番后,发现无解,因为这样增加了java和mysql的IO传输,必然会延长整个查询的速度。
最后结果:6s (抱歉没有放图)
速度很慢,因此考虑将所有的数据全部查询出来放入jvm中,用java来实现数据的分级。

尝试二:查询所有的地区信息,通过java来实现分级和封装

/**
     * @Description 省 -> 市 -> 县
     * 由于频繁请求数据库,取得全部省市县数据数据较慢,因此将数据全部取出交给JVM进行处理。
     * 于是进行三层封装数据处理。
     */
    @Test
    public void getProvinceInfo(){
        List<DomesticRegionResp> list = domesticRegionMapper.selectList();
        // 步骤一:获取省份或者直辖市,获取成功后删除获取到的信息 (第一级)
        List<DomesticRegionResp> provinceList = list.stream().filter( f -> f.getParentId() == 0)
                .collect(Collectors.toList());

        // 步骤二: 根据省份获取市级或者区级信息 (第二级)
        for (DomesticRegionResp province : provinceList) {
            List<DomesticRegionResp> cityList = list.stream().filter(f -> f.getParentId().equals(province.getCodeId()))
                    .collect(Collectors.toList());

            // 步骤三: 根据市级ID取得县信息或者区信息 (第三级)
            for (DomesticRegionResp city : cityList) {
                List<DomesticRegionResp> townList = list.stream().filter(f -> f.getParentId().equals(city.getCodeId()))
                        .collect(Collectors.toList());

                //将县/镇放入 区/市行政区内
                city.setChild(townList);
            }
            province.setChild(cityList);
        }
        System.out.println(provinceList);
    }

问题:最后打印时报错,所以我在想在使用java实现堆栈中的数据过多了。

运行时间: 35

java.lang.StackOverflowError
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at com.tjau.dto.DomesticRegionResp.toString(DomesticRegionResp.java:9)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at java.util.AbstractCollection.toString(AbstractCollection.java:462)

尝试三:在每次执行完查询后,删除已经完成查询的数据。

/**
     * @Description 省 -> 市 -> 县
     * 由于频繁请求数据库,取得全部省市县数据数据较慢,因此将数据全部取出交给JVM进行处理。
     * 于是进行三层封装数据处理。
     * 尝试一: 正常的数据过滤处理,最后执行打印语句时,出现堆栈溢出异常。
     * 尝试二:每次查询到一组数据后,在原List数组中删除已查询数据。这样实现查询次数的锐减。
     */
    @Test
    public void getProvinceInfo(){
        List<DomesticRegionResp> list = domesticRegionMapper.selectList();
        // 步骤一:获取省份或者直辖市,获取成功后删除获取到的信息 (第一级)
        List<DomesticRegionResp> provinceList = list.stream().filter( f -> f.getParentId() == 0)
                .collect(Collectors.toList());
        list.removeIf( f -> f.getParentId() == 0);

        // 步骤二: 根据省份获取市级或者区级信息 (第二级)
        for (DomesticRegionResp province : provinceList) {
            List<DomesticRegionResp> cityList = list.stream().filter(f -> f.getParentId().equals(province.getCodeId()))
                    .collect(Collectors.toList());
            list.removeIf( f -> f.getParentId().equals(province.getCodeId()));

            // 步骤三: 根据市级ID取得县信息或者区信息 (第三级)
            for (DomesticRegionResp city : cityList) {
                List<DomesticRegionResp> townList = list.stream().filter(f -> f.getParentId().equals(city.getCodeId()))
                        .collect(Collectors.toList());
                list.removeIf( f -> f.getParentId().equals(city.getCodeId()));
                //将县/镇放入 区/市行政区内
                city.setChild(townList);
            }
            province.setChild(cityList);
        }
        System.out.println(provinceList);
    }

最后成功打印

运行时间: 40
[DomesticRegionResp(codeId=110000, parentId=0, cityName=北京市, child=[DomesticRegionResp(codeId=110000, parentId=110000, cityName=北京市, child=[]), DomesticRegionResp(codeId=110101, parentId=110000, cityName=东城区, child=[]), DomesticRegionResp(codeId=110102, parentId=110000, cityName=西城区, child=[]), DomesticRegionResp(codeId=110105, parentId=110000, cityName=朝阳区, child=[]), DomesticRegionResp(codeId=110106, parentId=110000, cityName=丰台区, child=[]), DomesticRegionResp(codeId=110107, parentId=110000, cityName=石景山区, child=[]), DomesticRegionResp(codeId=110108, parentId=110000, cityName=海淀区, child=[]), DomesticRegionResp(codeId=110109, parentId=110000, cityName=门头沟区, child=[]), DomesticRegionResp(codeId=110111, parentId=110000, cityName=房山区, child=[]), DomesticRegionResp(codeId=110112, parentId=110000, cityName=通州区, child=[]), 

尝试四:其实在查询第二层和第三层时,方法是一致的,于是乎可以使用递归实现吗?

/**
     * @Description 省 -> 市 -> 县
     * 由于频繁请求数据库,取得全部省市县数据数据较慢,因此将数据全部取出交给JVM进行处理。
     * 于是进行三层封装数据处理。
     * 尝试一: 正常的数据过滤处理,最后执行打印语句时,出现堆栈溢出异常。
     * 尝试二:每次查询到一组数据后,在原List数组中删除已查询数据。这样实现查询次数的锐减。
     * 尝试三:递归
     */
@Test
    public void getProvinceInfo(){
        List<DomesticRegionResp> list = domesticRegionMapper.selectList();
        recursive(list);
	}
	
/**
     * @Description 递归
     */
    public void recursive(List<DomesticRegionResp> list){
        long start = System.currentTimeMillis();
        //过滤出第一层
        List<DomesticRegionResp> first = list.stream()
                .filter(region -> region.getParentId() == 0)
                .collect(Collectors.toList());
        list.removeIf( f -> f.getParentId() == 0);

        //第二三层递归
        first.forEach(region -> region.setChild(generateRegion(region.getCodeId(), list)));
        long end = System.currentTimeMillis();
        System.out.println("递归运行时间: " + (end - start));
    }

    public List<DomesticRegionResp> generateRegion(int parentId, List<DomesticRegionResp> list){
        return list.stream()
                .filter(region -> region.getParentId() == parentId)
                .peek(region -> region.setChild(generateRegion(region.getCodeId(), list)))
                .collect(Collectors.toList());
    }

问题:堆栈溢出, 至今没有解决问题,如果有大佬解决这个方法,还请您与我分享,非常感谢。

09:21:17.835 [main] DEBUG com.e3c.mapper.wsyy.DomesticRegionMapper.selectList - <==      Total: 3216

java.lang.StackOverflowError
	at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:480)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at com.e3c.ApplicationTest.generateRegion(ApplicationTest.java:213)
	at com.e3c.ApplicationTest.lambda$generateRegion$8(ApplicationTest.java:212)
	at java.util.stream.ReferencePipeline$11$1.accept(ReferencePipeline.java:372)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)

至此数据获取和封装就完成了。

然后在将数据返回给前端时,再一次出现了一个问题。那就是SpringMVC在对多层数据进行封装时,对一直嵌套循环,就如同套娃一样。

基于上面的问题:
方法一:我们自己将封装好的provinceList转为String类型,返回给前端。
方法二:更改SpringMVC默认封装数据的消息转换器(MessageConverter)
(不提倡,因为更改组件后,这个单体服务的所有数据封装格式都改变了,会影响到其他接口。)

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值