需求:实现省市县三级联动问题,责任继续划分,需要Java后端封装省市县三层数据返回给前端。于是乎开始探索之旅。
首先获取国家地区编码和名称对应表,可以自行前往国家统计局或者github上查询获取。这里就直接上表(本表使用的是2019年的国家行政区地区信息表)
资源链接
链接:https://pan.baidu.com/s/16ISH7MJbwx-Fa1ACZA3oww
提取码:l8dq
--来自百度网盘超级会员V5的分享
下面进行我的探索以及我踩得坑
<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)
(不提倡,因为更改组件后,这个单体服务的所有数据封装格式都改变了,会影响到其他接口。)