forked from Qortal/qortal
Better code for saving to database, using close-coupled column-value pairs
This commit is contained in:
parent
63be6b7e90
commit
216ed7c772
@ -3,7 +3,6 @@ package database;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@ -168,65 +167,6 @@ public class DB {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format table and column names into an INSERT INTO ... SQL statement.
|
||||
* <p>
|
||||
* Full form is:
|
||||
* <p>
|
||||
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (?, ...) ON DUPLICATE KEY UPDATE <I>column</I>=?, ...
|
||||
* <p>
|
||||
* Note that HSQLDB needs to put into mySQL compatibility mode first via "SET DATABASE SQL SYNTAX MYS TRUE".
|
||||
*
|
||||
* @param table
|
||||
* @param columns
|
||||
* @return String
|
||||
*/
|
||||
public static String formatInsertWithPlaceholders(String table, String... columns) {
|
||||
String[] placeholders = new String[columns.length];
|
||||
Arrays.setAll(placeholders, (int i) -> "?");
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.append("INSERT INTO ");
|
||||
output.append(table);
|
||||
output.append(" (");
|
||||
output.append(String.join(", ", columns));
|
||||
output.append(") VALUES (");
|
||||
output.append(String.join(", ", placeholders));
|
||||
output.append(") ON DUPLICATE KEY UPDATE ");
|
||||
output.append(String.join("=?, ", columns));
|
||||
output.append("=?");
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds Objects to PreparedStatement based on INSERT INTO ... ON DUPLICATE KEY UPDATE ...
|
||||
* <p>
|
||||
* Note that each object is bound to <b>two</b> place-holders based on this SQL syntax:
|
||||
* <p>
|
||||
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (<b>?</b>, ...) ON DUPLICATE KEY UPDATE <I>column</I>=<b>?</b>, ...
|
||||
* <p>
|
||||
* Requires that mySQL SQL syntax support is enabled during connection.
|
||||
*
|
||||
* @param preparedStatement
|
||||
* @param objects
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static void bindInsertPlaceholders(PreparedStatement preparedStatement, Object... objects) throws SQLException {
|
||||
for (int i = 0; i < objects.length; ++i) {
|
||||
Object object = objects[i];
|
||||
|
||||
// Special treatment for BigDecimals so that they retain their "scale",
|
||||
// which would otherwise be assumed as 0.
|
||||
if (object instanceof BigDecimal) {
|
||||
preparedStatement.setBigDecimal(i + 1, (BigDecimal) object);
|
||||
preparedStatement.setBigDecimal(i + objects.length + 1, (BigDecimal) object);
|
||||
} else {
|
||||
preparedStatement.setObject(i + 1, object);
|
||||
preparedStatement.setObject(i + objects.length + 1, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute SQL using byte[] as 1st placeholder.
|
||||
* <p>
|
||||
|
132
src/database/SaveHelper.java
Normal file
132
src/database/SaveHelper.java
Normal file
@ -0,0 +1,132 @@
|
||||
package database;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Database helper for building, and executing, INSERT INTO ... ON DUPLICATE KEY UPDATE ... statements.
|
||||
* <p>
|
||||
* Columns, and corresponding values, are bound via close-coupled pairs in a chain thus:
|
||||
* <p>
|
||||
* {@code SaveHelper helper = new SaveHelper(connection, "TableName"); }<br>
|
||||
* {@code helper.bind("column_name", someColumnValue).bind("column2", columnValue2); }<br>
|
||||
* {@code helper.execute(); }<br>
|
||||
*
|
||||
*/
|
||||
public class SaveHelper {
|
||||
|
||||
private Connection connection;
|
||||
private String table;
|
||||
|
||||
private List<String> columns = new ArrayList<String>();
|
||||
private List<Object> objects = new ArrayList<Object>();
|
||||
|
||||
/**
|
||||
* Construct a SaveHelper, using SQL Connection and table name.
|
||||
*
|
||||
* @param connection
|
||||
* @param table
|
||||
*/
|
||||
public SaveHelper(Connection connection, String table) {
|
||||
this.connection = connection;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column, and bound value, to be saved when execute() is called.
|
||||
*
|
||||
* @param column
|
||||
* @param value
|
||||
* @return the same SaveHelper object
|
||||
*/
|
||||
public SaveHelper bind(String column, Object value) {
|
||||
columns.add(column);
|
||||
objects.add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build PreparedStatement using bound column-value pairs then execute it.
|
||||
* <p>
|
||||
* Note that after this call, the SaveHelper's Connection is set to null and so this object is not reusable.
|
||||
*
|
||||
* @return the result from {@link PreparedStatement#execute()}
|
||||
* @throws SQLException
|
||||
*/
|
||||
public boolean execute() throws SQLException {
|
||||
String sql = this.formatInsertWithPlaceholders();
|
||||
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
|
||||
this.bindValues(preparedStatement);
|
||||
|
||||
try {
|
||||
return preparedStatement.execute();
|
||||
} finally {
|
||||
this.connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format table and column names into an INSERT INTO ... SQL statement.
|
||||
* <p>
|
||||
* Full form is:
|
||||
* <p>
|
||||
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (?, ...) ON DUPLICATE KEY UPDATE <I>column</I>=?, ...
|
||||
* <p>
|
||||
* Note that HSQLDB needs to put into mySQL compatibility mode first via "SET DATABASE SQL SYNTAX MYS TRUE" or "sql.syntax_mys=true" in connection URL.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
private String formatInsertWithPlaceholders() {
|
||||
String[] placeholders = new String[this.columns.size()];
|
||||
Arrays.setAll(placeholders, (int i) -> "?");
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.append("INSERT INTO ");
|
||||
output.append(this.table);
|
||||
output.append(" (");
|
||||
output.append(String.join(", ", this.columns));
|
||||
output.append(") VALUES (");
|
||||
output.append(String.join(", ", placeholders));
|
||||
output.append(") ON DUPLICATE KEY UPDATE ");
|
||||
output.append(String.join("=?, ", this.columns));
|
||||
output.append("=?");
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds objects to PreparedStatement based on INSERT INTO ... ON DUPLICATE KEY UPDATE ...
|
||||
* <p>
|
||||
* Note that each object is bound to <b>two</b> place-holders based on this SQL syntax:
|
||||
* <p>
|
||||
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (<b>?</b>, ...) ON DUPLICATE KEY UPDATE <I>column</I>=<b>?</b>, ...
|
||||
* <p>
|
||||
* Requires that mySQL SQL syntax support is enabled during connection.
|
||||
*
|
||||
* @param preparedStatement
|
||||
* @throws SQLException
|
||||
*/
|
||||
private void bindValues(PreparedStatement preparedStatement) throws SQLException {
|
||||
for (int i = 0; i < this.objects.size(); ++i) {
|
||||
Object object = this.objects.get(i);
|
||||
|
||||
// Special treatment for BigDecimals so that they retain their "scale",
|
||||
// which would otherwise be assumed as 0.
|
||||
if (object instanceof BigDecimal) {
|
||||
preparedStatement.setBigDecimal(i + 1, (BigDecimal) object);
|
||||
preparedStatement.setBigDecimal(i + this.objects.size() + 1, (BigDecimal) object);
|
||||
} else {
|
||||
preparedStatement.setObject(i + 1, object);
|
||||
preparedStatement.setObject(i + this.objects.size() + 1, object);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package qora.assets;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import database.DB;
|
||||
import database.SaveHelper;
|
||||
import qora.account.PublicKeyAccount;
|
||||
|
||||
public class Asset {
|
||||
@ -37,11 +37,10 @@ public class Asset {
|
||||
// Load/Save
|
||||
|
||||
public void save(Connection connection) throws SQLException {
|
||||
String sql = DB.formatInsertWithPlaceholders("Assets", "asset", "owner", "asset_name", "description", "quantity", "is_divisible", "reference");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.key, this.owner.getAddress(), this.name, this.description, this.quantity, this.isDivisible,
|
||||
this.reference);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Assets");
|
||||
saveHelper.bind("asset", this.key).bind("owner", this.owner.getAddress()).bind("asset_name", this.name).bind("description", this.description)
|
||||
.bind("quantity", this.quantity).bind("is_divisible", this.isDivisible).bind("reference", this.reference);
|
||||
saveHelper.execute();
|
||||
|
||||
if (this.key == null)
|
||||
this.key = DB.callIdentity(connection);
|
||||
|
@ -19,6 +19,7 @@ import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import database.SaveHelper;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
@ -290,13 +291,15 @@ public class Block {
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
String sql = DB.formatInsertWithPlaceholders("Blocks", "signature", "version", "reference", "transaction_count", "total_fees", "transactions_signature",
|
||||
"height", "generation", "generating_balance", "generator", "generator_signature", "AT_data", "AT_fees");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.getSignature(), this.version, this.reference, this.transactionCount, this.totalFees,
|
||||
this.transactionsSignature, this.height, new Timestamp(this.timestamp), this.generatingBalance, this.generator.getPublicKey(),
|
||||
this.generatorSignature, this.atBytes, this.atFees);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Blocks");
|
||||
|
||||
saveHelper.bind("signature", this.getSignature()).bind("version", this.version).bind("reference", this.reference)
|
||||
.bind("transaction_count", this.transactionCount).bind("total_fees", this.totalFees).bind("transactions_signature", this.transactionsSignature)
|
||||
.bind("height", this.height).bind("generation", new Timestamp(this.timestamp)).bind("generating_balance", this.generatingBalance)
|
||||
.bind("generator", this.generator.getPublicKey()).bind("generator_signature", this.generatorSignature).bind("AT_data", this.atBytes)
|
||||
.bind("AT_fees", this.atFees);
|
||||
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
@ -11,6 +11,7 @@ import org.json.simple.JSONObject;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import database.SaveHelper;
|
||||
import qora.transaction.Transaction;
|
||||
import qora.transaction.TransactionFactory;
|
||||
|
||||
@ -107,10 +108,9 @@ public class BlockTransaction {
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
String sql = DB.formatInsertWithPlaceholders("BlockTransactions", "block_signature", "sequence", "transaction_signature");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.blockSignature, this.sequence, this.transactionSignature);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "BlockTransactions");
|
||||
saveHelper.bind("block_signature", this.blockSignature).bind("sequence", this.sequence).bind("transaction_signature", this.transactionSignature);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
@ -5,7 +5,6 @@ import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
@ -18,6 +17,7 @@ import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import database.SaveHelper;
|
||||
import qora.account.Account;
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
@ -103,10 +103,9 @@ public class GenesisTransaction extends Transaction {
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
|
||||
String sql = DB.formatInsertWithPlaceholders("GenesisTransactions", "signature", "recipient", "amount");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.recipient.getAddress(), this.amount);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "GenesisTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("recipient", this.recipient.getAddress()).bind("amount", this.amount);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
@ -5,7 +5,6 @@ import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -17,6 +16,7 @@ import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import database.SaveHelper;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import utils.Base58;
|
||||
@ -110,10 +110,9 @@ public class PaymentTransaction extends Transaction {
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
|
||||
String sql = DB.formatInsertWithPlaceholders("PaymentTransactions", "signature", "sender", "recipient", "amount");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.sender.getPublicKey(), this.recipient.getAddress(), this.amount);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "PaymentTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("sender", this.sender.getPublicKey()).bind("recipient", this.recipient.getAddress()).bind("amount", this.amount);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
@ -4,7 +4,6 @@ import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
@ -17,6 +16,7 @@ import org.json.simple.JSONObject;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import database.SaveHelper;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.block.Block;
|
||||
@ -231,11 +231,11 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
String sql = DB.formatInsertWithPlaceholders("Transactions", "signature", "reference", "type", "creator", "creation", "fee", "milestone_block");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.reference, this.type.value, this.creator.getPublicKey(),
|
||||
new Timestamp(this.timestamp), this.fee, null);
|
||||
preparedStatement.execute();
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Transactions");
|
||||
saveHelper.bind("signature", this.signature).bind("reference", this.reference).bind("type", this.type.value)
|
||||
.bind("creator", this.creator.getPublicKey()).bind("creation", new Timestamp(this.timestamp)).bind("fee", this.fee)
|
||||
.bind("milestone_block", null);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
Loading…
Reference in New Issue
Block a user