Java 批量循环 SQL
Java For loop with batch SQL
我有问题。现在我正在使用 JOOQ 使用以下代码在我的数据库中插入大约 100.000 条记录:
try (Connection conn = DriverManager.getConnection(SqlConn.getURL(), SqlConn.getUSERNAME(), SqlConn.getPASSWORD())) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
for (String key : trendlines.keySet()) {
for (Trendline trendline : trendlines.get(key)) {
String sql = createTrendlineQuery(trendline);
create.fetch(sql);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
函数createTrendlineQuery()
:
private String createTrendlineQuery(Trendline trendline) {
return "INSERT INTO Trendline (openTime, market, coin, period, metric, number, slope, interceptY, percentage, formula, data) VALUES (" +
trendline.getOpenTime() + ", '" +
trendline.getMarket() + "', '" +
trendline.getCoin() + "', '" +
trendline.getPeriod() + "', '" +
trendline.getFormula() + "') " +
"ON DUPLICATE KEY UPDATE " +
"openTime = " + trendline.getOpenTime() + ", " +
"market = '" + trendline.getMarket()+ "', " +
"coin = '" + trendline.getCoin() + "', " +
"period = '" + trendline.getPeriod() + "', " +
"formula = '" + trendline.getFormula() + "';";
}
但这给我的 internet/database 带来了很大的负担,所以我发现您可以对大数据进行批量插入。我在 JOOQ 上找到了关于批量插入的以下页面:https://www.jooq.org/doc/3.14/manual/sql-execution/batch-execution/。现在我认为这就是我所需要的,但我有一个问题。这个例子在我的例子中是这样的:
try (Connection conn = DriverManager.getConnection(SqlConn.getURL(), SqlConn.getUSERNAME(), SqlConn.getPASSWORD())) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
create.batch(create.insertInto(DSL.table("Trendline"), DSL.field("openTime"), DSL.field("market"), DSL.field("coin") ).values((Integer) null, null, null))
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.execute();
}
catch (Exception e) {
e.printStackTrace();
}
除了我需要将 2 for-loops
放在 create.batch()
之间以编程方式创建插入。我如何插入 for loops
以及我是否以正确的方式使用批量插入来减少互联网流量和数据库压力?
使用 BatchedConnection
作为快速修复
将现有的 jOOQ 代码(或任何基于 JDBC 的代码,就此而言)转换为批处理的 JDBC 交互的最简单解决方案是使用 jOOQ 的 BatchedConnection
:
create.batched((Connection c) -> {
// Now work with this Connection c, instead of your own Connection and all the statements
// will be buffered and batched, e.g.
DSL.using(c).insertInto(...).values(...).execute();
});
使用您尝试过的批次 API
您只需将 BatchBindStep
分配给循环中的局部变量即可:
BatchBindStep step = create.batch(query);
for (...)
step = step.bind(...);
step.execute();
使用导入API
使用import API。假设您正在使用代码生成器并且您有通常的静态导入
import static org.jooq.impl.DSL.*;
import static com.example.generated.Tables.*;
这样写:
create.loadInto(TRENDLINE)
.onDuplicateKeyUpdate()
.loadArrays(trendlines
.values()
.stream()
.map(t -> new Object[] {
t.getOpenTime(),
t.getMarket(),
t.getCoin(),
t.getPeriod(),
t.getFormula()
/* And the other fields which you partially omitted */
})
.toArray(Object[][]::new)
)
.fields(
TRENDLINE.OPENTIME,
TRENDLINE.MARKET,
TRENDLINE.COIN,
TRENDLINE.PERIOD,
TRENDLINE.FORMULA
/* And the other fields which you partially omitted */
)
.execute();
另请参阅以下部分:
- Throttling(您可能想使用这些值来找到最适合您系统的值)
- Error handling
这可能很有趣。如果输入 Object[][]
变得太大,您可以在您这边手动分块输入 trendlines.values()
集合。如果按键排序你的地图真的很重要(它不应该从你的问题中可以看出),那么写这个:
trendlines
.keySet()
.stream()
.flatMap(k -> trendlines.get(k).stream())
.map(t -> new Object[] { ... })
...
关于自己尝试的一些评论
- 您正在呼叫
create.fetch(sql)
, when in fact your statement is a query with an update count, so in that case, you would have wanted to use create.execute(sql)
。
- 请不要在使用 jOOQ 时连接 SQL 个字符串!即使使用 plain SQL templating, there is never a need for concatenating SQL strings。您将 运行 陷入语法错误和 SQL 注入。请始终使用绑定变量。
- 我真的推荐你使用jOOQ的代码生成器。使用代码生成器时会出现使用 jOOQ 的大部分好处。避免代码生成的正当理由包括当您的架构是动态的并且在 运行 时间未知时。这几乎是不使用代码生成的唯一原因。
我有问题。现在我正在使用 JOOQ 使用以下代码在我的数据库中插入大约 100.000 条记录:
try (Connection conn = DriverManager.getConnection(SqlConn.getURL(), SqlConn.getUSERNAME(), SqlConn.getPASSWORD())) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
for (String key : trendlines.keySet()) {
for (Trendline trendline : trendlines.get(key)) {
String sql = createTrendlineQuery(trendline);
create.fetch(sql);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
函数createTrendlineQuery()
:
private String createTrendlineQuery(Trendline trendline) {
return "INSERT INTO Trendline (openTime, market, coin, period, metric, number, slope, interceptY, percentage, formula, data) VALUES (" +
trendline.getOpenTime() + ", '" +
trendline.getMarket() + "', '" +
trendline.getCoin() + "', '" +
trendline.getPeriod() + "', '" +
trendline.getFormula() + "') " +
"ON DUPLICATE KEY UPDATE " +
"openTime = " + trendline.getOpenTime() + ", " +
"market = '" + trendline.getMarket()+ "', " +
"coin = '" + trendline.getCoin() + "', " +
"period = '" + trendline.getPeriod() + "', " +
"formula = '" + trendline.getFormula() + "';";
}
但这给我的 internet/database 带来了很大的负担,所以我发现您可以对大数据进行批量插入。我在 JOOQ 上找到了关于批量插入的以下页面:https://www.jooq.org/doc/3.14/manual/sql-execution/batch-execution/。现在我认为这就是我所需要的,但我有一个问题。这个例子在我的例子中是这样的:
try (Connection conn = DriverManager.getConnection(SqlConn.getURL(), SqlConn.getUSERNAME(), SqlConn.getPASSWORD())) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
create.batch(create.insertInto(DSL.table("Trendline"), DSL.field("openTime"), DSL.field("market"), DSL.field("coin") ).values((Integer) null, null, null))
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.bind( trendline.getOpenTime() , trendline.getMarket() , trendline.getCoin() )
.execute();
}
catch (Exception e) {
e.printStackTrace();
}
除了我需要将 2 for-loops
放在 create.batch()
之间以编程方式创建插入。我如何插入 for loops
以及我是否以正确的方式使用批量插入来减少互联网流量和数据库压力?
使用 BatchedConnection
作为快速修复
将现有的 jOOQ 代码(或任何基于 JDBC 的代码,就此而言)转换为批处理的 JDBC 交互的最简单解决方案是使用 jOOQ 的 BatchedConnection
:
create.batched((Connection c) -> {
// Now work with this Connection c, instead of your own Connection and all the statements
// will be buffered and batched, e.g.
DSL.using(c).insertInto(...).values(...).execute();
});
使用您尝试过的批次 API
您只需将 BatchBindStep
分配给循环中的局部变量即可:
BatchBindStep step = create.batch(query);
for (...)
step = step.bind(...);
step.execute();
使用导入API
使用import API。假设您正在使用代码生成器并且您有通常的静态导入
import static org.jooq.impl.DSL.*;
import static com.example.generated.Tables.*;
这样写:
create.loadInto(TRENDLINE)
.onDuplicateKeyUpdate()
.loadArrays(trendlines
.values()
.stream()
.map(t -> new Object[] {
t.getOpenTime(),
t.getMarket(),
t.getCoin(),
t.getPeriod(),
t.getFormula()
/* And the other fields which you partially omitted */
})
.toArray(Object[][]::new)
)
.fields(
TRENDLINE.OPENTIME,
TRENDLINE.MARKET,
TRENDLINE.COIN,
TRENDLINE.PERIOD,
TRENDLINE.FORMULA
/* And the other fields which you partially omitted */
)
.execute();
另请参阅以下部分:
- Throttling(您可能想使用这些值来找到最适合您系统的值)
- Error handling
这可能很有趣。如果输入 Object[][]
变得太大,您可以在您这边手动分块输入 trendlines.values()
集合。如果按键排序你的地图真的很重要(它不应该从你的问题中可以看出),那么写这个:
trendlines
.keySet()
.stream()
.flatMap(k -> trendlines.get(k).stream())
.map(t -> new Object[] { ... })
...
关于自己尝试的一些评论
- 您正在呼叫
create.fetch(sql)
, when in fact your statement is a query with an update count, so in that case, you would have wanted to usecreate.execute(sql)
。 - 请不要在使用 jOOQ 时连接 SQL 个字符串!即使使用 plain SQL templating, there is never a need for concatenating SQL strings。您将 运行 陷入语法错误和 SQL 注入。请始终使用绑定变量。
- 我真的推荐你使用jOOQ的代码生成器。使用代码生成器时会出现使用 jOOQ 的大部分好处。避免代码生成的正当理由包括当您的架构是动态的并且在 运行 时间未知时。这几乎是不使用代码生成的唯一原因。