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 c00aba58..16d8a043 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -22,11 +22,17 @@ 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"; + protected HSQLDBRepository repository; + + public HSQLDBBlockRepository(HSQLDBRepository repository) { + this.repository = repository; + } + public BlockData fromSignature(byte[] signature) throws DataException { ResultSet rs; 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) { throw new DataException("Error loading data from DB", e); } @@ -37,7 +43,7 @@ public class HSQLDBBlockRepository implements BlockRepository { ResultSet rs; 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) { 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 { - int version = rs.getInt(1); - byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH); - int transactionCount = rs.getInt(3); - BigDecimal totalFees = rs.getBigDecimal(4); - byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH); - int height = rs.getInt(6); - long timestamp = rs.getTimestamp(7).getTime(); - BigDecimal generatingBalance = rs.getBigDecimal(8); - byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9)); - byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH); - 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); + try { + int version = rs.getInt(1); + byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2)); + int transactionCount = rs.getInt(3); + BigDecimal totalFees = rs.getBigDecimal(4); + byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5)); + int height = rs.getInt(6); + long timestamp = rs.getTimestamp(7).getTime(); + BigDecimal generatingBalance = rs.getBigDecimal(8); + byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9)); + byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10)); + 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); + } + 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 a8d66ffa..605f3495 100644 --- a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java +++ b/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java @@ -12,9 +12,13 @@ import database.DB; public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository { + public HSQLDBGenesisTransactionRepository(HSQLDBRepository repository) { + super(repository); + } + Transaction fromBase(byte[] signature, byte[] reference, PublicKeyAccount 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 3749946e..baded758 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; @@ -14,15 +15,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 Transaction 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; @@ -40,7 +43,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { public Transaction 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; @@ -74,7 +77,7 @@ 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; @@ -92,7 +95,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 +117,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { .bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee()) .bind("milestone_block", null); try { - saver.execute(); + saver.execute(repository.connection); } catch (SQLException e) { // 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 // definition. try { - DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transaction.getSignature()); + DB.checkedExecute(repository.connection, "DELETE FROM Transactions WHERE signature = ?", transaction.getSignature()); } catch (SQLException e) { // XXX do what? }