百万数据转换地理编码

13 篇文章 0 订阅
9 篇文章 0 订阅

导入百万数据(*.txt)到oracle数据库,同时需要请求第三方API(发送Http请求)

SpringBoot 使用 RestTemplate 查询地理编码API

步骤分析

  1. 获取.txt文件中的地址;
  2. 调用 高德地理编码API,得到经纬度;
  3. 将相应的信息入库。

实现思路

  • 第一种
  1. Java 读取文件内容,返回文件对应实体类InfoDto;
    1. 地址为空 >> 返回地址为空的List >> 转为实体类映射entity的List<InfoDto>;
    2. 地址不为空
      1. 拆分为十个一组的二维数组,不足10的再新建数组加入末尾;
  2. 拆分两个不同的List<InfoDto>为size的长度数组,余数不为空则数组长度加1,
    1. 地址为空List >> 实体类映射List
    2. 不为空List 
      1. 多线程遍历二维数组
        1. 查询API(十个一组的地址List),返回经纬度entityList
        2. 添加进List<List<InfoDo>> List,返回
      2. 查询API失败,将失败的地址等信息存入文件
  3. 获取不同的数据库映射entity的List,多线程循环二维数组 >> 执行入库
  • 第二种
  1. Java 读取文件内容,返回转换后的经纬度信息List;
    1. 地址为空,文件读完后返回设置成入库entity的List;
    2. 地址不为空
      1. 十个一组,查询高德地图API;
      2. 查询异常,将地址、唯一编号等信息写入文件;
      3. 将查询解析查询结果,设置为与数据库映射一致List
  2. 将两个List拆分为等同大小的List,执行入库操作;
  3. 读取查询API错误地址文件,执行入库
    1. 查询API,返回结果异常或查询失败时返回经纬度为0的entity;
    2. 将查询结果存入List;
    3. 按指定size拆分List,多线程执行入库

配置 pom 文件

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
  </dependency>
  <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
  </dependency>

SpringBoot 配置 RestTemplate 访问 API

使用RestTemplate调用RESTful配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

	@Bean
	public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
		return new RestTemplate(factory);
	}

	@Bean
	public ClientHttpRequestFactory simplClientHttpRequestFactory() {
		SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//		factory.setConnectTimeout(2000);
//		factory.setReadTimeout(2000);
		return factory;
	}
}

SpringBoot 配置访问 API 地址等

创建连接高德API .properties

# 高德——地理编码
geo.url= http://restapi.amap.com/v3/geocode/geo
geo.key= xxx #自己申请的key
  • 自定义的配置文件最好还是使用*.properties格式的文件,注解的方式还不支持手动加载*.yml格式文件的功能

创建实体类,获取API配置地址

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;

@Primary
@Configuration
@PropertySource("classpath:geocode.properties")
@ConfigurationProperties(prefix = "geo")
@Data
@ToString
public class GeocodingProperties {

	private String url;

	private String key;

}
  • 注: @Primary 如果只有一个自定义配置文件,则可以省略;如果有两个或两个以上则必须在某个@Configuration实体类上增加该注解

编写访问高德地理编码API工具类

import com.sanss.config.GeocodingProperties;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.PostConstruct;
import lombok.Data;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/**
 * @ClassName: GeoDto
 * @Description: 调用高德地理编码API传递的参数实体类
 * @date 2019/03/27
 */
class GeoDto {

	private Boolean batch;

	private String city;

	private String address;

	private String url;

	private String key;

	public GeoDto() {
	}

	public GeoDto(List<String> address, String city, Boolean batch, String url, String key) {
		StringBuilder addressSb = new StringBuilder();
		for (int i = 0, length = address.size(); i < length; i++) {
			addressSb.append(address.get(i));
			addressSb.append("|");
		}
		this.address = addressSb.substring(0, addressSb.lastIndexOf("|")).toString();
		this.city = city;
		this.batch = batch;
		this.url = url;
		this.key = key;
	}

	@Override
	public String toString() {
		return url + "?batch=" + batch + "&key=" + key + "&city=" + city + "&address=" + address;
	}

}

/**
 * @ClassName: Geocodes
 * @Description: 返回结果实体类, 只获取了location:经纬度 API 地址:https://lbs.amap.com/api/webservice/guide/api/georegeo
 * 返回结果示例: {"status":"1","info":"OK","infocode":"10000","count":"1","geocodes":[{"formatted_address":"上海市浦东新区东方明珠","country":"中国","province":"上海市","citycode":"021","city":"上海市","district":"浦东新区","township":[],"neighborhood":{"name":[],"type":[]},"building":{"name":[],"type":[]},"adcode":"310115","street":[],"number":[],"location":"121.499740,31.239853","level":"兴趣点"}]}
 * @date 2019/03/27
 */

@Data
@ToString
class Geocodes {

	private String location;

}

/**
 * @Title
 * @Description 高德地理编码API返回JSON实体类, 部分需要的属性, 完整的返回值在上方
 * @return
 **/
@Data
@ToString
class Geocode {

	private int status;

	private int count;

	private List<Geocodes> geocodes;


}

/**
 * @ClassName: GeoCodingUtil
 * @Description: 高德地理编码/逆编码(将地址转换为经度,纬度) API: https://lbs.amap.com/api/webservice/guide/api/georegeo/#scene
 * @date 2019/03/14
 */
@Component
public class GeoCodingUtil {

	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	private GeocodingProperties properties;

	private static GeoCodingUtil geoCodingUtil;

	@PostConstruct
	private void init() {
		geoCodingUtil = this;
		geoCodingUtil.properties = this.properties;
		geoCodingUtil.restTemplate = this.restTemplate;
	}

	/**
	 * 获取批量地址地理编码
	 **/
	public List<String> getGeocoding(List<String> address, String city) throws Exception {
		return getGeocoding(address, city, true);
	}

	/**
	 * 获取上海地区批量地址的地理编码
	 **/
	public List<String> getShanghaiGeocoding(List<String> address)
			throws Exception {
		return getGeocoding(address, "上海", true);
	}

	/**
	 * 获取上海地区单个地址的地理编码
	 **/
	public String getShanghaiGeocoding(String address) throws Exception {
		List<String> list = new ArrayList();
		list.add(address);
		list = getGeocoding(list, "上海", false);
		if ("[]".equals(list.toString())) {
			return list.toString();
		}
		return list.get(0);
	}

	/**
	 * 上海地区的高德地理编码获取
	 **/
	private List<String> getGeocoding(List<String> address, String city, boolean batch)
			throws Exception {
		List<String> point = new ArrayList<>();

		GeoDto geoDto = new GeoDto(address, city, batch, geoCodingUtil.properties.getUrl(),
				geoCodingUtil.properties.getKey());
		String realUrl = geoDto.toString();

		Geocode geocode = geoCodingUtil.restTemplate.getForObject(realUrl, Geocode.class);
		int count = geocode.getCount();
		if (count > 0) {
			List<Geocodes> geocodes = geocode.getGeocodes();
			Iterator<Geocodes> iterator = geocodes.iterator();
			String location;
			while (iterator.hasNext()) {
				location = iterator.next().getLocation();
				if (location == null) {
					point.add("");
					continue;
				}
				point.add(location);
			}
		}
		return point;
	}
}
  • 注: 在非 controller 中读取配置文件时获取不到配置类的属性值,欲了解详情可以看下我的另一篇博客SpringBoot配置文件详解

现在已经可以访问API了,接下来是service调用,返回经纬度信息。以上为通用的代码块,自此只写实现拆分List、多线程实现等主要代码块。


拆分List,多线程执行入库

  • 拆分List工具类
package com.sanss.util;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName ArrayUtil
 * @Description <br/> TODO
 * @Author Dew
 * @Date 2019/4/1 13:53
 * @Version 1.0
 **/
public class ArrayUtil<T> {

	/**
	 * @Author Dew
	 * @Description
	 * @Param [values 待分组Array 分组, groupSize 分组大小]
	 * @Date 13:54 2019/4/1
	 * @Return java.util.List<T>
	 **/
	public List<List<T>> spilitGroup(List<T> values, int groupSize) {
		List<List<T>> listGroup = new ArrayList<>();
		// List 长度
		int listSize = values.size();
		int runSize = (listSize / groupSize) + 1;

		List<T> value = null;
		for (int i = 0; i < runSize; i++) {
			int start = i * groupSize;
			if (i + 1 == runSize) {
				int end = listSize;
				value = values.subList(start, end);
			} else {
				int end = (i + 1) * groupSize;
				value = values.subList(start, end);
			}
			listGroup.add(value);
		}
		return listGroup;
	}

}
  • 多线程执行入库(使用线程池的方式创建线程)
/**
	 * @ClassName: BatchSaveThread
	 * @Description: 多线程执行入库操作
	 * @date 2019/03/14
	 */
	class BatchSaveThread implements Runnable {

		private List<AdUserGridDo> list;

		public BatchSaveThread(List<AdUserGridDo> list) {
			this.list = list;
		}

		@Override
		public void run() {
			if (list.size() > 0) {
				adUserRepository.batchSaveAdvertising(list);
			}
		}

	}

public void batchSave(List<AdUserGridDo> models, int groupSize) {
		List<List<AdUserGridDo>> groupList = new ArrayUtil<AdUserGridDo>().spilitGroup(models, groupSize);

		// 创建线程数 = 数据总数 / groupList
		int threadPoolSize = groupList.size();
		ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
		try {
			// 拆分网格数据
			for (int i = 0, length = groupList.size(); i < length; i++) {
				logger.error("线程:" + i + "save API");
				List<AdUserGridDo> list = groupList.get(i);
				BatchSaveThread saveThread = new BatchSaveThread(list);
				executor.execute(saveThread);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException();
		} finally {
			executor.shutdown();
		}
	}

使用 Connection 批量入库,降低入库时间

  • 注: 如果使用Hibernate 执行入库则可能出现数据库连接超时的现象,可以使用JDBC批量入库进行批量操作. 百万数据秒级入库

常见问题

RestTemplate 调用API超时

解决方案

  1. 配置类RestTemplateConfig中不设置connectTimeout、readTimeout
  2. 将超时时间变长,timeout时间单位为 毫秒

实现多线程有返回值的执行

/**
	 * @ClassName: BatchRestAPIThread
	 * @Description: 多线程查询API,地理编码操作
	 * @date 2019/03/18
	 */
	class BatchRestAPIThread implements Callable<List<InfoDo>> {

		private List<List<InfoDto>> list;

		public BatchRestAPIThread(List<List<InfoDto>> list) {
			this.list = list;
		}

		@Override
		public List<InfoDo> call() throws Exception {
			List<InfoDo> result = new ArrayList<>();
			// 执行代码块
			return result;
		}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值