diff --git a/src/database/DB.java b/src/database/DB.java index 7f511ade..6f74b486 100644 --- a/src/database/DB.java +++ b/src/database/DB.java @@ -68,6 +68,10 @@ public abstract class DB { return local.get(); } + public static Connection getPoolConnection() throws SQLException { + return connectionPool.getConnection(); + } + public static void releaseConnection() { Connection connection = local.get(); if (connection != null) @@ -179,7 +183,12 @@ public abstract class DB { * @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) // Special treatment for BigDecimals so that they retain their "scale", diff --git a/src/repository/Repository.java b/src/repository/Repository.java index 9e97edc5..9807ba84 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -5,6 +5,10 @@ public abstract class Repository { protected TransactionRepository transactionRepository; protected BlockRepository blockRepository; + public abstract void saveChanges() throws DataException ; + public abstract void discardChanges() throws DataException ; + public abstract void close() throws DataException ; + public TransactionRepository getTransactionRepository() { return this.transactionRepository; } diff --git a/src/repository/hsqldb/HSQLDBBlockRepository.java b/src/repository/hsqldb/HSQLDBBlockRepository.java index 990a1fbb..64336867 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -23,9 +23,15 @@ public class HSQLDBBlockRepository implements BlockRepository { 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"; - public BlockData fromSignature(byte[] signature) throws DataException { + protected HSQLDBRepository repository; + + public HSQLDBBlockRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + public BlockData fromSignature(byte[] signature) throws DataException { try { - ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature); + ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature); return getBlockFromResultSet(rs); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); @@ -34,7 +40,7 @@ public class HSQLDBBlockRepository implements BlockRepository { public BlockData fromReference(byte[] reference) throws DataException { try { - ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE reference = ?", reference); + ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); return getBlockFromResultSet(rs); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); @@ -43,7 +49,7 @@ public class HSQLDBBlockRepository implements BlockRepository { public BlockData fromHeight(int height) throws DataException { try { - ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); + ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); return getBlockFromResultSet(rs); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); @@ -67,11 +73,11 @@ public class HSQLDBBlockRepository implements BlockRepository { byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10)); byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11)); BigDecimal atFees = rs.getBigDecimal(12); - + return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey, generatorSignature, atBytes, atFees); - } catch (SQLException e) { - throw new DataException(e); + } catch(SQLException e) { + throw new DataException("Error extracting data from result set", e); } } diff --git a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java b/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java index 3affdf2f..6b10e92f 100644 --- a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java +++ b/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java @@ -11,9 +11,13 @@ import repository.DataException; public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository { + public HSQLDBGenesisTransactionRepository(HSQLDBRepository repository) { + super(repository); + } + TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) { 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) return null; diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index 2210d917..a2fdaeb2 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -1,11 +1,65 @@ 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; public class HSQLDBRepository extends Repository { - public HSQLDBRepository() { - this.transactionRepository = new HSQLDBTransactionRepository(); + Connection connection; + + 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); + } } } diff --git a/src/repository/hsqldb/HSQLDBSaver.java b/src/repository/hsqldb/HSQLDBSaver.java index d6eff39d..c3742aad 100644 --- a/src/repository/hsqldb/HSQLDBSaver.java +++ b/src/repository/hsqldb/HSQLDBSaver.java @@ -1,6 +1,7 @@ package repository.hsqldb; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; @@ -55,9 +56,13 @@ public class HSQLDBSaver { * @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); diff --git a/src/repository/hsqldb/HSQLDBTransactionRepository.java b/src/repository/hsqldb/HSQLDBTransactionRepository.java index a2c75cae..403d98af 100644 --- a/src/repository/hsqldb/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/HSQLDBTransactionRepository.java @@ -1,6 +1,7 @@ package repository.hsqldb; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -15,15 +16,17 @@ import repository.TransactionRepository; public class HSQLDBTransactionRepository implements TransactionRepository { + protected HSQLDBRepository repository; private HSQLDBGenesisTransactionRepository genesisTransactionRepository; - public HSQLDBTransactionRepository() { - genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(); + public HSQLDBTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository); } public TransactionData fromSignature(byte[] signature) { 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) return null; @@ -41,7 +44,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { public TransactionData fromReference(byte[] reference) { 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) return null; @@ -75,9 +78,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository { // in one go? 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) return 0; @@ -95,7 +97,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { // Fetch block signature (if any) 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) return null; @@ -114,7 +116,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { .bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())).bind("fee", transactionData.getFee()) .bind("milestone_block", null); try { - saver.execute(); + saver.execute(repository.connection); } catch (SQLException e) { throw new DataException(e); } @@ -125,7 +127,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 // definition. try { - DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature()); + DB.checkedExecute(repository.connection, "DELETE FROM Transactions WHERE signature = ?", transaction.getSignature()); } catch (SQLException e) { // XXX do what? }