I'm using Java 8, JDBC and MySql. I want to insert a large amount of data (2,000 rows) into 2 tables. The tables have a 1 to 1 relationship. First table is order_items
:
| id | amount |
|:--------|----------------:|
| 1 | 20 |
| 2 | 25 |
| 3 | 30 |
Second table is delivery_details
:
| orderItemId | message |
|----------------:|:-----------|
| 1 | hello. |
| 2 | salut. |
| 3 | ciao. |
orderItemId
is a foreign key to order_items
.
The data is represented in this class:
public class OrderItemDelivery {
@SerializedName("amount")
private BigDecimal amount = null;
@SerializedName("message")
private String message = null;
// getters and setters below
...
...
}
I need to execute the inserts as a batch to cut execution time. List<OrderItemDelivery> orderItemDeliveries
contains 2,000 items. My current code is:
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();
This is really efficient, but the limitation here is it's open to SQL Injection. If I was to use PreparedStatement
, I would need one for the order_items
batch and one for the delivery_details
batch. And then LAST_INSERT_ID()
would not work.
Is there any way around this? From what I've seen, there isn't. And I need to prevent SQL Injection by sanitising the message
and amount
with Java, which appears to have limitations. For example message
can contain apostrophies and emojis. Can anyone think of another solution?
EDIT
Here's a really efficient solution I've come up with:
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();
So for this to work, the order of the ids
must be sequential, and the order of orderItemDeliveries
must not change between the first loop through of the list and the second.
This feels a bit hacky, but it works. Am I missing something?