Require a distinction to be made between read-only and read-write queries so

that we can route read-only queries to one or more read-only mirrors of a
database (the MySQL database drivers supports round-robin load balancing of
queries to read-only slaves).


git-svn-id: https://samskivert.googlecode.com/svn/trunk@1822 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2006-04-13 18:01:29 +00:00
parent 86bce6f08f
commit 790d0dd35f
4 changed files with 88 additions and 41 deletions
@@ -48,6 +48,8 @@ public interface ConnectionProvider
* connection when it is released if appropriate.
*
* @param ident the database connection identifier.
* @param readOnly whether or not the connection may be to a read-only
* mirror of the repository.
*
* @return an active JDBC connection (which may have come from a
* connection pool).
@@ -55,7 +57,7 @@ public interface ConnectionProvider
* @exception PersistenceException thrown if a problem occurs trying
* to open the requested connection.
*/
public Connection getConnection (String ident)
public Connection getConnection (String ident, boolean readOnly)
throws PersistenceException;
/**
@@ -64,10 +66,13 @@ public interface ConnectionProvider
*
* @param ident the database identifier used when obtaining this
* connection.
* @param readOnly the same value that was passed to {@link #getConnection}
* to obtain this connection.
* @param conn the connection to release (back into the pool or to be
* closed if pooling is not going on).
*/
public void releaseConnection (String ident, Connection conn);
public void releaseConnection (String ident, boolean readOnly,
Connection conn);
/**
* Called by the repository if a failure occurred on the connection.
@@ -77,10 +82,12 @@ public interface ConnectionProvider
*
* @param ident the database identifier used when obtaining this
* connection.
* @param readOnly the same value that was passed to {@link #getConnection}
* to obtain this connection.
* @param conn the connection that failed.
* @param error the error thrown by the connection (which may be used
* to determine if the connection should be closed or can be reused).
*/
public void connectionFailed (String ident, Connection conn,
SQLException error);
public void connectionFailed (String ident, boolean readOnly,
Connection conn, SQLException error);
}
@@ -62,7 +62,7 @@ public abstract class JORARepository extends SimpleRepository
protected <T> int insert (final Table<T> table, final T object)
throws PersistenceException
{
return execute(new Operation<Integer>() {
return executeUpdate(new Operation<Integer>() {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -79,7 +79,7 @@ public abstract class JORARepository extends SimpleRepository
protected <T> void update (final Table<T> table, final T object)
throws PersistenceException
{
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -188,7 +188,7 @@ public abstract class JORARepository extends SimpleRepository
protected <T> int store (final Table<T> table, final T object)
throws PersistenceException
{
return execute(new Operation<Integer>() {
return executeUpdate(new Operation<Integer>() {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -211,7 +211,7 @@ public abstract class JORARepository extends SimpleRepository
{
final FieldMask mask = table.getFieldMask();
mask.setModified(field);
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -233,7 +233,7 @@ public abstract class JORARepository extends SimpleRepository
for (int ii = 0; ii < fields.length; ii++) {
mask.setModified(fields[ii]);
}
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -246,7 +246,7 @@ public abstract class JORARepository extends SimpleRepository
protected <T> void delete (final Table<T> table, final T object)
throws PersistenceException
{
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -70,7 +70,7 @@ public class SimpleRepository extends Repository
// give the repository a chance to do any schema migration before
// things get further underway
try {
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -85,7 +85,7 @@ public class SimpleRepository extends Repository
}
/**
* Executes the supplied operation. In the event of a transient
* Executes the supplied read-only operation. In the event of a transient
* failure, the repository will attempt to reestablish the database
* connection and try the operation again.
*
@@ -94,7 +94,20 @@ public class SimpleRepository extends Repository
protected <V> V execute (Operation<V> op)
throws PersistenceException
{
return execute(op, true);
return execute(op, true, true);
}
/**
* Executes the supplied read-write operation. In the event of a transient
* failure, the repository will attempt to reestablish the database
* connection and try the operation again.
*
* @return whatever value is returned by the invoked operation.
*/
protected <V> V executeUpdate (Operation<V> op)
throws PersistenceException
{
return execute(op, true, false);
}
/**
@@ -104,14 +117,16 @@ public class SimpleRepository extends Repository
* case a call to <code>rollback()</code> is executed on the
* connection.
*
* @param retryOnTransientFailure if true and the operation fails due
* to a transient failure (like losing the connection to the database
* or deadlock detection), the connection to the database will be
* @param retryOnTransientFailure if true and the operation fails due to a
* transient failure (like losing the connection to the database or
* deadlock detection), the connection to the database will be
* reestablished (if necessary) and the operation attempted once more.
* @param readOnly whether or not to request a read-only connection.
*
* @return whatever value is returned by the invoked operation.
*/
protected <V> V execute (Operation<V> op, boolean retryOnTransientFailure)
protected <V> V execute (Operation<V> op, boolean retryOnTransientFailure,
boolean readOnly)
throws PersistenceException
{
Connection conn = null;
@@ -129,7 +144,7 @@ public class SimpleRepository extends Repository
try {
// obtain our database connection and associated goodies
conn = _provider.getConnection(_dbident);
conn = _provider.getConnection(_dbident, readOnly);
liaison = LiaisonRegistry.getLiaison(conn);
// find out if we support transactions
@@ -172,7 +187,7 @@ public class SimpleRepository extends Repository
if (conn != null) {
// let the connection provider know that the connection failed
_provider.connectionFailed(_dbident, conn, sqe);
_provider.connectionFailed(_dbident, readOnly, conn, sqe);
// clear out the reference so that we don't release it later
conn = null;
@@ -189,7 +204,7 @@ public class SimpleRepository extends Repository
String msg = StringUtil.split("" + sqe, "\n")[0];
Log.info("Transient failure executing operation, " +
"retrying [error=" + msg + "].");
return execute(op, false);
return execute(op, false, readOnly);
}
String err = "Operation invocation failed";
@@ -222,7 +237,7 @@ public class SimpleRepository extends Repository
} finally {
if (conn != null) {
// release the database connection
_provider.releaseConnection(_dbident, conn);
_provider.releaseConnection(_dbident, readOnly, conn);
}
}
}
@@ -234,7 +249,7 @@ public class SimpleRepository extends Repository
protected int update (final String query)
throws PersistenceException
{
return execute(new Operation<Integer>() {
return executeUpdate(new Operation<Integer>() {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -257,7 +272,7 @@ public class SimpleRepository extends Repository
protected void checkedUpdate (final String query, final int count)
throws PersistenceException
{
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -287,7 +302,7 @@ public class SimpleRepository extends Repository
protected void maintenance (final String action, final String table)
throws PersistenceException
{
execute(new Operation<Object>() {
executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
@@ -127,13 +127,15 @@ public class StaticConnectionProvider implements ConnectionProvider
}
// documentation inherited
public Connection getConnection (String ident)
public Connection getConnection (String ident, boolean readOnly)
throws PersistenceException
{
Mapping conmap = _idents.get(ident);
String mapkey = ident + ":" + readOnly;
Mapping conmap = _idents.get(mapkey);
// open the connection if we haven't already
if (conmap == null) {
String[] defaults;
Properties props =
PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
@@ -147,9 +149,11 @@ public class StaticConnectionProvider implements ConnectionProvider
err = "No driver password specified [ident=" + ident + "].";
String password = requireProp(props, "password", err);
// we cache connections by username+url to avoid making more
// that one connection to a particular database server
String key = username + "@" + url;
// if this is a read-only connection,
// we cache connections by username+url+readOnly to avoid making
// more that one connection to a particular database server
String key = username + "@" + url + ":" + readOnly;
conmap = _keys.get(key);
if (conmap == null) {
Log.debug("Creating " + key + " for " + ident + ".");
@@ -157,42 +161,53 @@ public class StaticConnectionProvider implements ConnectionProvider
conmap.key = key;
conmap.connection =
openConnection(driver, url, username, password);
// make the connection read-only to let the JDBC driver know
// that it can and should use the read-only mirror(s)
if (readOnly) {
try {
conmap.connection.setReadOnly(true);
} catch (SQLException sqe) {
closeConnection(ident, conmap.connection);
err = "Failed to make connection read-only " +
"[key=" + key + ", ident=" + ident + "].";
throw new PersistenceException(err, sqe);
}
}
_keys.put(key, conmap);
} else {
Log.debug("Reusing " + key + " for " + ident + ".");
}
// cache the connection
conmap.idents.add(ident);
_idents.put(ident, conmap);
conmap.idents.add(mapkey);
_idents.put(mapkey, conmap);
}
return conmap.connection;
}
// documentation inherited
public void releaseConnection (String ident, Connection conn)
public void releaseConnection (String ident, boolean readOnly,
Connection conn)
{
// nothing to do here, all is well
}
// documentation inherited
public void connectionFailed (String ident, Connection conn,
SQLException error)
public void connectionFailed (String ident, boolean readOnly,
Connection conn, SQLException error)
{
Mapping conmap = _idents.get(ident);
String mapkey = ident + ":" + readOnly;
Mapping conmap = _idents.get(mapkey);
if (conmap == null) {
Log.warning("Unknown connection failed!? [ident=" + ident + "].");
return;
}
// attempt to close the connection
try {
conmap.connection.close();
} catch (SQLException sqe) {
Log.warning("Error closing failed connection [ident=" + ident +
", error=" + sqe + "].");
}
closeConnection(ident, conmap.connection);
// clear it from our mapping tables
for (int ii = 0; ii < conmap.idents.size(); ii++) {
@@ -224,6 +239,16 @@ public class StaticConnectionProvider implements ConnectionProvider
}
}
protected void closeConnection (String ident, Connection conn)
{
try {
conn.close();
} catch (SQLException sqe) {
Log.warning("Error closing failed connection [ident=" + ident +
", error=" + sqe + "].");
}
}
protected static String requireProp (
Properties props, String name, String errmsg)
throws PersistenceException