From e13e406cfdb7970221891fea38aa9d53b4c6908c Mon Sep 17 00:00:00 2001 From: mdb Date: Mon, 19 Mar 2001 22:59:08 +0000 Subject: [PATCH] Added the ability to retry operations if a transient failure occurs. Also cleaned up connection handling so that the connection to the database can properly be closed and reestablished on transient failure. git-svn-id: https://samskivert.googlecode.com/svn/trunk@103 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/jdbc/MySQLRepository.java | 17 +- .../java/com/samskivert/jdbc/Repository.java | 180 +++++++++++++++--- 2 files changed, 170 insertions(+), 27 deletions(-) diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/MySQLRepository.java b/projects/samskivert/src/java/com/samskivert/jdbc/MySQLRepository.java index 2683cd3f..7e0222cd 100644 --- a/projects/samskivert/src/java/com/samskivert/jdbc/MySQLRepository.java +++ b/projects/samskivert/src/java/com/samskivert/jdbc/MySQLRepository.java @@ -1,5 +1,5 @@ // -// $Id: MySQLRepository.java,v 1.2 2001/03/01 22:59:51 mdb Exp $ +// $Id: MySQLRepository.java,v 1.3 2001/03/19 22:59:08 mdb Exp $ package com.samskivert.jdbc; @@ -52,4 +52,19 @@ public abstract class MySQLRepository extends Repository String msg = sqe.getMessage(); return (msg != null && msg.indexOf("Duplicate entry") != -1); } + + /** + * Determines whether or not the supplied SQL exception is a transient + * failure, meaning one that is not related to the SQL being executed, + * but instead to the environment at the time of execution, like the + * connection to the database having been lost. + * + * @return true if the exception was thrown due to a transient + * failure, false if not. + */ + protected boolean isTransientException (SQLException sqe) + { + String msg = sqe.getMessage(); + return (msg != null && msg.indexOf("Lost connection") != -1); + } } diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/Repository.java b/projects/samskivert/src/java/com/samskivert/jdbc/Repository.java index 70a3a127..9fcd176c 100644 --- a/projects/samskivert/src/java/com/samskivert/jdbc/Repository.java +++ b/projects/samskivert/src/java/com/samskivert/jdbc/Repository.java @@ -1,5 +1,5 @@ // -// $Id: Repository.java,v 1.3 2001/03/01 02:04:09 mdb Exp $ +// $Id: Repository.java,v 1.4 2001/03/19 22:59:08 mdb Exp $ package com.samskivert.jdbc; @@ -7,6 +7,7 @@ import java.sql.*; import java.util.List; import java.util.Properties; +import com.samskivert.Log; import com.samskivert.util.*; import com.samskivert.jdbc.jora.*; @@ -37,30 +38,74 @@ public abstract class Repository public Repository (Properties props) throws SQLException { - // create our JORA session - String dclass = + // extract our configuration parameters + _dclass = requireProp(props, "driver", "No driver class specified."); - _session = new Session(dclass); - - // connect the session to the database - String url = + _url = requireProp(props, "url", "No driver url specified."); - String username = + _username = requireProp(props, "username", "No driver username specified."); - String password = + _password = requireProp(props, "password", "No driver password specified."); - // the only reason session.open() fails is class not found - if (!_session.open(url, username, password)) { - throw new SQLException("Unable to load JDBC driver class: " + - dclass); - } + // establish a connection to the database + establishConnection(); + } - // set auto-commit to false - _session.connection.setAutoCommit(false); + /** Establishes a connection to the database. */ + protected void establishConnection () + throws SQLException + { + // bail out if we've already got a connection + if (_session != null) { + return; + } - // create our table objects - createTables(); + try { + // create our JORA session + _session = new Session(_dclass); + + // connect the session to the database. the only reason + // session.open() fails is class not found + if (!_session.open(_url, _username, _password)) { + throw new SQLException("Unable to load JDBC driver class: " + + _dclass); + } + + // set auto-commit to false + if (supportsTransactions()) { + _session.connection.setAutoCommit(false); + } + + // create our table objects + createTables(); + + } catch (SQLException sqe) { + // if we have any problems establishing a connection, close + // and clear out our session reference to prevent attempted + // use of a half initialized session + try { + shutdown(); + } catch (SQLException sqe2) { + Log.warning("Error shutting down half-initialized " + + "connection [primary_failure=" + sqe + + ", secondary_failure=" + sqe2 + "]."); + } + throw sqe; + } + } + + /** + * Shuts down the existing connection to the database and + * reestablishes the connection. + */ + public void reestablishConnection () + throws SQLException + { + // first close the old connection + shutdown(); + // then open a new one + establishConnection(); } /** @@ -91,7 +136,10 @@ public abstract class Repository public void shutdown () throws SQLException { - _session.close(); + if (_session != null) { + _session.close(); + _session = null; + } } /** @@ -105,31 +153,111 @@ public abstract class Repository } /** - * Executes the supplied operation followed by a call to commit() on - * the session unless an SQLException or runtime error occurs, in - * which case a call to rollback() is executed on the session. + * Executes the supplied operation without attempting to reestablish + * the database connection in the event of a transient failure. */ protected void execute (Operation op) throws SQLException { + execute(op, true); + } + + /** + * Executes the supplied operation followed by a call to commit() on + * the session unless an SQLException or runtime error occurs, in + * which case a call to rollback() is executed on the session. + * + * @param retryOnTransientFailure if true and the operation fails due + * to an transient failure (like losing the connection to the database + * or deadlock detection), the connection to the database will be + * reestablished and the operation attempted once more. + */ + protected void execute (Operation op, boolean retryOnTransientFailure) + throws SQLException + { + // make sure our connection is established + establishConnection(); + try { // invoke the operation op.invoke(); // commit the session - _session.commit(); + if (supportsTransactions()) { + _session.commit(); + } } catch (SQLException sqe) { // back out our changes if something got hosed - _session.rollback(); - throw sqe; + if (supportsTransactions()) { + try { + _session.rollback(); + } catch (SQLException rbe) { + Log.warning("Unable to roll back operation."); + Log.logStackTrace(rbe); + } + } + + // if this is a transient failure and we've been requested to + // retry such failures, close and reopen the database + // connection and try one more time + if (retryOnTransientFailure && isTransientException(sqe)) { + Log.info("Transient failure executing operation, " + + "retrying [error=" + sqe + "]."); + // to reset the connection, we simply close it. it will be + // reopened on our subsequent call to execute() + shutdown(); + execute(op, false); + + } else { + throw sqe; + } } catch (RuntimeException rte) { // back out our changes if something got hosed - _session.rollback(); + if (supportsTransactions()) { + try { + _session.rollback(); + } catch (SQLException rbe) { + Log.warning("Unable to roll back operation."); + Log.logStackTrace(rbe); + } + } throw rte; } } + /** + * Specializations of this class for particular RDBMS/JDBC driver + * combinations should override this method and indicate whether or + * not transactions are supported. + */ + protected boolean supportsTransactions () + { + return false; + } + + /** + * Determines whether or not the supplied SQL exception is a transient + * failure, meaning one that is not related to the SQL being executed, + * but instead to the environment at the time of execution, like the + * connection to the database having been lost. + * + *

Specializations of the repository class for particular + * database/JDBC driver combos should override this function and match + * the appropriate exceptions. + * + * @return true if the exception was thrown due to a transient + * failure, false if not. + */ + protected boolean isTransientException (SQLException sqe) + { + return false; + } + protected Session _session; + protected String _dclass; + protected String _url; + protected String _username; + protected String _password; }