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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user