CHANGED: added support for DB transactions (commit/rollback) to repository

This commit is contained in:
Kc 2018-06-11 00:24:20 +02:00
parent 6c33cfed74
commit efb43c67fb
7 changed files with 122 additions and 31 deletions

View File

@ -68,6 +68,10 @@ public abstract class DB {
return local.get(); return local.get();
} }
public static Connection getPoolConnection() throws SQLException {
return connectionPool.getConnection();
}
public static void releaseConnection() { public static void releaseConnection() {
Connection connection = local.get(); Connection connection = local.get();
if (connection != null) if (connection != null)
@ -179,7 +183,12 @@ public abstract class DB {
* @throws SQLException * @throws SQLException
*/ */
public static ResultSet checkedExecute(String sql, Object... objects) throws SQLException { public static ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = DB.getConnection().prepareStatement(sql); Connection connection = DB.getConnection();
return checkedExecute(connection, sql, objects);
}
public static ResultSet checkedExecute(Connection connection, String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < objects.length; ++i) for (int i = 0; i < objects.length; ++i)
// Special treatment for BigDecimals so that they retain their "scale", // Special treatment for BigDecimals so that they retain their "scale",

View File

@ -5,6 +5,10 @@ public abstract class Repository {
protected TransactionRepository transactionRepository; protected TransactionRepository transactionRepository;
protected BlockRepository blockRepository; protected BlockRepository blockRepository;
public abstract void saveChanges() throws DataException ;
public abstract void discardChanges() throws DataException ;
public abstract void close() throws DataException ;
public TransactionRepository getTransactionRepository() { public TransactionRepository getTransactionRepository() {
return this.transactionRepository; return this.transactionRepository;
} }

View File

@ -22,11 +22,17 @@ public class HSQLDBBlockRepository implements BlockRepository
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, " private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees"; + "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
protected HSQLDBRepository repository;
public HSQLDBBlockRepository(HSQLDBRepository repository) {
this.repository = repository;
}
public BlockData fromSignature(byte[] signature) throws DataException public BlockData fromSignature(byte[] signature) throws DataException
{ {
ResultSet rs; ResultSet rs;
try { try {
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature); rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Error loading data from DB", e); throw new DataException("Error loading data from DB", e);
} }
@ -37,7 +43,7 @@ public class HSQLDBBlockRepository implements BlockRepository
{ {
ResultSet rs; ResultSet rs;
try { try {
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Error loading data from DB", e); throw new DataException("Error loading data from DB", e);
} }
@ -45,20 +51,26 @@ public class HSQLDBBlockRepository implements BlockRepository
} }
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException { private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
int version = rs.getInt(1); try {
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH); int version = rs.getInt(1);
int transactionCount = rs.getInt(3); byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
BigDecimal totalFees = rs.getBigDecimal(4); int transactionCount = rs.getInt(3);
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH); BigDecimal totalFees = rs.getBigDecimal(4);
int height = rs.getInt(6); byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5));
long timestamp = rs.getTimestamp(7).getTime(); int height = rs.getInt(6);
BigDecimal generatingBalance = rs.getBigDecimal(8); long timestamp = rs.getTimestamp(7).getTime();
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9)); BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH); byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11)); byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10));
BigDecimal atFees = rs.getBigDecimal(12); byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12);
return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees); return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees);
}
catch(SQLException e)
{
throw new DataException("Error extracting data from result set", e);
}
} }
} }

View File

@ -12,9 +12,13 @@ import database.DB;
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository { public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBGenesisTransactionRepository(HSQLDBRepository repository) {
super(repository);
}
Transaction fromBase(byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) { Transaction fromBase(byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) {
try { try {
ResultSet rs = DB.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature); ResultSet rs = DB.checkedExecute(repository.connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
return null; return null;

View File

@ -1,11 +1,65 @@
package repository.hsqldb; package repository.hsqldb;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.sql.Connection;
import java.sql.SQLException;
import database.DB;
import repository.DataException;
import repository.Repository; import repository.Repository;
public class HSQLDBRepository extends Repository { public class HSQLDBRepository extends Repository {
public HSQLDBRepository() { Connection connection;
this.transactionRepository = new HSQLDBTransactionRepository();
public HSQLDBRepository() throws DataException {
try {
initialize();
} catch (SQLException e) {
throw new DataException("initialization error", e);
}
this.transactionRepository = new HSQLDBTransactionRepository(this);
}
private void initialize() throws SQLException {
connection = DB.getPoolConnection();
// start transaction
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setAutoCommit(false);
}
@Override
public void saveChanges() throws DataException {
try {
connection.commit();
} catch (SQLException e) {
throw new DataException("commit error", e);
}
}
@Override
public void discardChanges() throws DataException {
try {
connection.rollback();
} catch (SQLException e) {
throw new DataException("rollback error", e);
}
}
// TODO prevent leaking of connections if .close() is not called before garbage collection of the repository.
// Maybe use PhantomReference to call .close() on connection after repository destruction?
@Override
public void close() throws DataException {
try {
// give connection back to the pool
connection.close();
connection = null;
} catch (SQLException e) {
throw new DataException("close error", e);
}
} }
} }

View File

@ -1,6 +1,7 @@
package repository.hsqldb; package repository.hsqldb;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,9 +56,13 @@ public class HSQLDBSaver {
* @throws SQLException * @throws SQLException
*/ */
public boolean execute() throws SQLException { public boolean execute() throws SQLException {
String sql = this.formatInsertWithPlaceholders(); Connection connection = DB.getConnection();
return execute(connection);
}
PreparedStatement preparedStatement = DB.getConnection().prepareStatement(sql); public boolean execute(Connection connection) throws SQLException {
String sql = this.formatInsertWithPlaceholders();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
this.bindValues(preparedStatement); this.bindValues(preparedStatement);

View File

@ -1,6 +1,7 @@
package repository.hsqldb; package repository.hsqldb;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
@ -14,15 +15,17 @@ import repository.TransactionRepository;
public class HSQLDBTransactionRepository implements TransactionRepository { public class HSQLDBTransactionRepository implements TransactionRepository {
protected HSQLDBRepository repository;
private HSQLDBGenesisTransactionRepository genesisTransactionRepository; private HSQLDBGenesisTransactionRepository genesisTransactionRepository;
public HSQLDBTransactionRepository() { public HSQLDBTransactionRepository(HSQLDBRepository repository) {
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(); this.repository = repository;
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
} }
public Transaction fromSignature(byte[] signature) { public Transaction fromSignature(byte[] signature) {
try { try {
ResultSet rs = DB.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature); ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
return null; return null;
@ -40,7 +43,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
public Transaction fromReference(byte[] reference) { public Transaction fromReference(byte[] reference) {
try { try {
ResultSet rs = DB.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference); ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
if (rs == null) if (rs == null)
return null; return null;
@ -74,7 +77,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// in one go? // in one go?
try { try {
ResultSet rs = DB.checkedExecute("SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", signature); ResultSet rs = DB.checkedExecute(repository.connection, "SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", signature);
if (rs == null) if (rs == null)
return 0; return 0;
@ -92,7 +95,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// Fetch block signature (if any) // Fetch block signature (if any)
try { try {
ResultSet rs = DB.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature); ResultSet rs = DB.checkedExecute(repository.connection, "SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature);
if (rs == null) if (rs == null)
return null; return null;
@ -114,7 +117,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
.bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee()) .bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee())
.bind("milestone_block", null); .bind("milestone_block", null);
try { try {
saver.execute(); saver.execute(repository.connection);
} catch (SQLException e) { } catch (SQLException e) {
// XXX do what? // XXX do what?
} }
@ -125,7 +128,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY // NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY
// definition. // definition.
try { try {
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transaction.getSignature()); DB.checkedExecute(repository.connection, "DELETE FROM Transactions WHERE signature = ?", transaction.getSignature());
} catch (SQLException e) { } catch (SQLException e) {
// XXX do what? // XXX do what?
} }