PersistentBankServerpublic class PersistentBankServer extends UnicastRemoteObject implements RemoteBankThis class is another implementation of the RemoteBank interface.
It uses a database connection as its back end, so that client data isn't
lost if the server goes down. Note that it takes the database connection
out of "auto commit" mode and explicitly calls commit() and rollback() to
ensure that updates happen atomically. |
Fields Summary |
---|
Connection | db |
Constructors Summary |
---|
public PersistentBankServer(Connection db)The constructor. Just save the database connection object away
this.db = db;
|
Methods Summary |
---|
public synchronized FunnyMoney | closeAccount(java.lang.String name, java.lang.String password)Close a named account
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
s = db.createStatement();
// Delete the account from the accounts table
s.executeUpdate("DELETE FROM accounts " +
"WHERE name = '" + name + "' " +
" AND password = '" + password + "'");
// And drop the transaction history table for this account
s.executeUpdate("DROP TABLE " + name + "_history");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
// Finally, return whatever balance remained in the account
return new FunnyMoney(balance);
| public synchronized void | deposit(java.lang.String name, java.lang.String password, FunnyMoney money)Deposit the specified money into the named account
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
s = db.createStatement();
// Update the balance
s.executeUpdate("UPDATE accounts " +
"SET balance = " + balance + money.amount + " " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
// Add a row to the transaction history
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Deposited " + money.amount +
" at " + new Date() + "')");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
| public synchronized int | getBalance(java.lang.String name, java.lang.String password)Return the balance of the specified account
int balance;
try {
// Get the balance
balance = verify(name, password);
// Commit the transaction
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
// Return the balance
return balance;
| public synchronized java.util.List | getTransactionHistory(java.lang.String name, java.lang.String password)Get the transaction history of the named account
Statement s = null;
List list = new ArrayList();
try {
// Call verify to check the password, even though we don't
// care what the current balance is.
verify(name, password);
s = db.createStatement();
// Request everything out of the history table
s.executeQuery("SELECT * from " + name + "_history");
// Get the results of the query and put them in a Vector
ResultSet r = s.getResultSet();
while(r.next()) list.add(r.getString(1));
// Commit the transaction
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
// Return the Vector of transaction history.
return list;
| public static void | main(java.lang.String[] args)This main() method is the standalone program that figures out what
database to connect to with what driver, connects to the database,
creates a PersistentBankServer object, and registers it with the registry,
making it available for client use
try {
// Create a new Properties object. Attempt to initialize it from
// the BankDB.props file or the file optionally specified on the
// command line, ignoring errors.
Properties p = new Properties();
try { p.load(new FileInputStream(args[0])); }
catch (Exception e) {
try { p.load(new FileInputStream("BankDB.props")); }
catch (Exception e2) {}
}
// The BankDB.props file (or file specified on the command line)
// must contain properties "driver" and "database", and may
// optionally contain properties "user" and "password".
String driver = p.getProperty("driver");
String database = p.getProperty("database");
String user = p.getProperty("user", "");
String password = p.getProperty("password", "");
// Load the database driver class
Class.forName(driver);
// Connect to the database that stores our accounts
Connection db = DriverManager.getConnection(database,
user, password);
// Configure the database to allow multiple queries and updates
// to be grouped into atomic transactions
db.setAutoCommit(false);
db.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// Create a server object that uses our database connection
PersistentBankServer bank = new PersistentBankServer(db);
// Read a system property to figure out how to name this server.
// Use "SecondRemote" as the default.
String name = System.getProperty("bankname", "SecondRemote");
// Register the server with the name
Naming.rebind(name, bank);
// And tell everyone that we're up and running.
System.out.println(name + " is open and ready for customers.");
}
catch (Exception e) {
System.err.println(e);
if (e instanceof SQLException)
System.err.println("SQL State: " +
((SQLException)e).getSQLState());
System.err.println("Usage: java [-Dbankname=<name>] " +
"je3.rmi.PersistentBankServer " +
"[<dbpropsfile>]");
System.exit(1);
}
| public synchronized void | openAccount(java.lang.String name, java.lang.String password)Open an account
// First, check if there is already an account with that name
Statement s = null;
try {
s = db.createStatement();
s.executeQuery("SELECT * FROM accounts WHERE name='" + name + "'");
ResultSet r = s.getResultSet();
if (r.next()) throw new BankingException("Account name in use.");
// If it doesn't exist, go ahead and create it Also, create a
// table for the transaction history of this account and insert an
// initial transaction into it.
s = db.createStatement();
s.executeUpdate("INSERT INTO accounts VALUES ('" + name + "', '" +
password + "', 0)");
s.executeUpdate("CREATE TABLE " + name +
"_history (msg VARCHAR(80))");
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Account opened at " + new Date() + "')");
// And if we've been successful so far, commit these updates,
// ending the atomic transaction. All the methods below also use
// this atomic transaction commit/rollback scheme
db.commit();
}
catch(SQLException e) {
// If an exception was thrown, "rollback" the prior updates,
// removing them from the database. This also ends the atomic
// transaction.
try { db.rollback(); } catch (Exception e2) {}
// Pass the SQLException on in the body of a BankingException
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
// No matter what happens, don't forget to close the DB Statement
finally { try { s.close(); } catch (Exception e) {} }
| public int | verify(java.lang.String name, java.lang.String password)This convenience method checks whether the name and password match
an existing account. If so, it returns the balance in that account.
If not, it throws an exception. Note that this method does not call
commit() or rollback(), so its query is part of a larger transaction.
Statement s = null;
try {
s = db.createStatement();
s.executeQuery("SELECT balance FROM accounts " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
ResultSet r = s.getResultSet();
if (!r.next())
throw new BankingException("Bad account name or password");
return r.getInt(1);
}
finally { try { s.close(); } catch (Exception e) {} }
| public synchronized FunnyMoney | withdraw(java.lang.String name, java.lang.String password, int amount)Withdraw the specified amount from the named account
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
if (balance < amount)
throw new BankingException("Insufficient Funds");
s = db.createStatement();
// Update the account balance
s.executeUpdate("UPDATE accounts " +
"SET balance = " + (balance - amount) + " " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
// Add a row to the transaction history
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Withdrew " + amount +
" at " + new Date() + "')");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
return new FunnyMoney(amount);
|
|