From 790d0dd35f84db059d838c4c70abf9e04a426f76 Mon Sep 17 00:00:00 2001 From: mdb Date: Thu, 13 Apr 2006 18:01:29 +0000 Subject: [PATCH] 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 --- .../samskivert/jdbc/ConnectionProvider.java | 15 +++-- .../com/samskivert/jdbc/JORARepository.java | 12 ++-- .../com/samskivert/jdbc/SimpleRepository.java | 43 +++++++++----- .../jdbc/StaticConnectionProvider.java | 59 +++++++++++++------ 4 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/java/com/samskivert/jdbc/ConnectionProvider.java b/src/java/com/samskivert/jdbc/ConnectionProvider.java index 60c5e7ef..2e95c781 100644 --- a/src/java/com/samskivert/jdbc/ConnectionProvider.java +++ b/src/java/com/samskivert/jdbc/ConnectionProvider.java @@ -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); } diff --git a/src/java/com/samskivert/jdbc/JORARepository.java b/src/java/com/samskivert/jdbc/JORARepository.java index 00cddcbd..347fcf70 100644 --- a/src/java/com/samskivert/jdbc/JORARepository.java +++ b/src/java/com/samskivert/jdbc/JORARepository.java @@ -62,7 +62,7 @@ public abstract class JORARepository extends SimpleRepository protected int insert (final Table table, final T object) throws PersistenceException { - return execute(new Operation() { + return executeUpdate(new Operation() { public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { @@ -79,7 +79,7 @@ public abstract class JORARepository extends SimpleRepository protected void update (final Table table, final T object) throws PersistenceException { - execute(new Operation() { + executeUpdate(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { @@ -188,7 +188,7 @@ public abstract class JORARepository extends SimpleRepository protected int store (final Table table, final T object) throws PersistenceException { - return execute(new Operation() { + return executeUpdate(new Operation() { 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() { + executeUpdate(new Operation() { 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() { + executeUpdate(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { @@ -246,7 +246,7 @@ public abstract class JORARepository extends SimpleRepository protected void delete (final Table table, final T object) throws PersistenceException { - execute(new Operation() { + executeUpdate(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { diff --git a/src/java/com/samskivert/jdbc/SimpleRepository.java b/src/java/com/samskivert/jdbc/SimpleRepository.java index a50ac464..1106c9da 100644 --- a/src/java/com/samskivert/jdbc/SimpleRepository.java +++ b/src/java/com/samskivert/jdbc/SimpleRepository.java @@ -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() { + executeUpdate(new Operation() { 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 execute (Operation 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 executeUpdate (Operation op) + throws PersistenceException + { + return execute(op, true, false); } /** @@ -104,14 +117,16 @@ public class SimpleRepository extends Repository * case a call to rollback() 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 execute (Operation op, boolean retryOnTransientFailure) + protected V execute (Operation 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() { + return executeUpdate(new Operation() { 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() { + executeUpdate(new Operation() { 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() { + executeUpdate(new Operation() { public Object invoke (Connection conn, DatabaseLiaison liaison) throws SQLException, PersistenceException { diff --git a/src/java/com/samskivert/jdbc/StaticConnectionProvider.java b/src/java/com/samskivert/jdbc/StaticConnectionProvider.java index 8a433611..f50c9a08 100644 --- a/src/java/com/samskivert/jdbc/StaticConnectionProvider.java +++ b/src/java/com/samskivert/jdbc/StaticConnectionProvider.java @@ -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