public synchronized void close() {
   for (Connection conn : allConnections) {
     try {
       conn.rollback();
     } catch (SQLException ex) {
       throw new RuntimeException(ex);
     }
   }
   allConnections.clear();
 }
  private synchronized void maybeConnect() throws BlockStoreException {
    try {
      if (conn.get() != null) return;

      Properties props = new Properties();
      props.setProperty("user", this.username);
      props.setProperty("password", this.password);

      conn.set(DriverManager.getConnection(connectionURL, props));

      Connection connection = conn.get();
      allConnections.add(conn.get());
      log.info("Made a new connection to database " + connectionURL);
    } catch (SQLException ex) {
      throw new BlockStoreException(ex);
    }
  }
  public StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException {
    maybeConnect();
    PreparedStatement s = null;
    try {
      s =
          conn.get()
              .prepareStatement(
                  "SELECT txOutChanges, transactions FROM undoableBlocks WHERE hash = ?");
      // We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes

      byte[] hashBytes = new byte[28];
      System.arraycopy(hash.getBytes(), 3, hashBytes, 0, 28);
      s.setBytes(1, hashBytes);
      ResultSet results = s.executeQuery();
      if (!results.next()) {
        return null;
      }
      // Parse it.
      byte[] txOutChanges = results.getBytes(1);
      byte[] transactions = results.getBytes(2);
      StoredUndoableBlock block;
      if (txOutChanges == null) {
        int offset = 0;
        int numTxn =
            ((transactions[offset++] & 0xFF) << 0)
                | ((transactions[offset++] & 0xFF) << 8)
                | ((transactions[offset++] & 0xFF) << 16)
                | ((transactions[offset++] & 0xFF) << 24);
        List<Transaction> transactionList = new LinkedList<Transaction>();
        for (int i = 0; i < numTxn; i++) {
          Transaction tx = new Transaction(params, transactions, offset);
          transactionList.add(tx);
          offset += tx.getMessageSize();
        }
        block = new StoredUndoableBlock(hash, transactionList);
      } else {
        TransactionOutputChanges outChangesObject =
            new TransactionOutputChanges(new ByteArrayInputStream(txOutChanges));
        block = new StoredUndoableBlock(hash, outChangesObject);
      }
      return block;
    } catch (SQLException ex) {
      throw new BlockStoreException(ex);
    } catch (NullPointerException e) {
      // Corrupted database.
      throw new BlockStoreException(e);
    } catch (ClassCastException e) {
      // Corrupted database.
      throw new BlockStoreException(e);
    } catch (ProtocolException e) {
      // Corrupted database.
      throw new BlockStoreException(e);
    } catch (IOException e) {
      // Corrupted database.
      throw new BlockStoreException(e);
    } finally {
      if (s != null)
        try {
          s.close();
        } catch (SQLException e) {
          throw new BlockStoreException("Failed to close PreparedStatement");
        }
    }
  }