Some infrastructure for transaction support.
ConnectionProvider may normally return the same connection for a call to getConnection(), so we add a separate getTxConnection() which is guaranteed to always return a connection that's not in use by anyone else. The DataSourceConnectionProvider already meets that requirement because it returns connections from a pool, but the StaticConnectionProvider does not. In the latter case, we just open a new connection to the database every time you do a transaction. Since StaticConnectionProvider is only used for testing (in conjunction with Depot anyway, Yohoho uses it in production IIRC), this is not a big deal.
This commit is contained in:
@@ -65,6 +65,23 @@ public interface ConnectionProvider
|
||||
*/
|
||||
void connectionFailed (String ident, boolean readOnly, Connection conn, SQLException error);
|
||||
|
||||
/**
|
||||
* Returns a connection that can be used in a transaction. See {@link #getConnection}.
|
||||
*/
|
||||
Connection getTxConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Releases a connection obtained by {@link #getTxConnection}. See {@link #releaseConnection}.
|
||||
*/
|
||||
void releaseTxConnection (String ident, boolean readOnly, Connection conn);
|
||||
|
||||
/**
|
||||
* Reports failure of a connection obtained by {@link #getTxConnection}. See {@link
|
||||
* #connectionFailed}.
|
||||
*/
|
||||
void txConnectionFailed (String ident, boolean readOnly, Connection conn, SQLException error);
|
||||
|
||||
/**
|
||||
* Returns the URL associated with this database identifier. This should be the same value that
|
||||
* would be used if {@link #getConnection} were called.
|
||||
|
||||
@@ -15,7 +15,8 @@ import static com.samskivert.jdbc.Log.log;
|
||||
|
||||
/**
|
||||
* Provides connections using a pair of {@link DataSource} instances (one for read-only operations
|
||||
* and one for read-write operations).
|
||||
* and one for read-write operations). Note: if transactions are going to be used, the data sources
|
||||
* must be pooled data sources, otherwise bad things will happen.
|
||||
*/
|
||||
public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
@@ -36,7 +37,7 @@ public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
_writeSource = writeSource;
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
// from ConnectionProvider
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
@@ -55,7 +56,7 @@ public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
// from ConnectionProvider
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
try {
|
||||
@@ -66,7 +67,7 @@ public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
// from ConnectionProvider
|
||||
public void connectionFailed (String ident, boolean readOnly, Connection conn,
|
||||
SQLException error)
|
||||
{
|
||||
@@ -78,13 +79,36 @@ public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
// from ConnectionProvider
|
||||
public Connection getTxConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
// our connections are pooled, so we can just get them normally
|
||||
return getConnection(ident, readOnly);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void releaseTxConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
// our connections are pooled, so we can just release them normally
|
||||
releaseConnection(ident, readOnly, conn);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void txConnectionFailed (String ident, boolean readOnly, Connection conn,
|
||||
SQLException error)
|
||||
{
|
||||
// our connections are pooled, so we can just fail them normally
|
||||
connectionFailed(ident, readOnly, conn, error);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public String getURL (String ident)
|
||||
{
|
||||
return _url;
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
// from ConnectionProvider
|
||||
public void shutdown ()
|
||||
{
|
||||
// nothing doing, the caller has to shutdown the datasources
|
||||
|
||||
@@ -101,82 +101,20 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
return props.getProperty("url");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from ConnectionProvider
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
String mapkey = ident + ":" + readOnly;
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
|
||||
// open the connection if we haven't already
|
||||
if (conmap == null) {
|
||||
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
|
||||
// get the JDBC configuration info
|
||||
String err = "No driver class specified [ident=" + ident + "].";
|
||||
String driver = requireProp(props, "driver", err);
|
||||
err = "No driver URL specified [ident=" + ident + "].";
|
||||
String url = requireProp(props, "url", err);
|
||||
err = "No driver username specified [ident=" + ident + "].";
|
||||
String username = requireProp(props, "username", err);
|
||||
String password = props.getProperty("password", "");
|
||||
String autoCommit = props.getProperty("autocommit");
|
||||
|
||||
// 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 + ".");
|
||||
conmap = new Mapping();
|
||||
conmap.key = key;
|
||||
conmap.connection = openConnection(driver, url, username, password);
|
||||
|
||||
// if we were requested to configure auto-commit, then do so
|
||||
if (autoCommit != null) {
|
||||
try {
|
||||
conmap.connection.setAutoCommit(Boolean.valueOf(autoCommit));
|
||||
} catch (SQLException sqe) {
|
||||
closeConnection(ident, conmap.connection);
|
||||
err = "Failed to configure auto-commit [key=" + key +
|
||||
", ident=" + ident + ", autoCommit=" + autoCommit + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(mapkey);
|
||||
_idents.put(mapkey, conmap);
|
||||
}
|
||||
|
||||
return conmap.connection;
|
||||
return getMapping(ident, readOnly).getConnection(ident);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from ConnectionProvider
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
// nothing to do here, all is well
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from ConnectionProvider
|
||||
public void connectionFailed (
|
||||
String ident, boolean readOnly, Connection conn, SQLException error)
|
||||
{
|
||||
@@ -184,17 +122,28 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
if (conmap == null) {
|
||||
log.warning("Unknown connection failed!?", "key", mapkey);
|
||||
return;
|
||||
} else {
|
||||
conmap.closeConnection(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to close the connection
|
||||
closeConnection(ident, conmap.connection);
|
||||
// from ConnectionProvider
|
||||
public Connection getTxConnection (String ident, boolean readOnly) throws PersistenceException
|
||||
{
|
||||
return getMapping(ident, readOnly).openConnection(ident, null);
|
||||
}
|
||||
|
||||
// clear it from our mapping tables
|
||||
for (int ii = 0; ii < conmap.idents.size(); ii++) {
|
||||
_idents.remove(conmap.idents.get(ii));
|
||||
}
|
||||
_keys.remove(conmap.key);
|
||||
// from ConnectionProvider
|
||||
public void releaseTxConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
close(conn, ident);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void txConnectionFailed (
|
||||
String ident, boolean readOnly, Connection conn, SQLException error)
|
||||
{
|
||||
close(conn, ident);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
@@ -202,12 +151,7 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
// close all of the connections
|
||||
for (Map.Entry<String, Mapping> entry : _keys.entrySet()) {
|
||||
Mapping conmap = entry.getValue();
|
||||
try {
|
||||
conmap.connection.close();
|
||||
} catch (SQLException sqe) {
|
||||
log.warning("Error shutting down connection", "key", entry.getKey(), "err", sqe);
|
||||
}
|
||||
entry.getValue().closeConnection(entry.getKey());
|
||||
}
|
||||
|
||||
// clear out our mapping tables
|
||||
@@ -215,34 +159,33 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
_idents.clear();
|
||||
}
|
||||
|
||||
protected Connection openConnection (String driver, String url, String username, String passwd)
|
||||
throws PersistenceException
|
||||
{
|
||||
// create an instance of the driver
|
||||
Driver jdriver;
|
||||
try {
|
||||
jdriver = (Driver)Class.forName(driver).newInstance();
|
||||
} catch (Exception e) {
|
||||
String err = "Error loading driver [class=" + driver + "].";
|
||||
throw new PersistenceException(err, e);
|
||||
protected Mapping getMapping (String ident, boolean readOnly) throws PersistenceException {
|
||||
String mapkey = ident + ":" + readOnly;
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
if (conmap != null) return conmap;
|
||||
|
||||
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
Info info = new Info(ident, props);
|
||||
|
||||
// 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 = info.username + "@" + info.url + ":" + readOnly;
|
||||
conmap = _keys.get(key);
|
||||
if (conmap == null) {
|
||||
log.debug("Creating " + key + " for " + ident + ".");
|
||||
_keys.put(key, conmap = new Mapping(key, info, readOnly));
|
||||
|
||||
} else {
|
||||
log.debug("Reusing " + key + " for " + ident + ".");
|
||||
}
|
||||
|
||||
// create the connection
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.put("user", username);
|
||||
props.put("password", passwd);
|
||||
return jdriver.connect(url, props);
|
||||
// cache the connection
|
||||
_idents.put(mapkey, conmap);
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
String err = "Error creating database connection [driver=" + driver + ", url=" + url +
|
||||
", username=" + username + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
return conmap;
|
||||
}
|
||||
|
||||
protected void closeConnection (String ident, Connection conn)
|
||||
{
|
||||
protected static void close (Connection conn, String ident) {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException sqe) {
|
||||
@@ -250,30 +193,119 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
}
|
||||
|
||||
protected static String requireProp (Properties props, String name, String errmsg)
|
||||
throws PersistenceException
|
||||
{
|
||||
String value = props.getProperty(name);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
errmsg = "Unable to get connection. " + errmsg; // augment the error message
|
||||
throw new PersistenceException(errmsg);
|
||||
protected static class Info {
|
||||
public final String ident, driver, url, username, password;
|
||||
public final Boolean autoCommit;
|
||||
|
||||
public Info (String ident, Properties props) throws PersistenceException {
|
||||
this.ident = ident;
|
||||
this.driver = requireProp(props, "driver", "No driver class specified");
|
||||
this.url = requireProp(props, "url", "No driver URL specified");
|
||||
this.username = requireProp(props, "username", "No driver username specified");
|
||||
this.password = props.getProperty("password", "");
|
||||
String ac = props.getProperty("autocommit");
|
||||
this.autoCommit = (ac == null) ? null : Boolean.valueOf(ac);
|
||||
}
|
||||
|
||||
protected String requireProp (Properties props, String name,
|
||||
String errmsg) throws PersistenceException {
|
||||
String value = props.getProperty(name);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
errmsg = "Unable to get connection. " + errmsg + " [ident=" + ident + "]";
|
||||
throw new PersistenceException(errmsg);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Contains information on a particular connection to which any number of database identifiers
|
||||
* can be mapped. */
|
||||
* can be mapped. */
|
||||
protected static class Mapping
|
||||
{
|
||||
/** The combination of username and JDBC url that uniquely identifies our database
|
||||
* connection. */
|
||||
public String key;
|
||||
public final String key;
|
||||
|
||||
/** The connection itself. */
|
||||
public Connection connection;
|
||||
public Mapping (String key, Info info, boolean readOnly) {
|
||||
this.key = key;
|
||||
_info = info;
|
||||
_readOnly = readOnly;
|
||||
}
|
||||
|
||||
/** The database identifiers that are mapped to this connection. */
|
||||
public List<String> idents = new ArrayList<String>();
|
||||
/** Returns the main connection for this mapping, (re)opening it if necessary. */
|
||||
public Connection getConnection (String ident) throws PersistenceException {
|
||||
if (_conn == null) _conn = openConnection(ident, _info.autoCommit);
|
||||
return _conn;
|
||||
}
|
||||
|
||||
/** Opens and returns a new connection to this mapping's database. */
|
||||
public Connection openConnection (String ident, Boolean autoCommit)
|
||||
throws PersistenceException {
|
||||
// create an instance of the driver
|
||||
Driver jdriver;
|
||||
try {
|
||||
jdriver = (Driver)Class.forName(_info.driver).newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(
|
||||
"Error loading driver [class=" + _info.driver + "].", e);
|
||||
}
|
||||
|
||||
// create the connection
|
||||
Connection conn;
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.put("user", _info.username);
|
||||
props.put("password", _info.password);
|
||||
conn = jdriver.connect(_info.url, props);
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
throw new PersistenceException(
|
||||
"Error creating database connection [driver=" + _info.driver +
|
||||
", url=" + _info.url + ", username=" + _info.username + "].", sqe);
|
||||
}
|
||||
|
||||
// if we were requested to configure auto-commit, then do so
|
||||
if (autoCommit != null) {
|
||||
try {
|
||||
conn.setAutoCommit(autoCommit);
|
||||
} catch (SQLException sqe) {
|
||||
close(conn, ident);
|
||||
throw new PersistenceException(
|
||||
"Failed to configure auto-commit [key=" + key + ", ident=" + ident +
|
||||
", autoCommit=" + autoCommit + "].", sqe);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
conn.setReadOnly(true);
|
||||
} catch (SQLException sqe) {
|
||||
close(conn, ident);
|
||||
throw new PersistenceException(
|
||||
"Failed to make connection read-only [key=" + key + ", ident=" + ident +
|
||||
"].", sqe);
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the main connection for this mapping, causing it to be reopened on the next call
|
||||
* to {@link #getConnection}.
|
||||
* @param ident the ident on behalf of which we are operating.
|
||||
*/
|
||||
public void closeConnection (String ident) {
|
||||
if (_conn != null) {
|
||||
close(_conn, ident);
|
||||
_conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected final Info _info;
|
||||
protected final boolean _readOnly;
|
||||
protected Connection _conn;
|
||||
}
|
||||
|
||||
/** Our configuration in the form of a properties object. */
|
||||
|
||||
Reference in New Issue
Block a user