工作中领导给的一个任务,让把一个网站的一些文本数据批量保存下来,每个数据都有json格式和xml格式,找了一段时间发现只有办法批量下载各个数据的id,但是每个json和xml数据后面都跟着id,就想到用链接头+id的方法来下载,代码如下:
@Autowired
private ResMapper resMapper ;
// OkHttpClient是一个HTTP客户端,用于发送HTTP请求和接收HTTP响应。
private static final OkHttpClient client = new OkHttpClient();
//@Qualifier("dataSource")
@Override
public void add(){
String jsonMsg = "";
String xmlMsg = "";
String depId = "";
for (int id = 1; id < 22344; id++) {
// 和数据库交互我用的mybatis,sql语句在下面
depId = resMapper.selectById(id);
String url = "https://链接头——————/" + depId;
String url2 = "https://链接头————————/" + depId;
Request jsonRequest = new Request.Builder()
.url(url)
.build();
try (Response jsonResponse = client.newCall(jsonRequest).execute()) {
if (!jsonResponse.isSuccessful()) continue;
String jsonResponseBody = jsonResponse.body().string();
// 解析JSON响应(示例使用Gson)
JsonElement jsonElement = JsonParser.parseString(jsonResponseBody);
jsonMsg = jsonElement.toString();
// 获取xml数据
Request xmlRequest = new Request.Builder()
.url(url2)
.build();
try (Response xmlResponse = client.newCall(xmlRequest).execute()) {
if (xmlResponse.isSuccessful()) {
String xmlResponseBody = xmlResponse.body().string();
xmlMsg = xmlResponseBody;
resMapper.save(id, jsonMsg, xmlMsg);
// System.out.println(id+"_"+depId+"成功");
}else {
System.out.println("xml失败");
}
} catch (Exception e) {
//本来是抛出异常,但是中间会有保存失败的时候,就改成了失败就输出到控制台,然后第二次我把这些id加到一个集合里,重新添加失败的
System.out.println("id = " + id + ";depId = " + depId);
}
} catch (Exception e) {
System.out.println("id = " + id + ";depId = " + depId);
}
}
}
2万多次循环还是很臃肿,但是从网上下载这些数据还是很快,毕竟只有文本数据,再数据库中做更新操作很慢(save方法是做update更新),不知道有没有大哥还有更好的方法。
虽然下载了一天,但是也省去了自己一条一条去拷贝的时间,毕竟程序后台跑着也不耽误我同时做别的工作
<mapper namespace="com.example.demo.demos.mapper.ResMapper">
<update id="save">
update res set json_msg = #{jsonMsg}, xml_msg = #{xmlMsg} where id = #{id}
</update>
<select id="selectById" resultType="java.lang.String">
select dep_id from res where id = #{id}
</select>
</mapper>
数据库表:
遇到的其他问题:
1.不小心加了事务注解,因为数据量太大导致一个失败都失败,没办法实时更新数据库
2.mybatis连接问题,发现是文件夹名字没对应好
3.lombok的Maven依赖导入不了,没解决,最后是重新初始化项目直接idea可以选择自动导入lombok了,但是我这个程序用不到,只是导入了
4.文本信息太长,varchar已经满足不了,用的mediumtext
5.还有一个问题就是String这里要做4万多次拼接字符串,肯定是用StringBuffer更好,但是我这里没改,直接让他跑起来了
再更新
public void getListAsync() {
ExecutorService executorService = Executors.newFixedThreadPool(20); // 根据实际情况调整线程池大小
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
PengMapper pengMapper = sqlSession.getMapper(PengMapper.class);
List<String> depIds = pengMapper.selectAll();
/* List<String> depIds = new ArrayList<>();
depIds.add("10087163");*/
List<CompletableFuture<Void>> futures = depIds.parallelStream().map(depId -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
String jsonMsg = "";
String xmlMsg = "";
String urlJson = "链接头" + depId;
String urlXml = "链接头" + depId;
try {
// 异步获取JSON数据
String jsonResponseBody = client.newCall(new Request.Builder().url(urlJson).build()).execute().body().string();
JsonElement jsonElement = JsonParser.parseString(jsonResponseBody);
jsonMsg = jsonElement.toString();
// 异步获取XML数据
String xmlResponseBody = client.newCall(new Request.Builder().url(urlXml).build()).execute().body().string();
xmlMsg = xmlResponseBody;
Peng msg = new Peng(depId, jsonMsg, xmlMsg);
pengMapper.insert(msg);
} catch (Exception e) {
System.err.println("Error processing depId=" + depId + ": " + e.getMessage());
}
}, executorService);
return future;
}).collect(Collectors.toList());
// 等待所有异步任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 提交事务
sqlSession.commit();
sqlSession.close();
executorService.shutdown(); // 关闭线程池
}
下面是逐行的详细解释:
1.创建线程池:通过 Executors.newFixedThreadPool(20) 创建一个固定大小为20的线程池,用于并行处理多个任务。
2.打开MyBatis的SqlSession:使用 sqlSessionFactory.openSession(ExecutorType.BATCH, false) 打开一个新的SqlSession,设置为批量执行模式且非自动提交,以便手动控制事务。
3.获取Mapper实例:通过SqlSession获取PengMapper的实例,用于执行SQL操作。
4.查询部门ID列表:调用 pengMapper.selectAll() 获取所有部门ID的列表。
5-6. /注释部分/ 是一个示例代码,展示如果直接给定一个部门ID列表而非从数据库查询的情况。
7.并行流处理部门ID:对depIds列表使用 parallelStream() 创建并行流,然后使用 map() 方法为每个depId创建一个异步任务。
8-13. 创建异步任务:对于每个depId,创建一个 CompletableFuture<Void>,表示一个没有返回值的异步操作。该操作在 executorService 线程池中执行。
14-15. 初始化消息变量:为JSON和XML消息分配空字符串。
16-17. 构建URL:根据部门ID构造获取JSON和XML数据的URL。
18-27. 异步获取数据并存储:
使用OkHttp客户端异步请求JSON和XML数据。
解析JSON响应并将其转换为字符串。
直接存储XML响应内容。
创建一个 Peng 对象,并使用 pengMapper.insert(msg) 将其插入数据库。
28-30. 异常处理:捕获执行过程中可能出现的任何异常,并打印错误信息。
返回Future对象:每个映射操作返回一个 CompletableFuture<Void> 实例。
32-33. 收集Future列表:将所有异步任务的Future收集到一个列表中。
34-35. 等待所有任务完成:使用 CompletableFuture.allOf() 方法等待所有异步任务完成,然后调用 join() 阻塞直到它们都完成。
36.提交事务:调用 sqlSession.commit() 提交之前所有批量插入操作。
27.关闭SqlSession:完成操作后关闭SqlSession。
38.关闭线程池:通过 executorService.shutdown() 平滑地关闭线程池。
速度加快了6倍,本来12个小时完成的,加快到2个小时完成,但是感觉还是不够快,有没有更好的办法去优化