Java 批插入到2个相关表中,同时避免SQL注入
我正在使用Java8、JDBC和MySql。我想在两个表中插入大量数据(2000行)。这些表具有1对1的关系。第一个表是订单项目:Java 批插入到2个相关表中,同时避免SQL注入,java,mysql,foreign-keys,bulkinsert,Java,Mysql,Foreign Keys,Bulkinsert,我正在使用Java8、JDBC和MySql。我想在两个表中插入大量数据(2000行)。这些表具有1对1的关系。第一个表是订单项目: | id | amount | |:--------|----------------:| | 1 | 20 | | 2 | 25 | | 3 | 30 | 第二个表是交付详细信息: | orderItemId |
| id | amount |
|:--------|----------------:|
| 1 | 20 |
| 2 | 25 |
| 3 | 30 |
第二个表是交付详细信息:
| orderItemId | message |
|----------------:|:-----------|
| 1 | hello. |
| 2 | salut. |
| 3 | ciao. |
orderItemId
是order\u items
的外键
数据在此类中表示:
public class OrderItemDelivery {
@SerializedName("amount")
private BigDecimal amount = null;
@SerializedName("message")
private String message = null;
// getters and setters below
...
...
}
我需要批量执行插入以缩短执行时间<代码>列表orderItemDeliveries包含2000个项目。我目前的代码是:
Connection connection = this.hikariDataSource.getConnection();
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
for (int x = 0; x < orderItemDeliveries.size(); x++) {
sql = String.format("INSERT INTO order_items (amount) VALUES ('%s')", orderItemDelivery.getAmount());
statement.addBatch(sql);
sql = String.format("INSERT INTO `delivery_details` (`orderItemId`, `message`) VALUES (LAST_INSERT_ID(), '%s')", orderItemDelivery.getMessage());
statement.addBatch(sql);
}
statement.executeBatch();
statement.close();
connection.setAutoCommit(true);
connection.close();
因此,要使其起作用,id
的顺序必须是顺序的,并且orderItemDeliveries
的顺序不能在列表的第一个循环到第二个循环之间更改
这感觉有点不舒服,但很有效。我遗漏了什么吗?准备好的声明是否可能
这一点很好,但由于它是1:1关系,您可以为每个表使用单独的序列键或自动增量键,而不是last\u insert\u id()
,因为它们为相关记录生成相同的值。在具有并发事务的oltp设置中,我不会这样做,但由于您正在批处理,这可能是合理的。如果可以的话,您可以提前强制两个表独占访问
让应用程序跟踪键值也是一个选项,而不是使用一个autoinc字段。不幸的是,与Oracle相反,mysql不允许直接从序列中选择下一个值。例如,这种方法:使用一个带有字段MAX的MAXKEY表。假设您要插入10行,MAX为200。独占锁定MAXKEY,选择MAX(现在您知道,您的密钥可以从200+1开始),将MAXKEY更新为200+10,提交(释放锁)。将201…210用于2组带有准备好的查询的批处理插入
您可以使用存储过程接受两个表的值并分别插入其中的一个(请参阅),再次使用last\u insert\u id()
,并以批处理方式调用该过程(请参阅)
最终会出现sql清理程序,可能是org.apache.commons.lang.StringEscapeUtils.escapeSlq()行中的某些功能
但准备好的声明也添加了其他优化。sql与一个二维值数组一起只发送一次到服务器。解析后的查询可以缓存,并在后续调用中重用。您应该能够从中看到更多的性能改进
字符串连接版本为每一行发送整个查询,所有行都不同,需要解析,在缓存中找不到 我建议你试试这个。即使不是批处理方法,它也是基于
PreparedStatement
,这将始终比内联SQL获得更好的性能:
private void insertItems(Connection connection, Collection<OrderItemDelivery> orderItemDeliveries)
throws SQLException
{
try (PreparedStatement pst1=connection.prepareStatement("INSERT INTO order_items (amount) VALUES (?)", new String[] { "id"});
PreparedStatement pst2=connection.prepareStatement("INSERT INTO delivery_details(orderItemId, message) VALUES (?, ?)"))
{
for (OrderItemDelivery orderItemDelivery : orderItemDeliveries)
{
pst1.setString(1, orderItemDelivery.getAmount());
int x=pst1.executeUpdate();
if (x != 1)
{
throw new SQLException("Row was not inserted");
}
try (ResultSet rs=pst1.getGeneratedKeys())
{
if (rs.next())
{
long id=rs.getLong(1);
// TODO Fill the values in 2nd prepared statement and call executeUpdate().
}
else
{
throw new SQLException("Id was not generated");
}
}
}
}
}
private void插入项(连接、集合orderItemDeliveries)
抛出SQLException
{
try(PreparedStatement pst1=connection.prepareStatement(“插入订单项目(金额)值(?)”),新字符串[]{“id”});
PreparedStatement pst2=connection.prepareStatement(“插入交付详细信息(orderItemId,message)值(?,)”)
{
for(OrderItemDelivery OrderItemDelivery:orderItemDeliveries)
{
setString(1,orderItemDelivery.getAmount());
int x=pst1.executeUpdate();
如果(x!=1)
{
抛出新的SQLException(“未插入行”);
}
try(ResultSet rs=pst1.getGeneratedKeys())
{
如果(rs.next())
{
long id=rs.getLong(1);
//TODO填充第二条准备好的语句中的值并调用executeUpdate()。
}
其他的
{
抛出新的SQLException(“未生成Id”);
}
}
}
}
}
注意:您必须先试用;并非所有db供应商都实现了
getGeneratedKeys
方法。如果您没有,只需调用LAST\u INSERT\u ID
:它应该也能工作。下面是我使用getGeneratedKeys()
完成的操作:
String orderItemSql=“在订单中插入项目(金额)值(?);
对于(int x=1;x
因此,要使其工作,ID的顺序必须是顺序的,并且orderItemDeliveries的顺序在列表的第一个循环到第二个循环之间不能改变
这感觉有点不舒服,但很有效 我建议您专注于避免SQL注入,因为这是一个安全问题,即使这意味着放弃优化。因此,您应该使用
PreparedStatement
和executeUpdate
。甚至可以使用PreparedStatement
?我需要在订单项目中执行1次插入,然后在
private void insertItems(Connection connection, Collection<OrderItemDelivery> orderItemDeliveries)
throws SQLException
{
try (PreparedStatement pst1=connection.prepareStatement("INSERT INTO order_items (amount) VALUES (?)", new String[] { "id"});
PreparedStatement pst2=connection.prepareStatement("INSERT INTO delivery_details(orderItemId, message) VALUES (?, ?)"))
{
for (OrderItemDelivery orderItemDelivery : orderItemDeliveries)
{
pst1.setString(1, orderItemDelivery.getAmount());
int x=pst1.executeUpdate();
if (x != 1)
{
throw new SQLException("Row was not inserted");
}
try (ResultSet rs=pst1.getGeneratedKeys())
{
if (rs.next())
{
long id=rs.getLong(1);
// TODO Fill the values in 2nd prepared statement and call executeUpdate().
}
else
{
throw new SQLException("Id was not generated");
}
}
}
}
}
String orderItemSql = "INSERT INTO order_items (amount) VALUES (?) ";
for (int x = 1; x < orderItemDeliveries.size(); x++) {
orderItemSql += ", (?)";
}
PreparedStatement preparedStatement = connection.prepareStatement(orderItemSql, Statement.RETURN_GENERATED_KEYS);
int i = 1;
for (int x = 0; x < orderItemDeliveries.size(); x++) {
preparedStatement.setDouble(i++, orderItemDelivery.getAmount().doubleValue());
}
preparedStatement.executeUpdate();
Long ids[] = new Long[orderItemDeliveries.size()];
ResultSet rs = preparedStatement.getGeneratedKeys();
int x = 0;
while (rs.next()) {
ids[x] = rs.getLong(1);
x++;
}
String deliveryDetails = "INSERT INTO `delivery_details` (`orderItemId`, `message`) VALUES (?, ?)";
for (x = 1; x < orderItemDeliveries.size(); x++) {
deliveryDetails += ", (?)";
}
preparedStatement = connection.prepareStatement(deliveryDetails);
i = 1;
for (x = 0; x < orderItemDeliveries.size(); x++) {
orderItemDelivery = orderItemDeliveries.get(x);
preparedStatement.setLong(i++, ids[x]);
preparedStatement.setString(i++, orderItemDelivery.getMessage());
}
preparedStatement.executeUpdate();