We need to ensure that only one repository operation is active on a particular
JDBC connection at a time. JDBC connections are thread safe, but we frequently have to execute statements like: insert into FOO values(0, blah, blah); select LAST_INSERTED_ID() to get the value assigned to an auto-increment column. If another thread slipped in between those statements and inserted something into its own auto-increment column having table, we'd get funny business. And indeed we very occasionally see this race condition happen in the thread-happy webapps. git-svn-id: https://samskivert.googlecode.com/svn/trunk@1838 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -142,104 +142,111 @@ public class SimpleRepository extends Repository
|
|||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// obtain our database connection and associated goodies
|
||||||
// obtain our database connection and associated goodies
|
conn = _provider.getConnection(_dbident, readOnly);
|
||||||
conn = _provider.getConnection(_dbident, readOnly);
|
|
||||||
liaison = LiaisonRegistry.getLiaison(conn);
|
|
||||||
|
|
||||||
// find out if we support transactions
|
// make sure that no one else performs a database operation using the
|
||||||
DatabaseMetaData dmd = conn.getMetaData();
|
// same connection until we're done
|
||||||
if (dmd != null) {
|
synchronized (conn) {
|
||||||
supportsTransactions = dmd.supportsTransactions();
|
try {
|
||||||
}
|
liaison = LiaisonRegistry.getLiaison(conn);
|
||||||
|
|
||||||
// turn off auto-commit
|
// find out if we support transactions
|
||||||
conn.setAutoCommit(false);
|
DatabaseMetaData dmd = conn.getMetaData();
|
||||||
|
if (dmd != null) {
|
||||||
|
supportsTransactions = dmd.supportsTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
// let derived classes do any got-connection processing
|
// turn off auto-commit
|
||||||
gotConnection(conn);
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
// invoke the operation
|
// let derived classes do any got-connection processing
|
||||||
attemptedOperation = true;
|
gotConnection(conn);
|
||||||
rv = op.invoke(conn, liaison);
|
|
||||||
|
|
||||||
// commit the transaction
|
// invoke the operation
|
||||||
if (supportsTransactions) {
|
attemptedOperation = true;
|
||||||
conn.commit();
|
rv = op.invoke(conn, liaison);
|
||||||
}
|
|
||||||
|
|
||||||
// return the operation result
|
// commit the transaction
|
||||||
return rv;
|
if (supportsTransactions) {
|
||||||
|
conn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (SQLException sqe) {
|
// return the operation result
|
||||||
if (attemptedOperation) {
|
return rv;
|
||||||
// back out our changes if something got hosed (but not if
|
|
||||||
// the hosage was a result of losing our connection)
|
} catch (SQLException sqe) {
|
||||||
|
if (attemptedOperation) {
|
||||||
|
// back out our changes if something got hosed (but not if
|
||||||
|
// the hosage was a result of losing our connection)
|
||||||
|
try {
|
||||||
|
if (supportsTransactions && !conn.isClosed()) {
|
||||||
|
conn.rollback();
|
||||||
|
}
|
||||||
|
} catch (SQLException rbe) {
|
||||||
|
Log.warning("Unable to roll back operation " +
|
||||||
|
"[err=" + sqe + ", rberr=" + rbe + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn != null) {
|
||||||
|
// let the provider know that the connection failed
|
||||||
|
_provider.connectionFailed(_dbident, readOnly, conn, sqe);
|
||||||
|
// clear out the reference so that we don't release it later
|
||||||
|
conn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!retryOnTransientFailure || liaison == null ||
|
||||||
|
!liaison.isTransientException(sqe)) {
|
||||||
|
String err = "Operation invocation failed";
|
||||||
|
throw new PersistenceException(err, sqe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the MySQL JDBC driver has the annoying habit of including
|
||||||
|
// the embedded exception stack trace in the message of their
|
||||||
|
// outer exception; if I want a fucking stack trace, I'll call
|
||||||
|
// printStackTrace() thanksverymuch
|
||||||
|
String msg = StringUtil.split("" + sqe, "\n")[0];
|
||||||
|
Log.info("Transient failure executing operation, " +
|
||||||
|
"retrying [error=" + msg + "].");
|
||||||
|
|
||||||
|
} catch (PersistenceException pe) {
|
||||||
|
// back out our changes if something got hosed
|
||||||
try {
|
try {
|
||||||
if (supportsTransactions && !conn.isClosed()) {
|
if (supportsTransactions && !conn.isClosed()) {
|
||||||
conn.rollback();
|
conn.rollback();
|
||||||
}
|
}
|
||||||
} catch (SQLException rbe) {
|
} catch (SQLException rbe) {
|
||||||
Log.warning("Unable to roll back operation " +
|
Log.warning("Unable to roll back operation " +
|
||||||
"[origerr=" + sqe + ", rberr=" + rbe + "].");
|
"[origerr=" + pe + ", rberr=" + rbe + "].");
|
||||||
|
}
|
||||||
|
throw pe;
|
||||||
|
|
||||||
|
} catch (RuntimeException rte) {
|
||||||
|
// back out our changes if something got hosed
|
||||||
|
try {
|
||||||
|
if (supportsTransactions &&
|
||||||
|
conn != null && !conn.isClosed()) {
|
||||||
|
conn.rollback();
|
||||||
|
}
|
||||||
|
} catch (SQLException rbe) {
|
||||||
|
Log.warning("Unable to roll back operation " +
|
||||||
|
"[origerr=" + rte + ", rberr=" + rbe + "].");
|
||||||
|
}
|
||||||
|
throw rte;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
// release the database connection
|
||||||
|
_provider.releaseConnection(_dbident, readOnly, conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (conn != null) {
|
// we'll only fall through here if the above code failed due to a
|
||||||
// let the connection provider know that the connection failed
|
// transient exception (the connection was closed for being idle, for
|
||||||
_provider.connectionFailed(_dbident, readOnly, conn, sqe);
|
// example) and we've been asked to retry; so let's do so
|
||||||
|
return execute(op, false, readOnly);
|
||||||
// clear out the reference so that we don't release it later
|
|
||||||
conn = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is a transient failure and we've been requested to
|
|
||||||
// retry such failures, try one more time
|
|
||||||
if (retryOnTransientFailure &&
|
|
||||||
liaison != null && liaison.isTransientException(sqe)) {
|
|
||||||
// the MySQL JDBC driver has the annoying habit of
|
|
||||||
// including the embedded exception stack trace in the
|
|
||||||
// message of their outer exception; if I want a fucking
|
|
||||||
// stack trace, I'll call printStackTrace() thanksverymuch
|
|
||||||
String msg = StringUtil.split("" + sqe, "\n")[0];
|
|
||||||
Log.info("Transient failure executing operation, " +
|
|
||||||
"retrying [error=" + msg + "].");
|
|
||||||
return execute(op, false, readOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
String err = "Operation invocation failed";
|
|
||||||
throw new PersistenceException(err, sqe);
|
|
||||||
|
|
||||||
} catch (PersistenceException pe) {
|
|
||||||
// back out our changes if something got hosed
|
|
||||||
try {
|
|
||||||
if (supportsTransactions && !conn.isClosed()) {
|
|
||||||
conn.rollback();
|
|
||||||
}
|
|
||||||
} catch (SQLException rbe) {
|
|
||||||
Log.warning("Unable to roll back operation " +
|
|
||||||
"[origerr=" + pe + ", rberr=" + rbe + "].");
|
|
||||||
}
|
|
||||||
throw pe;
|
|
||||||
|
|
||||||
} catch (RuntimeException rte) {
|
|
||||||
// back out our changes if something got hosed
|
|
||||||
try {
|
|
||||||
if (conn != null && supportsTransactions && !conn.isClosed()) {
|
|
||||||
conn.rollback();
|
|
||||||
}
|
|
||||||
} catch (SQLException rbe) {
|
|
||||||
Log.warning("Unable to roll back operation " +
|
|
||||||
"[origerr=" + rte + ", rberr=" + rbe + "].");
|
|
||||||
}
|
|
||||||
throw rte;
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (conn != null) {
|
|
||||||
// release the database connection
|
|
||||||
_provider.releaseConnection(_dbident, readOnly, conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user