Java 从HashMap高效地批量插入/复制到表中
任务: 给定这个HashMap结构:Map mainMap=newhashmap 我想将内部映射的每个值插入或复制到数据库中它自己的单元格中 主地图的大小(如果50000)。 内部贴图的大小为50。 要插入的表有50列。 每个列的标题都是内部映射的键。 编辑:最初用户上载一个包含50列中35列的大型电子表格。然后,我用各种格式清理数据,并为每个mainMap条目向innerMap添加我自己的15个新对。如果不进行清理/格式化/添加,我无法直接从用户的源文件复制到我的数据库 一旦我完成了对电子表格的迭代和mainMap的构建,我就需要高效地将其插入到数据库表中 研究: 我知道拷贝是最初批量填充表的最佳方法,但是我仍然无法确定我的需求是否支持该命令 声明Postgres对查询的预处理语句参数限制为34464 我假设总共需要50 x 50000=2500000个参数。 这相当于约73个单独的查询 问题: 在这里复制正确的方法而不是所有这些参数吗? 如果是这样,我是否将HashMap值转换为.sql文件,将其保存在web app服务器的磁盘上,然后在COPY命令中引用该值,然后删除临时文件?或者我可以直接将连接的字符串传递给它,而不必冒SQL注入的风险? 此命令将经常发生,因此需要进行优化 我找不到任何将Java对象转换为兼容的Postgres文本文件格式的例子,因此任何反馈都有帮助 你将如何处理这个问题 其他信息: “我的表”是预先存在的,无法删除,因为它是我的webapp的后端,并且在任何给定时间都连接了多个用户 我知道在使用复制之前临时删除索引可以提高性能,但我只要求一次最多插入或复制50000行,而不是数百万行Java 从HashMap高效地批量插入/复制到表中,java,postgresql,Java,Postgresql,任务: 给定这个HashMap结构:Map mainMap=newhashmap 我想将内部映射的每个值插入或复制到数据库中它自己的单元格中 主地图的大小(如果50000)。 内部贴图的大小为50。 要插入的表有50列。 每个列的标题都是内部映射的键。 编辑:最初用户上载一个包含50列中35列的大型电子表格。然后,我用各种格式清理数据,并为每个mainMap条目向innerMap添加我自己的15个新对。如果不进行清理/格式化/添加,我无法直接从用户的源文件复制到我的数据库 一旦我完成了对电子表格
StackExchange让我在这里提问。虽然Java肯定不是进行此类ETL的最佳选择,但使用标准INSERT语句和准备好的查询肯定是可行的,而且开销很小: conn.setAutoCommitfalse; PreparedStatement stmt=conn.prepareStatement 在我的表格中插入列a,列b。。。 +值。。。; int batchSize=1000; int行=0; 对于贴图值:mainMap.values{ int i=0; stmt.setString++i,values.getcol\u a; stmt.setString++i,values.getcol_b; // ... stmt.addBatch;//将行添加到批中 如果++行%batchSize==0{ //批量调整:执行。。。 stmt.executeBatch; } } 如果行%batchSize!=0 { //如有必要,最后一次执行。。。 stmt.executeBatch; } conn.commit;//原子操作-如果任何记录失败,整个导入将失败
或者,您可以将映射写入一个文件并使用,但我严重怀疑这会比使用成批插入更快,但对于数百万行来说,这会有所不同。尽管Java肯定不是进行此类ETL的最佳选择,使用标准INSERT语句和准备好的查询当然是可能的,而且开销很小: conn.setAutoCommitfalse; PreparedStatement stmt=conn.prepareStatement 在我的表格中插入列a,列b。。。 +值。。。; int batchSize=1000; int行=0; 对于贴图值:mainMap.values{ int i=0; stmt.setString++i,values.getcol\u a; stmt.setString++i,values.getcol_b; // ... stmt.addBatch;//将行添加到批中 如果++行%batchSize==0{ //批量调整:执行。。。 stmt.executeBatch; } } 如果行%batchSize!=0 { //如有必要,最后一次执行。。。 stmt.executeBatch; } conn.commit;//原子操作-如果任何记录失败,整个导入将失败 或者,您可以将映射写入一个文件并使用,但我严重怀疑这会比批量插入更快,但对于数百万行来说,这可能会有所不同。可能确实是初始批量上载的推荐方式,但考虑到初始数据存储在Java映射的内存中,存在一些限制: 首先,它希望从服务器本地加载文件,并由其用户读取,或者再次从服务器本地或通过STDIN执行的程序加载。这些选项对JDBC连接都不是特别友好的。 其次,假设您在同一台机器上准备这样一个文件,即使您可以准备该格式的数据,例如,您仍然需要将存储在Java内存中的数据转换为复制所需的格式。这种处理方式可能不值得使用COPY。 我会创建一个PreparedStatement来插入您的50列,然后 然后对mainMap.values中的每个映射执行准备好的语句,即每次执行50列 你可以使用它来提高速度。也就是说,我不会在一批中执行所有50000个,而是分批执行 我会这样做:
int BATCH_SIZE = 100;
List<String> keyNames = new ArrayList<>();
int i = 0;
try (PreparedStatement ps = conn
.prepareStatement("INSERT INTO xyz (col1, col2, ...) VALUES (?, ?, ...)")) {
for (Map<String, String> rowMap : mainMap.values()) {
int j = 1;
// You need the keynames to be in the same order as the columns
// they match.
for (String key : keyNames) {
ps.setString(j, rowMap.get(key));
j++;
}
ps.addBatch();
if (i > 0 && i % BATCH_SIZE == 0) {
ps.executeBatch();
}
i++;
}
if (i % BATCH_SIZE != 1) {
// More batches to execute since the last time it was done.
ps.executeBatch();
}
}
这可能确实是初始批量上传的推荐方式,但考虑到初始数据存储在Java映射的内存中,存在一些限制:
首先,它希望从服务器本地加载文件,并由其用户读取,或者再次从服务器本地或通过STDIN执行的程序加载。这些选项对JDBC连接都不是特别友好的。
其次,假设您在同一台机器上准备这样一个文件,即使您可以准备该格式的数据,例如,您仍然需要将存储在Java内存中的数据转换为复制所需的格式。这种处理方式可能不值得使用COPY。
相反,我会创建一个PreparedStatement来插入50列,然后迭代执行mainMap.values中每个映射的准备好的语句,即每次50列
你可以使用它来提高速度。也就是说,我不会在一批中执行所有50000个,而是分批执行
我会这样做:
int BATCH_SIZE = 100;
List<String> keyNames = new ArrayList<>();
int i = 0;
try (PreparedStatement ps = conn
.prepareStatement("INSERT INTO xyz (col1, col2, ...) VALUES (?, ?, ...)")) {
for (Map<String, String> rowMap : mainMap.values()) {
int j = 1;
// You need the keynames to be in the same order as the columns
// they match.
for (String key : keyNames) {
ps.setString(j, rowMap.get(key));
j++;
}
ps.addBatch();
if (i > 0 && i % BATCH_SIZE == 0) {
ps.executeBatch();
}
i++;
}
if (i % BATCH_SIZE != 1) {
// More batches to execute since the last time it was done.
ps.executeBatch();
}
}
问题是对象从何而来?好的,您可以处理用户的数据并在单个请求中将其持久化到数据库中吗?这将消除主地图和大批量处理的需要。一旦用户上传电子表格,我就必须逐行迭代。例如,在我迭代第一行之后,在处理下一行之前,我应该如何处理清理后的数据?我不知道如何在不发送昂贵的INSERT查询的情况下,将单行数据持久化到数据库中。这就是我将其存储在RAM中的原因,因此在所有迭代完成后,我将使用最少的查询进行批量插入/复制。我不明白什么?如果我理解正确,mainMap代表一个用户的一个电子表格,并统计50k行,每行50列?不,用户上载的电子表格最多有50000行数据,但只有35列。然后我需要逐行迭代,以不同的方式清理数据的每个单元格。根据该行的数据,我将自己的另外15对添加到内部映射中。因此目标表有50列。在完全组合mainMap时,它基本上是目标表应该具有的相同数据复制。问题是对象来自何处?好的,您可以在单个请求中处理用户的数据并将其保留在数据库中吗?这将消除主地图和大批量处理的需要。一旦用户上传电子表格,我就必须逐行迭代。例如,在我迭代第一行之后,在处理下一行之前,我应该如何处理清理后的数据?我不知道如何在不发送昂贵的INSERT查询的情况下,将单行数据持久化到数据库中。这就是我将其存储在RAM中的原因,因此在所有迭代完成后,我将使用最少的查询进行批量插入/复制。我不明白什么?如果我理解正确,mainMap代表一个用户的一个电子表格,并统计50k行,每行50列?不,用户上载的电子表格最多有50000行数据,但只有35列。然后我需要逐行迭代,以不同的方式清理数据的每个单元格。根据该行的数据,我将自己的另外15对添加到内部映射中。因此目标表有50列。在完全合成mainMap时,它实际上是目标表应该具有的相同数据复制。这看起来很有希望。我会回来报到的。谢谢,我觉得我不得不接受你的回答,因为它是第一个、正确的、有凝聚力的,而且还提供了有关conn.commit的有趣信息,我不知道。谢谢你的支持+这看起来很有希望。我会回来报到的。谢谢,我觉得我不得不接受你的回答,因为它是第一个、正确的、有凝聚力的,而且还提供了有关conn.commit的有趣信息,我不知道。谢谢你的支持+1汉克·布鲁诺,我现在花了一些时间来真正理解你们的答案,然后再进一步回答。我不得不接受另一个答案,因为它是第一个也是正确的,但你的答案非常相似,所以谢谢你。你证实了我对在这里使用COPY的担忧,并教了我一些有趣的花边新闻+1汉克·布鲁诺,我现在花了一些时间来真正理解你们的答案,然后再进一步回答。我不得不接受另一个答案,因为它是第一个也是正确的,但你的答案非常相似,所以谢谢你。你证实了我对在这里使用COPY的担忧,并教了我一些有趣的花边新闻+1.