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
This commit is contained in:
mdb
2001-03-19 22:59:08 +00:00
parent a6854ab613
commit e13e406cfd
2 changed files with 170 additions and 27 deletions
@@ -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; package com.samskivert.jdbc;
@@ -52,4 +52,19 @@ public abstract class MySQLRepository extends Repository
String msg = sqe.getMessage(); String msg = sqe.getMessage();
return (msg != null && msg.indexOf("Duplicate entry") != -1); 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);
}
} }
@@ -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; package com.samskivert.jdbc;
@@ -7,6 +7,7 @@ import java.sql.*;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import com.samskivert.Log;
import com.samskivert.util.*; import com.samskivert.util.*;
import com.samskivert.jdbc.jora.*; import com.samskivert.jdbc.jora.*;
@@ -37,30 +38,74 @@ public abstract class Repository
public Repository (Properties props) public Repository (Properties props)
throws SQLException throws SQLException
{ {
// create our JORA session // extract our configuration parameters
String dclass = _dclass =
requireProp(props, "driver", "No driver class specified."); requireProp(props, "driver", "No driver class specified.");
_session = new Session(dclass); _url =
// connect the session to the database
String url =
requireProp(props, "url", "No driver url specified."); requireProp(props, "url", "No driver url specified.");
String username = _username =
requireProp(props, "username", "No driver username specified."); requireProp(props, "username", "No driver username specified.");
String password = _password =
requireProp(props, "password", "No driver password specified."); requireProp(props, "password", "No driver password specified.");
// the only reason session.open() fails is class not found // establish a connection to the database
if (!_session.open(url, username, password)) { establishConnection();
throw new SQLException("Unable to load JDBC driver class: " + }
dclass);
}
// set auto-commit to false /** Establishes a connection to the database. */
_session.connection.setAutoCommit(false); protected void establishConnection ()
throws SQLException
{
// bail out if we've already got a connection
if (_session != null) {
return;
}
// create our table objects try {
createTables(); // 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 () public void shutdown ()
throws SQLException 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 * Executes the supplied operation without attempting to reestablish
* the session unless an SQLException or runtime error occurs, in * the database connection in the event of a transient failure.
* which case a call to rollback() is executed on the session.
*/ */
protected void execute (Operation op) protected void execute (Operation op)
throws SQLException 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 { try {
// invoke the operation // invoke the operation
op.invoke(); op.invoke();
// commit the session // commit the session
_session.commit(); if (supportsTransactions()) {
_session.commit();
}
} catch (SQLException sqe) { } catch (SQLException sqe) {
// back out our changes if something got hosed // back out our changes if something got hosed
_session.rollback(); if (supportsTransactions()) {
throw sqe; 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) { } catch (RuntimeException rte) {
// back out our changes if something got hosed // 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; 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.
*
* <p> 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 Session _session;
protected String _dclass;
protected String _url;
protected String _username;
protected String _password;
} }