Phase one of the database dialect supports for Depot from Zell. This is all

enhancements to non-Depot JDBC utilities (and dialect agnosticizing
TransitionRepository though we could accomplish that by Depot-izing it as
well).


git-svn-id: https://samskivert.googlecode.com/svn/trunk@2134 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2007-07-23 21:32:22 +00:00
parent 3cd3397ad3
commit 868ebd38b4
11 changed files with 803 additions and 286 deletions
@@ -0,0 +1,233 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.Log;
import com.samskivert.util.StringUtil;
/**
* A superclass to help with the shrinking subset of SQL our supported dialects can agree on,
* or when there is disagreement, implement the most standard-compliant version and let the
* dialectal sub-classes override.
*/
public abstract class BaseLiaison implements DatabaseLiaison
{
public BaseLiaison ()
{
super();
}
// from DatabaseLiaison
public boolean tableExists (Connection conn, String name) throws SQLException
{
ResultSet rs = conn.getMetaData().getTables("", "", name, null);
while (rs.next()) {
String tname = rs.getString("TABLE_NAME");
if (name.equals(tname)) {
return true;
}
}
return false;
}
// from DatabaseLiaison
public boolean tableContainsColumn (Connection conn, String table, String column)
throws SQLException
{
ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
while (rs.next()) {
String tname = rs.getString("TABLE_NAME");
String cname = rs.getString("COLUMN_NAME");
if (tname.equals(table) && cname.equals(column)) {
return true;
}
}
return false;
}
// from DatabaseLiaison
public boolean tableContainsIndex (Connection conn, String table, String index)
throws SQLException
{
ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true);
while (rs.next()) {
String tname = rs.getString("TABLE_NAME");
String iname = rs.getString("INDEX_NAME");
if (tname.equals(table) && index.equals(iname)) {
return true;
}
}
return false;
}
// from DatabaseLiaison
public boolean addIndexToTable (Connection conn, String table, String[] columns, String ixName)
throws SQLException
{
if (tableContainsIndex(conn, table, ixName)) {
return false;
}
ixName = (ixName != null ? ixName : StringUtil.join(columns, "_"));
StringBuilder update = new StringBuilder("CREATE INDEX ").append(indexSQL(ixName)).
append(" on ").append(tableSQL(table)).append(" (");
for (int ii = 0; ii < columns.length; ii ++) {
if (ii > 0) {
update.append(", ");
}
update.append(columnSQL(columns[ii]));
}
update.append(")");
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(update.toString());
stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
Log.info("Database index '" + ixName + "' added to table '" + table + "'");
return true;
}
// from DatabaseLiaison
public boolean changeColumn (Connection conn, String table, String column, String definition)
throws SQLException
{
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
"ALTER TABLE " + tableSQL(table) + " CHANGE " + columnSQL(column) +
" " + column + " " + definition);
stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
Log.info("Database column '" + column + "' of table '" + table +
"' modified to have definition '" + definition + "'.");
return true;
}
// from DatabaseLiaison
public boolean renameColumn (Connection conn, String table, String from, String to)
throws SQLException
{
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
"ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " + columnSQL(from) +
" TO " + columnSQL(to));
if (stmt.executeUpdate() == 1) {
Log.info("Renamed column '" + from + "' on table '" + table + "' to '" + to + "'");
}
} finally {
JDBCUtil.close(stmt);
}
return true;
}
// from DatabaseLiaison
public boolean dropColumn (Connection conn, String table, String column) throws SQLException
{
if (!tableContainsColumn(conn, table, column)) {
return false;
}
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
"ALTER TABLE " + tableSQL(table) + " DROP COLUMN " + columnSQL(column));
if (stmt.executeUpdate() == 1) {
Log.info("Database index '" + column + "' removed from table '" + table + "'.");
}
} finally {
JDBCUtil.close(stmt);
}
return true;
}
// from DatabaseLiaison
public boolean createTableIfMissing (
Connection conn, String table, String[] columns, String[] definitions,
String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
throws SQLException
{
if (tableExists(conn, table)) {
return false;
}
if (columns.length != definitions.length) {
throw new IllegalArgumentException("Column name and definition number mismatch");
}
StringBuilder builder =
new StringBuilder("CREATE TABLE ").append(tableSQL(table)).append(" (");
for (int ii = 0; ii < columns.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(columnSQL(columns[ii])).append(" ").append(definitions[ii]);
}
if (uniqueConstraintColumns != null && uniqueConstraintColumns.length > 0) {
for (String[] uCols : uniqueConstraintColumns) {
builder.append(", UNIQUE (");
for (int ii = 0; ii < uCols.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(columnSQL(uCols[ii]));
}
builder.append(")");
}
}
if (primaryKeyColumns != null && primaryKeyColumns.length > 0) {
builder.append(", PRIMARY KEY (");
for (int ii = 0; ii < primaryKeyColumns.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(columnSQL(primaryKeyColumns[ii]));
}
builder.append(")");
}
builder.append(")");
Statement stmt = conn.createStatement();
try {
stmt.executeUpdate(builder.toString());
} finally {
JDBCUtil.close(stmt);
}
Log.info("Database table '" + table + "' created.");
return true;
}
}
@@ -25,69 +25,65 @@ import java.sql.*;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
/** /**
* As the repository aims to interface with whatever database pooling * As the repository aims to interface with whatever database pooling services a project cares to
* services a project cares to use, it obtains all of its database * use, it obtains all of its database connections through a connection provider. The connection
* connections through a connection provider. The connection provider * provider provides connections based on a database identifier (a string) which identifies a
* provides connections based on a database identifier (a string) which * particular connection to a particular database. A user of these services would then coordinate
* identifies a particular connection to a particular database. A user of * the database identifier (or identifiers) used to invoke a database operation with the database
* these services would then coordinate the database identifier (or * connections that are returned by the connection provider that they provided to the repository at
* identifiers) used to invoke a database operation with the database * construct time.
* connections that are returned by the connection provider that they
* provided to the repository at construct time.
*/ */
public interface ConnectionProvider public interface ConnectionProvider
{ {
/** /**
* Obtains a database connection based on the supplied database * Obtains a database connection based on the supplied database identifier. The repository
* identifier. The repository expects to have exclusive use of this * expects to have exclusive use of this connection instance until it releases it. This
* connection instance until it releases it. This connection will be * connection will be released subsequently with a call to {@link #releaseConnection} or {@link
* released subsequently with a call to {@link #releaseConnection} or * #connectionFailed} depending on the circumstances of the release. <code>close()</code>
* {@link #connectionFailed} depending on the circumstances of the * <em>will not</em> be called on the connection. It is up to the connection provider to close
* release. <code>close()</code> <em>will not</em> be called on the * the connection when it is released if appropriate.
* connection. It is up to the connection provider to close the
* connection when it is released if appropriate.
* *
* @param ident the database connection identifier. * @param ident the database connection identifier.
* @param readOnly whether or not the connection may be to a read-only * @param readOnly whether or not the connection may be to a read-only mirror of the
* mirror of the repository. * repository.
* *
* @return an active JDBC connection (which may have come from a * @return an active JDBC connection (which may have come from a connection pool).
* connection pool).
* *
* @exception PersistenceException thrown if a problem occurs trying * @exception PersistenceException thrown if a problem occurs trying to open the requested
* to open the requested connection. * connection.
*/ */
public Connection getConnection (String ident, boolean readOnly) public Connection getConnection (String ident, boolean readOnly)
throws PersistenceException; throws PersistenceException;
/** /**
* Releases a database connection when it is no longer needed by the * Releases a database connection when it is no longer needed by the repository.
* repository.
* *
* @param ident the database identifier used when obtaining this * @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. * connection.
* @param readOnly the same value that was passed to {@link #getConnection} * @param conn the connection to release (back into the pool or to be closed if pooling is not
* to obtain this connection. * going on).
* @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, boolean readOnly, public void releaseConnection (String ident, boolean readOnly, Connection conn);
Connection conn);
/** /**
* Called by the repository if a failure occurred on the connection. * Called by the repository if a failure occurred on the connection. It is expected that the
* It is expected that the connection will be disposed of and * connection will be disposed of and subsequent calls to <code>getConnection</code> will
* subsequent calls to <code>getConnection</code> will return a * return a freshly established connection to the database.
* freshly established connection to the database.
* *
* @param ident the database identifier used when obtaining this * @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. * connection.
* @param readOnly the same value that was passed to {@link #getConnection}
* to obtain this connection.
* @param conn the connection that failed. * @param conn the connection that failed.
* @param error the error thrown by the connection (which may be used * @param error the error thrown by the connection (which may be used to determine if the
* to determine if the connection should be closed or can be reused). * connection should be closed or can be reused).
*/ */
public void connectionFailed (String ident, boolean readOnly, public void connectionFailed (String ident, boolean readOnly, Connection conn,
Connection conn, SQLException error); 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.
*/
public String getURL (String ident);
} }
+120 -28
View File
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published // under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or // by the Free Software Foundation; either version 2.1 of the License, or
@@ -24,52 +24,144 @@ import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* Despite good intentions, JDBC and SQL do not provide a unified * Despite good intentions, JDBC and SQL do not provide a unified interface to all databases. There
* interface to all databases. There remain idiosyncrasies that must be * remain idiosyncrasies that must be worked around when making code interact with different
* worked around when making code interact with different database * database servers. The database liaison encapsulates the code needed to straighten out the curves
* servers. The database liaison encapsulates the code needed to * and curve out the straights.
* straighten out the curves and curve out the straights.
*/ */
public interface DatabaseLiaison public interface DatabaseLiaison
{ {
/** /**
* Indicates whether or not this database liaison is the proper * Indicates whether or not this database liaison is the proper liaison for the specified
* liaison for the specified database URL. * database URL.
* *
* @return true if we should use this liaison for connections created * @return true if we should use this liaison for connections created with the supplied URL,
* with the supplied URL, false if we should not. * false if we should not.
*/ */
public boolean matchesURL (String url); public boolean matchesURL (String url);
/** /**
* Determines whether or not the supplied SQL exception was caused by * Determines whether or not the supplied SQL exception was caused by a duplicate row being
* a duplicate row being inserted into a table with a unique key. * inserted into a table with a unique key.
* *
* @return true if the exception was caused by the insertion of a * @return true if the exception was caused by the insertion of a duplicate row, false if not.
* duplicate row, false if not.
*/ */
public boolean isDuplicateRowException (SQLException sqe); public boolean isDuplicateRowException (SQLException sqe);
/** /**
* Determines whether or not the supplied SQL exception is a transient * Determines whether or not the supplied SQL exception is a transient failure, meaning one
* failure, meaning one that is not related to the SQL being executed, * that is not related to the SQL being executed, but instead to the environment at the time of
* but instead to the environment at the time of execution, like the * execution, like the connection to the database having been lost.
* connection to the database having been lost.
* *
* @return true if the exception was thrown due to a transient * @return true if the exception was thrown due to a transient failure, false if not.
* failure, false if not.
*/ */
public boolean isTransientException (SQLException sqe); public boolean isTransientException (SQLException sqe);
/** /**
* Returns the value of an <code>AUTO_INCREMENT</code> column for the * Returns the value of an <code>AUTO_INCREMENT</code> column for the last row insertion. This
* last row insertion. This is MySQL specific, but exists here for now * is MySQL specific, but exists here for now until we sort out a general purpose mechanism for
* until we sort out a general purpose mechanism for assigning unique * assigning unique ids.
* ids.
* *
* @return the most recent ID generated by an insert into an * Note: More general purpose: {@link #lastInsertedId(Connection, String, String)
* <code>AUTO_INCREMENT</code> table or -1 if it could not be *
* obtained. * @return the most recent ID generated by an insert into an <code>AUTO_INCREMENT</code> table
* or -1 if it could not be obtained.
*/ */
public int lastInsertedId (Connection conn) throws SQLException; @Deprecated
public int lastInsertedId (Connection conn)
throws SQLException;
/**
* This method initializes the column value auto-generator described in {@link
* #lastInsertedId(Connection, String, String)}.
*/
public void initializeGenerator (
Connection conn, String table, String column, int first, int step)
throws SQLException;
/**
* This method attempts as dialect-agnostic an interface as possible to the ability of certain
* databases to auto-generated numerical values for i.e. key columns; there is MySQL's
* AUTO_INCREMENT and PostgreSQL's DEFAULT nextval(sequence), for example.
*/
public int lastInsertedId (Connection conn, String table, String column)
throws SQLException;
/**
* Drops the given column from the given table. Returns true or false if the database did or
* did not report a schema modification.
*/
public boolean dropColumn (Connection conn, String table, String column)
throws SQLException;
/**
* Adds a named index to a table on the given columns. Returns true or false if the database
* did or did not report a schema modification.
*/
public boolean addIndexToTable (Connection conn, String table, String[] columns, String index)
throws SQLException;
/**
* Alter the name, but not the definition, of a given column on a given table. Returns true or
* false if the database did or did not report a schema modification.
*/
public boolean renameColumn (Connection conn, String table, String oldColumn, String newColumn)
throws SQLException;
/**
* Alter the definition, but not the name, of a given column on a given table. Returns true or
* false if the database did or did not report a schema modification.
*
* TODO: We may wish to further split column definitions into type, nullability, etc.
*/
public boolean changeColumn (
Connection conn, String table, String column, String definition)
throws SQLException;
/**
* Created a new table of the given name with the given column names and column definitions;
* the given set of unique constraints (or null) and the given primary key columns (or null).
* Returns true if the table was successfully created, false if it already existed.
*/
public boolean createTableIfMissing (
Connection conn, String table, String[] columns, String[] definitions,
String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
throws SQLException;
/**
* Returns true if the specified table exists and contains an index of the specified name;
* false if either conditions does not hold true. <em>Note:</em> the names are case sensitive.
*/
public boolean tableContainsIndex (Connection conn, String table, String index)
throws SQLException;
/**
* Returns true if the specified table exists and contains a column with the specified name;
* false if either condition does not hold true. <em>Note:</em> the names are case sensitive.
*/
public boolean tableContainsColumn (Connection conn, String table, String column)
throws SQLException;
/**
* Returns true if the table with the specified name exists, false if it does not.
* <em>Note:</em> the table name is case sensitive.
*/
public boolean tableExists (Connection conn, String name) throws SQLException;
/**
* Returns the proper SQL to identify a table. Some databases require table names to be quoted.
*/
public String tableSQL (String table);
/**
* Returns the proper SQL to identify a column. Some databases require columns names to be
* quoted.
*/
public String columnSQL (String column);
/**
* Returns the proper SQL to identify an index. Some databases require index names to be
* quoted.
*/
public String indexSQL (String index);
} }
@@ -24,33 +24,65 @@ import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* The default liaison is used if no other liaison could be matched for a * The default liaison is used if no other liaison could be matched for a particular database
* particular database connection. It isn't very smart or useful but we * connection. It isn't very smart or useful but we need something.
* need something.
*/ */
public class DefaultLiaison implements DatabaseLiaison public class DefaultLiaison extends BaseLiaison
{ {
// documentation inherited // from DatabaseLiaison
public boolean matchesURL (String url) public boolean matchesURL (String url)
{ {
return true; return true;
} }
// documentation inherited // from DatabaseLiaison
public boolean isDuplicateRowException (SQLException sqe) public boolean isDuplicateRowException (SQLException sqe)
{ {
return false; return false;
} }
// documentation inherited // from DatabaseLiaison
public boolean isTransientException (SQLException sqe) public boolean isTransientException (SQLException sqe)
{ {
return false; return false;
} }
// documentation inherited // from DatabaseLiaison
public int lastInsertedId (Connection conn) throws SQLException public int lastInsertedId (Connection conn) throws SQLException
{ {
return -1; return -1;
} }
// from DatabaseLiaison
public void initializeGenerator (
Connection conn, String table, String column, int value, int size)
throws SQLException
{
// nothing doing
}
// from DatabaseLiaison
public int lastInsertedId (Connection conn, String table, String column)
throws SQLException
{
return -1;
}
// from DatabaseLiaison
public String columnSQL (String column)
{
return column;
}
// from DatabaseLiaison
public String tableSQL (String table)
{
return table;
}
// from DatabaseLiaison
public String indexSQL (String index)
{
return index;
}
} }
@@ -20,7 +20,6 @@
package com.samskivert.jdbc; package com.samskivert.jdbc;
import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published // under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or // by the Free Software Foundation; either version 2.1 of the License, or
@@ -23,30 +23,24 @@ package com.samskivert.jdbc;
import java.sql.*; import java.sql.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap; import java.util.HashMap;
import com.samskivert.Log; import com.samskivert.Log;
/** /**
* The liaison registry provides access to the appropriate database * The liaison registry provides access to the appropriate database liaison implementation for a
* liaison implementation for a particular database connection. * particular database connection.
*/ */
public class LiaisonRegistry public class LiaisonRegistry
{ {
/** /**
* Fetch the appropriate database liaison for the supplied database * Fetch the appropriate database liaison for the supplied URL, which should be the same string
* connection. * that would be used to configure a connection to the database.
*/ */
public static DatabaseLiaison getLiaison (Connection conn) public static DatabaseLiaison getLiaison (String url)
throws SQLException
{ {
DatabaseMetaData dmd = conn.getMetaData();
String url = dmd.getURL();
// see if we already have a liaison mapped for this connection // see if we already have a liaison mapped for this connection
DatabaseLiaison liaison = _mappings.get(url); DatabaseLiaison liaison = _mappings.get(url);
if (liaison == null) { if (liaison == null) {
// scan the list looking for a matching liaison // scan the list looking for a matching liaison
for (DatabaseLiaison candidate : _liaisons) { for (DatabaseLiaison candidate : _liaisons) {
@@ -58,8 +52,8 @@ public class LiaisonRegistry
// if we didn't find a matching liaison, use the default // if we didn't find a matching liaison, use the default
if (liaison == null) { if (liaison == null) {
Log.warning("Unable to match liaison for database " + Log.warning("Unable to match liaison for database [url=" + url + "]. " +
"[url=" + url + "]. Using default."); "Using default.");
liaison = new DefaultLiaison(); liaison = new DefaultLiaison();
} }
@@ -70,25 +64,34 @@ public class LiaisonRegistry
return liaison; return liaison;
} }
protected static void registerLiaisonClass ( /**
Class<? extends DatabaseLiaison> lclass) * Fetch the appropriate database liaison for the supplied database connection.
*/
public static DatabaseLiaison getLiaison (Connection conn)
throws SQLException
{
return getLiaison(conn.getMetaData().getURL());
}
protected static void registerLiaisonClass (Class<? extends DatabaseLiaison> lclass)
{ {
// create a new instance and stick it on our list // create a new instance and stick it on our list
try { try {
_liaisons.add(lclass.newInstance()); _liaisons.add(lclass.newInstance());
} catch (Exception e) { } catch (Exception e) {
Log.warning("Unable to instantiate liaison " + Log.warning("Unable to instantiate liaison [class=" + lclass.getName() +
"[class=" + lclass.getName() + ", error=" + e + "]."); ", error=" + e + "].");
} }
} }
protected static ArrayList<DatabaseLiaison> _liaisons = protected static ArrayList<DatabaseLiaison> _liaisons = new ArrayList<DatabaseLiaison>();
new ArrayList<DatabaseLiaison>();
protected static HashMap<String,DatabaseLiaison> _mappings = protected static HashMap<String,DatabaseLiaison> _mappings =
new HashMap<String,DatabaseLiaison>(); new HashMap<String,DatabaseLiaison>();
// register our liaison classes // register our liaison classes
static { static {
registerLiaisonClass(MySQLLiaison.class); registerLiaisonClass(MySQLLiaison.class);
registerLiaisonClass(PostgreSQLLiaison.class);
} }
} }
+78 -14
View File
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published // under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or // by the Free Software Foundation; either version 2.1 of the License, or
@@ -22,25 +22,28 @@ package com.samskivert.jdbc;
import java.sql.*; import java.sql.*;
import com.samskivert.Log;
import com.samskivert.util.StringUtil;
/** /**
* A database liaison for the MySQL database. * A database liaison for the MySQL database.
*/ */
public class MySQLLiaison implements DatabaseLiaison public class MySQLLiaison extends BaseLiaison
{ {
// documentation inherited // from DatabaseLiaison
public boolean matchesURL (String url) public boolean matchesURL (String url)
{ {
return url.startsWith("jdbc:mysql"); return url.startsWith("jdbc:mysql");
} }
// documentation inherited // from DatabaseLiaison
public boolean isDuplicateRowException (SQLException sqe) public boolean isDuplicateRowException (SQLException sqe)
{ {
String msg = sqe.getMessage(); String msg = sqe.getMessage();
return (msg != null && msg.indexOf("Duplicate entry") != -1); return (msg != null && msg.indexOf("Duplicate entry") != -1);
} }
// documentation inherited // from DatabaseLiaison
public boolean isTransientException (SQLException sqe) public boolean isTransientException (SQLException sqe)
{ {
String msg = sqe.getMessage(); String msg = sqe.getMessage();
@@ -50,23 +53,84 @@ public class MySQLLiaison implements DatabaseLiaison
msg.indexOf("Broken pipe") != -1)); msg.indexOf("Broken pipe") != -1));
} }
// documentation inherited // from DatabaseLiaison
public int lastInsertedId (Connection conn) throws SQLException public int lastInsertedId (Connection conn) throws SQLException
{ {
Statement stmt = null; return lastInsertedId(conn, null, null);
}
// we have to do this by hand. alas all is not roses. // from DatabaseLiaison
public void initializeGenerator (
Connection conn, String table, String column, int first, int step)
throws SQLException
{
// AUTO_INCREMENT does not allow this kind of control
}
// from DatabaseLiaison
public int lastInsertedId (Connection conn, String table, String column) throws SQLException
{
// MySQL does not keep track of per-table-and-column insertion data, so we are pretty much
// going on blind faith here that we're fetching the right ID. In the overwhelming number
// of cases that will be so, but it's still not pretty.
Statement stmt = null;
try { try {
stmt = conn.createStatement(); stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()"); ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
if (rs.next()) { return rs.next() ? rs.getInt(1) : -1;
return rs.getInt(1);
} else {
return -1;
}
} finally { } finally {
JDBCUtil.close(stmt); JDBCUtil.close(stmt);
} }
} }
@Override // from BaseLiaison
public boolean addIndexToTable (Connection conn, String table, String[] columns, String ixName)
throws SQLException
{
if (tableContainsIndex(conn, table, ixName)) {
return false;
}
ixName = (ixName != null ? ixName : StringUtil.join(columns, "_"));
// MySQL's "CREATE INDEX" is buggy, it actually changes the case of the table names you're
// working with. Luckily (?) ALTER TABLE ADD INDEX works, so we do that here.
StringBuilder update = new StringBuilder("ALTER TABLE ").append(table).
append(" ADD INDEX ").append(indexSQL(ixName)).append(" (");
for (int ii = 0; ii < columns.length; ii ++) {
if (ii > 0) {
update.append(", ");
}
update.append(columnSQL(columns[ii]));
}
update.append(")");
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(update.toString());
stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
Log.info("Database index '" + ixName + "' added to table '" + table + "'");
return true;
}
// from DatabaseLiaison
public String columnSQL (String column)
{
return column;
}
// from DatabaseLiaison
public String tableSQL (String table)
{
return table;
}
// from DatabaseLiaison
public String indexSQL (String index)
{
return index;
}
} }
@@ -0,0 +1,113 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001 Michael Bayne
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc;
import java.sql.*;
/**
* A database liaison for the MySQL database.
*/
public class PostgreSQLLiaison extends BaseLiaison
{
// from DatabaseLiaison
public boolean matchesURL (String url)
{
return url.startsWith("jdbc:postgresql");
}
// from DatabaseLiaison
public boolean isDuplicateRowException (SQLException sqe)
{
String msg = sqe.getMessage();
return (msg != null && msg.indexOf("duplicate key") != -1);
}
// from DatabaseLiaison
public boolean isTransientException (SQLException sqe)
{
// TODO: Add more error messages here as we encounter them.
String msg = sqe.getMessage();
return (msg != null &&
msg.indexOf("An I/O error occured while sending to the backend") != -1);
}
// from DatabaseLiaison
public int lastInsertedId (Connection conn) throws SQLException
{
throw new SQLException(
"No backwards-compatible lastInsertedId() support for PostgreSQL in this code.");
}
// from DatabaseLiaison
public int lastInsertedId (Connection conn, String table, String column) throws SQLException
{
// PostgreSQL's support for auto-generated ID's comes in the form of appropriately named
// sequences and DEFAULT nextval(sequence) modifiers in the ID columns. To get the next ID,
// we use the currval() method which is set in a database sessions when any given sequence
// is incremented.
Statement stmt = null;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"select currval('\"" + table + "_" + column + "_seq\"')");
if (rs.next()) {
return rs.getInt(1);
} else {
return -1;
}
} finally {
JDBCUtil.close(stmt);
}
}
public void initializeGenerator (
Connection conn, String table, String column, int first, int step)
throws SQLException
{
Statement stmt = conn.createStatement();
try {
stmt.executeUpdate("alter sequence \"" + table + "_" + column + "_seq\" " +
" restart with " + first + " increment " + step);
} finally {
JDBCUtil.close(stmt);
}
}
// from DatabaseLiaison
public String columnSQL (String column)
{
return "\"" + column + "\"";
}
// from DatabaseLiaison
public String tableSQL (String table)
{
return "\"" + table + "\"";
}
// from DatabaseLiaison
public String indexSQL (String index)
{
return "\"" + index + "\"";
}
}
@@ -27,9 +27,8 @@ import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
/** /**
* The simple repository should be used for a repository that only needs * The simple repository should be used for a repository that only needs access to a single JDBC
* access to a single JDBC connection instance to perform its persistence * connection instance to perform its persistence services.
* services.
*/ */
public class SimpleRepository extends Repository public class SimpleRepository extends Repository
{ {
@@ -41,12 +40,10 @@ public class SimpleRepository extends Repository
} }
/** /**
* Configures an operation that will be invoked prior to the execution * Configures an operation that will be invoked prior to the execution of every database
* of every database operation to validate whether some pre-condition * operation to validate whether some pre-condition is met. This mainly exists for systems that
* is met. This mainly exists for systems that wish to ensure that all * wish to ensure that all database operations take place on a particular thread (or not on a
* database operations take place on a particular thread (or not on a * particular thread) as database operations are generally slow and blocking.
* particular thread) as database operations are generally slow and
* blocking.
*/ */
public static void setExecutePreCondition (PreCondition condition) public static void setExecutePreCondition (PreCondition condition)
{ {
@@ -54,14 +51,13 @@ public class SimpleRepository extends Repository
} }
/** /**
* Creates and initializes a simple repository which will access the * Creates and initializes a simple repository which will access the database identified by the
* database identified by the supplied database identifier. * supplied database identifier.
* *
* @param provider the connection provider which will be used to * @param provider the connection provider which will be used to obtain our database
* obtain our database connection. * connection.
* @param dbident the identifier of the database that will be accessed by * @param dbident the identifier of the database that will be accessed by this repository or
* this repository or null if the derived class will call {@link * null if the derived class will call {@link #configureDatabaseIdent} by hand later.
* #configureDatabaseIdent} by hand later.
*/ */
public SimpleRepository (ConnectionProvider provider, String dbident) public SimpleRepository (ConnectionProvider provider, String dbident)
{ {
@@ -73,18 +69,17 @@ public class SimpleRepository extends Repository
} }
/** /**
* This is called automatically if a dbident is provided at construct time, * This is called automatically if a dbident is provided at construct time, but a derived class
* but a derived class can pass null to its constructor and then call this * can pass null to its constructor and then call this method itself later if it wishes to
* method itself later if it wishes to obtain its database identifier from * obtain its database identifier from an overridable method which could not otherwise be
* an overridable method which could not otherwise be called at construct * called at construct time.
* time.
*/ */
protected void configureDatabaseIdent (String dbident) protected void configureDatabaseIdent (String dbident)
{ {
_dbident = dbident; _dbident = dbident;
// give the repository a chance to do any schema migration before // give the repository a chance to do any schema migration before things get further
// things get further underway // underway
try { try {
executeUpdate(new Operation<Object>() { executeUpdate(new Operation<Object>() {
public Object invoke (Connection conn, DatabaseLiaison liaison) public Object invoke (Connection conn, DatabaseLiaison liaison)
@@ -101,9 +96,8 @@ public class SimpleRepository extends Repository
} }
/** /**
* Executes the supplied read-only operation. In the event of a transient * Executes the supplied read-only operation. In the event of a transient failure, the
* failure, the repository will attempt to reestablish the database * repository will attempt to reestablish the database connection and try the operation again.
* connection and try the operation again.
* *
* @return whatever value is returned by the invoked operation. * @return whatever value is returned by the invoked operation.
*/ */
@@ -114,9 +108,8 @@ public class SimpleRepository extends Repository
} }
/** /**
* Executes the supplied read-write operation. In the event of a transient * Executes the supplied read-write operation. In the event of a transient failure, the
* failure, the repository will attempt to reestablish the database * repository will attempt to reestablish the database connection and try the operation again.
* connection and try the operation again.
* *
* @return whatever value is returned by the invoked operation. * @return whatever value is returned by the invoked operation.
*/ */
@@ -127,22 +120,18 @@ public class SimpleRepository extends Repository
} }
/** /**
* Executes the supplied operation followed by a call to * Executes the supplied operation followed by a call to <code>commit()</code> on the
* <code>commit()</code> on the connection unless a * connection unless a <code>PersistenceException</code> or runtime error occurs, in which case
* <code>PersistenceException</code> or runtime error occurs, in which * a call to <code>rollback()</code> is executed on the connection.
* case a call to <code>rollback()</code> is executed on the
* connection.
* *
* @param retryOnTransientFailure if true and the operation fails due to a * @param retryOnTransientFailure if true and the operation fails due to a transient failure
* transient failure (like losing the connection to the database or * (like losing the connection to the database or deadlock detection), the connection to the
* deadlock detection), the connection to the database will be * database will be reestablished (if necessary) and the operation attempted once more.
* reestablished (if necessary) and the operation attempted once more.
* @param readOnly whether or not to request a read-only connection. * @param readOnly whether or not to request a read-only connection.
* *
* @return whatever value is returned by the invoked operation. * @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)
boolean readOnly)
throws PersistenceException throws PersistenceException
{ {
Connection conn = null; Connection conn = null;
@@ -153,16 +142,16 @@ public class SimpleRepository extends Repository
// check our pre-condition // check our pre-condition
if (_precond != null && !_precond.validate(_dbident, op)) { if (_precond != null && !_precond.validate(_dbident, op)) {
Log.warning("Repository operation failed pre-condition check! " + Log.warning("Repository operation failed pre-condition check! [dbident=" + _dbident +
"[dbident=" + _dbident + ", op=" + op + "]."); ", op=" + op + "].");
Thread.dumpStack(); Thread.dumpStack();
} }
// obtain our database connection and associated goodies // obtain our database connection and associated goodies
conn = _provider.getConnection(_dbident, readOnly); conn = _provider.getConnection(_dbident, readOnly);
// make sure that no one else performs a database operation using the // make sure that no one else performs a database operation using the same connection until
// same connection until we're done // we're done
synchronized (conn) { synchronized (conn) {
try { try {
liaison = LiaisonRegistry.getLiaison(conn); liaison = LiaisonRegistry.getLiaison(conn);
@@ -193,15 +182,15 @@ public class SimpleRepository extends Repository
} catch (SQLException sqe) { } catch (SQLException sqe) {
if (attemptedOperation) { if (attemptedOperation) {
// back out our changes if something got hosed (but not if // back out our changes if something got hosed (but not if the hosage was a
// the hosage was a result of losing our connection) // result of losing our connection)
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 [err=" + sqe +
"[err=" + sqe + ", rberr=" + rbe + "]."); ", rberr=" + rbe + "].");
} }
} }
@@ -218,13 +207,11 @@ public class SimpleRepository extends Repository
throw new PersistenceException(err, sqe); throw new PersistenceException(err, sqe);
} }
// the MySQL JDBC driver has the annoying habit of including // the MySQL JDBC driver has the annoying habit of including the embedded exception
// the embedded exception stack trace in the message of their // stack trace in the message of their outer exception; if I want a fucking stack
// outer exception; if I want a fucking stack trace, I'll call // trace, I'll call printStackTrace() thanksverymuch
// printStackTrace() thanksverymuch
String msg = StringUtil.split("" + sqe, "\n")[0]; String msg = StringUtil.split("" + sqe, "\n")[0];
Log.info("Transient failure executing operation, " + Log.info("Transient failure executing operation, retrying [error=" + msg + "].");
"retrying [error=" + msg + "].");
} catch (PersistenceException pe) { } catch (PersistenceException pe) {
// back out our changes if something got hosed // back out our changes if something got hosed
@@ -233,8 +220,8 @@ public class SimpleRepository extends Repository
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=" + pe +
"[origerr=" + pe + ", rberr=" + rbe + "]."); ", rberr=" + rbe + "].");
} }
throw pe; throw pe;
@@ -246,8 +233,8 @@ public class SimpleRepository extends Repository
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=" + rte +
"[origerr=" + rte + ", rberr=" + rbe + "]."); ", rberr=" + rbe + "].");
} }
throw rte; throw rte;
@@ -259,15 +246,15 @@ public class SimpleRepository extends Repository
} }
} }
// we'll only fall through here if the above code failed due to a // we'll only fall through here if the above code failed due to a transient exception (the
// transient exception (the connection was closed for being idle, for // connection was closed for being idle, for example) and we've been asked to retry; so
// example) and we've been asked to retry; so let's do so // let's do so
return execute(op, false, readOnly); return execute(op, false, readOnly);
} }
/** /**
* Executes the supplied update query in this repository, returning * Executes the supplied update query in this repository, returning the number of rows
* the number of rows modified. * modified.
*/ */
protected int update (final String query) protected int update (final String query)
throws PersistenceException throws PersistenceException
@@ -288,9 +275,8 @@ public class SimpleRepository extends Repository
} }
/** /**
* Executes the supplied update query in this repository, throwing an * Executes the supplied update query in this repository, throwing an exception if the
* exception if the modification count is not equal to the specified * modification count is not equal to the specified count.
* count.
*/ */
protected void checkedUpdate (final String query, final int count) protected void checkedUpdate (final String query, final int count)
throws PersistenceException throws PersistenceException
@@ -312,8 +298,8 @@ public class SimpleRepository extends Repository
} }
/** /**
* Executes the supplied update query in this repository, logging a warning * Executes the supplied update query in this repository, logging a warning if the modification
* if the modification count is not equal to the specified count. * count is not equal to the specified count.
*/ */
protected void warnedUpdate (final String query, final int count) protected void warnedUpdate (final String query, final int count)
throws PersistenceException throws PersistenceException
@@ -335,15 +321,12 @@ public class SimpleRepository extends Repository
} }
/** /**
* Instructs MySQL to perform table maintenance on the specified * Instructs MySQL to perform table maintenance on the specified table.
* table.
* *
* @param action <code>analyze</code> recomputes the distribution of * @param action <code>analyze</code> recomputes the distribution of the keys for the specified
* the keys for the specified table. This can help certain joins to be * table. This can help certain joins to be performed more efficiently. <code>optimize</code>
* performed more efficiently. <code>optimize</code> instructs MySQL * instructs MySQL to coalesce fragmented records and reclaim space left by deleted records.
* to coalesce fragmented records and reclaim space left by deleted * This can improve a tables efficiency but can take a long time to run on large tables.
* records. This can improve a tables efficiency but can take a long
* time to run on large tables.
*/ */
protected void maintenance (final String action, final String table) protected void maintenance (final String action, final String table)
throws PersistenceException throws PersistenceException
@@ -355,15 +338,12 @@ public class SimpleRepository extends Repository
Statement stmt = null; Statement stmt = null;
try { try {
stmt = conn.createStatement(); stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery( ResultSet rs = stmt.executeQuery(action + " table " + table);
action + " table " + table);
while (rs.next()) { while (rs.next()) {
String result = rs.getString("Msg_text"); String result = rs.getString("Msg_text");
if (result == null || if (result == null ||
result.indexOf("up to date") == -1 && (result.indexOf("up to date") == -1 && !result.equals("OK"))) {
!result.equals("OK")) { Log.info("Table maintenance [" + SimpleRepository.toString(rs) + "].");
Log.info("Table maintenance [" +
SimpleRepository.toString(rs) + "].");
} }
} }
@@ -376,12 +356,10 @@ public class SimpleRepository extends Repository
} }
/** /**
* Derived classes can override this method and perform any schema * Derived classes can override this method and perform any schema migration they might need
* migration they might need (using the idempotent {@link JDBCUtil} schema * (using the idempotent {@link JDBCUtil} schema migration methods). This is called during the
* migration methods). This is called during the repository's constructor * repository's constructor and will thus take place before derived classes (like the {@link
* and will thus take place before derived classes (like the {@link * JORARepository} introspect on the schema to match it up to associated Java classes).
* JORARepository} introspect on the schema to match it up to associated
* Java classes).
*/ */
protected void migrateSchema (Connection conn, DatabaseLiaison liaison) protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
@@ -389,17 +367,17 @@ public class SimpleRepository extends Repository
} }
/** /**
* Called when we fetch a connection from the provider. This gives * Called when we fetch a connection from the provider. This gives derived classes an
* derived classes an opportunity to configure whatever internals they * opportunity to configure whatever internals they might be using with the connection that was
* might be using with the connection that was fetched. * fetched.
*/ */
protected void gotConnection (Connection conn) protected void gotConnection (Connection conn)
{ {
} }
/** /**
* Converts a row of a result set to a string, prepending each column * Converts a row of a result set to a string, prepending each column with the column name from
* with the column name from the result set metadata. * the result set metadata.
*/ */
protected static String toString (ResultSet rs) protected static String toString (ResultSet rs)
throws SQLException throws SQLException
@@ -418,5 +396,6 @@ public class SimpleRepository extends Repository
} }
protected String _dbident; protected String _dbident;
protected static PreCondition _precond; protected static PreCondition _precond;
} }
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published // under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or // by the Free Software Foundation; either version 2.1 of the License, or
@@ -31,15 +31,12 @@ import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
/** /**
* The static connection provider generates JDBC connections based on * The static connection provider generates JDBC connections based on configuration information
* configuration information provided via a properties file. It does no * provided via a properties file. It does no connection pooling and always returns the same
* connection pooling and always returns the same connection for a * connection for a particular identifier (unless that connection need be closed because of a
* particular identifier (unless that connection need be closed because of * connection failure, in which case it opens a new one the next time the connection is requested).
* a connection failure, in which case it opens a new one the next time
* the connection is requested).
* *
* <p> The configuration properties file should contain the following * <p> The configuration properties file should contain the following information:
* information:
* *
* <pre> * <pre>
* IDENT.driver=[jdbc driver class] * IDENT.driver=[jdbc driver class]
@@ -50,14 +47,13 @@ import com.samskivert.util.StringUtil;
* [...] * [...]
* </pre> * </pre>
* *
* Where <code>IDENT</code> is the database identifier for a particular * Where <code>IDENT</code> is the database identifier for a particular database connection. When a
* database connection. When a particular database identifier is * particular database identifier is requested, the configuration information will be fetched from
* requested, the configuration information will be fetched from the * the properties.
* properties.
* *
* <p> Additionally, a default set of properties can be provided using the * <p> Additionally, a default set of properties can be provided using the identifier
* identifier <code>default</code>. Values not provided for a specific * <code>default</code>. Values not provided for a specific identifier will be sought from the
* identifier will be sought from the defaults. For example: * defaults. For example:
* *
* <pre> * <pre>
* default.driver=[jdbc driver class] * default.driver=[jdbc driver class]
@@ -75,15 +71,15 @@ import com.samskivert.util.StringUtil;
public class StaticConnectionProvider implements ConnectionProvider public class StaticConnectionProvider implements ConnectionProvider
{ {
/** /**
* Constructs a static connection provider which will load its * Constructs a static connection provider which will load its configuration from a properties
* configuration from a properties file accessible via the classpath * file accessible via the classpath of the running application and identified by the specified
* of the running application and identified by the specified path. * path.
* *
* @param propPath the path (relative to the classpath) to the * @param propPath the path (relative to the classpath) to the properties file that will be
* properties file that will be used for configuration information. * used for configuration information.
* *
* @exception IOException thrown if an error occurs locating or * @exception IOException thrown if an error occurs locating or loading the specified
* loading the specified properties file. * properties file.
*/ */
public StaticConnectionProvider (String propPath) public StaticConnectionProvider (String propPath)
throws IOException throws IOException
@@ -92,8 +88,8 @@ public class StaticConnectionProvider implements ConnectionProvider
} }
/** /**
* Constructs a static connection provider which will fetch its * Constructs a static connection provider which will fetch its configuration information from
* configuration information from the specified properties object. * the specified properties object.
* *
* @param props the configuration for this connection provider. * @param props the configuration for this connection provider.
*/ */
@@ -103,8 +99,7 @@ public class StaticConnectionProvider implements ConnectionProvider
} }
/** /**
* Closes all of the open database connections in preparation for * Closes all of the open database connections in preparation for shutting down.
* shutting down.
*/ */
public void shutdown () public void shutdown ()
{ {
@@ -112,12 +107,11 @@ public class StaticConnectionProvider implements ConnectionProvider
Iterator iter = _keys.keySet().iterator(); Iterator iter = _keys.keySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
String key = (String)iter.next(); String key = (String)iter.next();
Mapping conmap = (Mapping)_keys.get(key); Mapping conmap = _keys.get(key);
try { try {
conmap.connection.close(); conmap.connection.close();
} catch (SQLException sqe) { } catch (SQLException sqe) {
Log.warning("Error shutting down connection " + Log.warning("Error shutting down connection [key=" + key + ", err=" + sqe + "].");
"[key=" + key + ", err=" + sqe + "].");
} }
} }
@@ -126,6 +120,13 @@ public class StaticConnectionProvider implements ConnectionProvider
_idents.clear(); _idents.clear();
} }
// from ConnectionProvider
public String getURL (String ident)
{
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
return props.getProperty("url");
}
// documentation inherited // documentation inherited
public Connection getConnection (String ident, boolean readOnly) public Connection getConnection (String ident, boolean readOnly)
throws PersistenceException throws PersistenceException
@@ -135,9 +136,7 @@ public class StaticConnectionProvider implements ConnectionProvider
// open the connection if we haven't already // open the connection if we haven't already
if (conmap == null) { if (conmap == null) {
String[] defaults; Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
Properties props =
PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
// get the JDBC configuration info // get the JDBC configuration info
String err = "No driver class specified [ident=" + ident + "]."; String err = "No driver class specified [ident=" + ident + "].";
@@ -149,33 +148,30 @@ public class StaticConnectionProvider implements ConnectionProvider
err = "No driver password specified [ident=" + ident + "]."; err = "No driver password specified [ident=" + ident + "].";
String password = requireProp(props, "password", err); String password = requireProp(props, "password", err);
// if this is a read-only connection, // 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
// we cache connections by username+url+readOnly to avoid making
// more that one connection to a particular database server
String key = username + "@" + url + ":" + readOnly; String key = username + "@" + url + ":" + readOnly;
conmap = _keys.get(key); conmap = _keys.get(key);
if (conmap == null) { if (conmap == null) {
Log.debug("Creating " + key + " for " + ident + "."); Log.debug("Creating " + key + " for " + ident + ".");
conmap = new Mapping(); conmap = new Mapping();
conmap.key = key; conmap.key = key;
conmap.connection = conmap.connection = openConnection(driver, url, username, password);
openConnection(driver, url, username, password);
// make the connection read-only to let the JDBC driver know // make the connection read-only to let the JDBC driver know that it can and should
// that it can and should use the read-only mirror(s) // use the read-only mirror(s)
if (readOnly) { if (readOnly) {
try { try {
conmap.connection.setReadOnly(true); conmap.connection.setReadOnly(true);
} catch (SQLException sqe) { } catch (SQLException sqe) {
closeConnection(ident, conmap.connection); closeConnection(ident, conmap.connection);
err = "Failed to make connection read-only " + err = "Failed to make connection read-only [key=" + key +
"[key=" + key + ", ident=" + ident + "]."; ", ident=" + ident + "].";
throw new PersistenceException(err, sqe); throw new PersistenceException(err, sqe);
} }
} }
_keys.put(key, conmap); _keys.put(key, conmap);
} else { } else {
Log.debug("Reusing " + key + " for " + ident + "."); Log.debug("Reusing " + key + " for " + ident + ".");
} }
@@ -185,19 +181,25 @@ public class StaticConnectionProvider implements ConnectionProvider
_idents.put(mapkey, conmap); _idents.put(mapkey, conmap);
} }
// in case the previous user turned off auto-commit we have to make sure here it's on
try {
conmap.connection.setAutoCommit(true);
} catch (SQLException e) {
throw new PersistenceException("Failed to turn on connection auto-commit", e);
}
return conmap.connection; return conmap.connection;
} }
// documentation inherited // documentation inherited
public void releaseConnection (String ident, boolean readOnly, public void releaseConnection (String ident, boolean readOnly, Connection conn)
Connection conn)
{ {
// nothing to do here, all is well // nothing to do here, all is well
} }
// documentation inherited // documentation inherited
public void connectionFailed (String ident, boolean readOnly, public void connectionFailed (
Connection conn, SQLException error) String ident, boolean readOnly, Connection conn, SQLException error)
{ {
String mapkey = ident + ":" + readOnly; String mapkey = ident + ":" + readOnly;
Mapping conmap = _idents.get(mapkey); Mapping conmap = _idents.get(mapkey);
@@ -216,8 +218,7 @@ public class StaticConnectionProvider implements ConnectionProvider
_keys.remove(conmap.key); _keys.remove(conmap.key);
} }
protected Connection openConnection ( protected Connection openConnection (String driver, String url, String username, String passwd)
String driver, String url, String username, String password)
throws PersistenceException throws PersistenceException
{ {
// create an instance of the driver // create an instance of the driver
@@ -233,11 +234,11 @@ public class StaticConnectionProvider implements ConnectionProvider
try { try {
Properties props = new Properties(); Properties props = new Properties();
props.put("user", username); props.put("user", username);
props.put("password", password); props.put("password", passwd);
return jdriver.connect(url, props); return jdriver.connect(url, props);
} catch (SQLException sqe) { } catch (SQLException sqe) {
String err = "Error creating database connection " + String err = "Error creating database connection [driver=" + driver + ", url=" + url +
"[driver=" + driver + ", url=" + url +
", username=" + username + "]."; ", username=" + username + "].";
throw new PersistenceException(err, sqe); throw new PersistenceException(err, sqe);
} }
@@ -253,25 +254,23 @@ public class StaticConnectionProvider implements ConnectionProvider
} }
} }
protected static String requireProp ( protected static String requireProp (Properties props, String name, String errmsg)
Properties props, String name, String errmsg)
throws PersistenceException throws PersistenceException
{ {
String value = props.getProperty(name); String value = props.getProperty(name);
if (StringUtil.isBlank(value)) { if (StringUtil.isBlank(value)) {
// augment the error message errmsg = "Unable to get connection. " + errmsg; // augment the error message
errmsg = "Unable to get connection. " + errmsg;
throw new PersistenceException(errmsg); throw new PersistenceException(errmsg);
} }
return value; return value;
} }
/** Contains information on a particular connection to which any /** Contains information on a particular connection to which any number of database identifiers
* number of database identifiers can be mapped. */ * can be mapped. */
protected static class Mapping protected static class Mapping
{ {
/** The combination of username and JDBC url that uniquely /** The combination of username and JDBC url that uniquely identifies our database
* identifies our database connection. */ * connection. */
public String key; public String key;
/** The connection itself. */ /** The connection itself. */
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 Michael Bayne // Copyright (C) 2001-2007 Michael Bayne
// //
// This library is free software; you can redistribute it and/or modify it // This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published // under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or // by the Free Software Foundation; either version 2.1 of the License, or
@@ -28,9 +28,8 @@ import java.sql.SQLException;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
/** /**
* Used to note that transitionary code has been run to migrate persistent * Used to note that transitionary code has been run to migrate persistent data. This is especially
* data. The TransitionRepository is especially useful for data that * useful for data that one cannot examine to determine if it's been transitioned.
* one cannot examine to determine if it's been transitioned.
*/ */
public class TransitionRepository extends SimpleRepository public class TransitionRepository extends SimpleRepository
{ {
@@ -81,18 +80,20 @@ public class TransitionRepository extends SimpleRepository
public Boolean invoke (Connection conn, DatabaseLiaison liaison) public Boolean invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
Object found = null;
PreparedStatement stmt = null; PreparedStatement stmt = null;
try { try {
stmt = conn.prepareStatement("select NAME " + stmt = conn.prepareStatement(
"from TRANSITIONS where CLASS=? and NAME=?"); " select " + liaison.columnSQL("NAME") +
" from " + liaison.tableSQL("TRANSITIONS") +
" where " + liaison.columnSQL("CLASS") + "=?" +
" and " + liaison.columnSQL("NAME") + "=?");
stmt.setString(1, cname); stmt.setString(1, cname);
stmt.setString(2, name); stmt.setString(2, name);
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
if (rs.next()) { if (rs.next()) {
while (rs.next()); // is this really necessary?
return true; return true;
} }
} finally { } finally {
JDBCUtil.close(stmt); JDBCUtil.close(stmt);
} }
@@ -114,12 +115,14 @@ public class TransitionRepository extends SimpleRepository
{ {
PreparedStatement stmt = null; PreparedStatement stmt = null;
try { try {
stmt = conn.prepareStatement("insert into TRANSITIONS " + stmt = conn.prepareStatement(
"(CLASS, NAME) values (?, ?)"); "insert into " + liaison.tableSQL("TRANSITIONS") + " (" +
liaison.columnSQL("CLASS") + ", " + liaison.columnSQL("NAME") +
") values (?, ?)");
stmt.setString(1, cname); stmt.setString(1, cname);
stmt.setString(2, name); stmt.setString(2, name);
JDBCUtil.checkedUpdate(stmt, 1); JDBCUtil.checkedUpdate(stmt, 1);
} finally { } finally {
JDBCUtil.close(stmt); JDBCUtil.close(stmt);
} }
@@ -141,8 +144,10 @@ public class TransitionRepository extends SimpleRepository
{ {
PreparedStatement stmt = null; PreparedStatement stmt = null;
try { try {
stmt = conn.prepareStatement("delete from TRANSITIONS " + stmt = conn.prepareStatement(
"where CLASS=? and NAME=?"); " delete from " + liaison.tableSQL("TRANSITIONS") +
" where " + liaison.columnSQL("CLASS") + "=? " +
" and " + liaison.columnSQL("NAME") + "=?");
stmt.setString(1, cname); stmt.setString(1, cname);
stmt.setString(2, name); stmt.setString(2, name);
@@ -160,10 +165,12 @@ public class TransitionRepository extends SimpleRepository
protected void migrateSchema (Connection conn, DatabaseLiaison liaison) protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException throws SQLException, PersistenceException
{ {
JDBCUtil.createTableIfMissing(conn, "TRANSITIONS", new String[] { liaison.createTableIfMissing(
"CLASS varchar(200) not null", conn,
"NAME varchar(50) not null", "TRANSITIONS",
"APPLIED timestamp not null", new String[] { "CLASS", "NAME", "APPLIED" },
"primary key (CLASS, NAME)" }, ""); new String[] { "VARCHAR(200)", "VARCHAR(50)", "TIMESTAMP NOT NULL" },
null,
new String[] { "CLASS", "NAME" });
} }
} }