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:
mdb
2006-04-29 18:54:47 +00:00
parent 5fc9cafc36
commit 7a4588e859
@@ -142,104 +142,111 @@ public class SimpleRepository extends Repository
Thread.dumpStack();
}
try {
// obtain our database connection and associated goodies
conn = _provider.getConnection(_dbident, readOnly);
liaison = LiaisonRegistry.getLiaison(conn);
// obtain our database connection and associated goodies
conn = _provider.getConnection(_dbident, readOnly);
// find out if we support transactions
DatabaseMetaData dmd = conn.getMetaData();
if (dmd != null) {
supportsTransactions = dmd.supportsTransactions();
}
// make sure that no one else performs a database operation using the
// same connection until we're done
synchronized (conn) {
try {
liaison = LiaisonRegistry.getLiaison(conn);
// turn off auto-commit
conn.setAutoCommit(false);
// find out if we support transactions
DatabaseMetaData dmd = conn.getMetaData();
if (dmd != null) {
supportsTransactions = dmd.supportsTransactions();
}
// let derived classes do any got-connection processing
gotConnection(conn);
// turn off auto-commit
conn.setAutoCommit(false);
// invoke the operation
attemptedOperation = true;
rv = op.invoke(conn, liaison);
// let derived classes do any got-connection processing
gotConnection(conn);
// commit the transaction
if (supportsTransactions) {
conn.commit();
}
// invoke the operation
attemptedOperation = true;
rv = op.invoke(conn, liaison);
// return the operation result
return rv;
// commit the transaction
if (supportsTransactions) {
conn.commit();
}
} 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)
// return the operation result
return rv;
} 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 {
if (supportsTransactions && !conn.isClosed()) {
conn.rollback();
}
} catch (SQLException rbe) {
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) {
// let the connection 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 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);
}
}
// we'll only fall through here if the above code failed due to a
// transient exception (the connection was closed for being idle, for
// example) and we've been asked to retry; so let's do so
return execute(op, false, readOnly);
}
/**