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:
@@ -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;
|
||||
|
||||
/**
|
||||
* As the repository aims to interface with whatever database pooling
|
||||
* services a project cares to use, it obtains all of its database
|
||||
* connections through a connection provider. The connection provider
|
||||
* provides connections based on a database identifier (a string) which
|
||||
* identifies a particular connection to a particular database. A user of
|
||||
* these services would then coordinate the database identifier (or
|
||||
* identifiers) used to invoke a database operation with the database
|
||||
* connections that are returned by the connection provider that they
|
||||
* provided to the repository at construct time.
|
||||
* As the repository aims to interface with whatever database pooling services a project cares to
|
||||
* use, it obtains all of its database connections through a connection provider. The connection
|
||||
* provider provides connections based on a database identifier (a string) which identifies a
|
||||
* particular connection to a particular database. A user of these services would then coordinate
|
||||
* the database identifier (or identifiers) used to invoke a database operation with the database
|
||||
* connections that are returned by the connection provider that they provided to the repository at
|
||||
* construct time.
|
||||
*/
|
||||
public interface ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Obtains a database connection based on the supplied database
|
||||
* identifier. The repository expects to have exclusive use of this
|
||||
* connection instance until it releases it. This connection will be
|
||||
* released subsequently with a call to {@link #releaseConnection} or
|
||||
* {@link #connectionFailed} depending on the circumstances of the
|
||||
* release. <code>close()</code> <em>will not</em> be called on the
|
||||
* connection. It is up to the connection provider to close the
|
||||
* connection when it is released if appropriate.
|
||||
* Obtains a database connection based on the supplied database identifier. The repository
|
||||
* expects to have exclusive use of this connection instance until it releases it. This
|
||||
* connection will be released subsequently with a call to {@link #releaseConnection} or {@link
|
||||
* #connectionFailed} depending on the circumstances of the release. <code>close()</code>
|
||||
* <em>will not</em> be called on the 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 readOnly whether or not the connection may be to a read-only
|
||||
* mirror of the repository.
|
||||
* @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).
|
||||
* @return an active JDBC connection (which may have come from a connection pool).
|
||||
*
|
||||
* @exception PersistenceException thrown if a problem occurs trying
|
||||
* to open the requested connection.
|
||||
* @exception PersistenceException thrown if a problem occurs trying to open the requested
|
||||
* connection.
|
||||
*/
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Releases a database connection when it is no longer needed by the
|
||||
* repository.
|
||||
* Releases a database connection when it is no longer needed by the 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.
|
||||
* @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).
|
||||
* @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,
|
||||
Connection conn);
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn);
|
||||
|
||||
/**
|
||||
* Called by the repository if a failure occurred on the connection.
|
||||
* It is expected that the connection will be disposed of and
|
||||
* subsequent calls to <code>getConnection</code> will return a
|
||||
* freshly established connection to the database.
|
||||
* Called by the repository if a failure occurred on the connection. It is expected that the
|
||||
* connection will be disposed of and subsequent calls to <code>getConnection</code> will
|
||||
* return a 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.
|
||||
* @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).
|
||||
* @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, boolean readOnly,
|
||||
Connection conn, SQLException error);
|
||||
public void connectionFailed (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.
|
||||
*/
|
||||
public String getURL (String ident);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2007 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
|
||||
@@ -24,52 +24,144 @@ import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Despite good intentions, JDBC and SQL do not provide a unified
|
||||
* interface to all databases. There remain idiosyncrasies that must be
|
||||
* worked around when making code interact with different database
|
||||
* servers. The database liaison encapsulates the code needed to
|
||||
* straighten out the curves and curve out the straights.
|
||||
* Despite good intentions, JDBC and SQL do not provide a unified interface to all databases. There
|
||||
* remain idiosyncrasies that must be worked around when making code interact with different
|
||||
* database servers. The database liaison encapsulates the code needed to straighten out the curves
|
||||
* and curve out the straights.
|
||||
*/
|
||||
public interface DatabaseLiaison
|
||||
{
|
||||
/**
|
||||
* Indicates whether or not this database liaison is the proper
|
||||
* liaison for the specified database URL.
|
||||
* Indicates whether or not this database liaison is the proper liaison for the specified
|
||||
* database URL.
|
||||
*
|
||||
* @return true if we should use this liaison for connections created
|
||||
* with the supplied URL, false if we should not.
|
||||
* @return true if we should use this liaison for connections created with the supplied URL,
|
||||
* false if we should not.
|
||||
*/
|
||||
public boolean matchesURL (String url);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception was caused by
|
||||
* a duplicate row being inserted into a table with a unique key.
|
||||
* Determines whether or not the supplied SQL exception was caused by a duplicate row being
|
||||
* inserted into a table with a unique key.
|
||||
*
|
||||
* @return true if the exception was caused by the insertion of a
|
||||
* duplicate row, false if not.
|
||||
* @return true if the exception was caused by the insertion of a duplicate row, false if not.
|
||||
*/
|
||||
public boolean isDuplicateRowException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception is a transient
|
||||
* failure, meaning one that is not related to the SQL being executed,
|
||||
* but instead to the environment at the time of execution, like the
|
||||
* connection to the database having been lost.
|
||||
* Determines whether or not the supplied SQL exception is a transient failure, meaning one
|
||||
* that is not related to the SQL being executed, but instead to the environment at the time of
|
||||
* execution, like the connection to the database having been lost.
|
||||
*
|
||||
* @return true if the exception was thrown due to a transient
|
||||
* failure, false if not.
|
||||
* @return true if the exception was thrown due to a transient failure, false if not.
|
||||
*/
|
||||
public boolean isTransientException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Returns the value of an <code>AUTO_INCREMENT</code> column for the
|
||||
* last row insertion. This is MySQL specific, but exists here for now
|
||||
* until we sort out a general purpose mechanism for assigning unique
|
||||
* ids.
|
||||
* Returns the value of an <code>AUTO_INCREMENT</code> column for the last row insertion. This
|
||||
* is MySQL specific, but exists here for now until we sort out a general purpose mechanism for
|
||||
* assigning unique ids.
|
||||
*
|
||||
* @return the most recent ID generated by an insert into an
|
||||
* <code>AUTO_INCREMENT</code> table or -1 if it could not be
|
||||
* obtained.
|
||||
* Note: More general purpose: {@link #lastInsertedId(Connection, String, String)
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* The default liaison is used if no other liaison could be matched for a
|
||||
* particular database connection. It isn't very smart or useful but we
|
||||
* need something.
|
||||
* The default liaison is used if no other liaison could be matched for a particular database
|
||||
* connection. It isn't very smart or useful but we need something.
|
||||
*/
|
||||
public class DefaultLiaison implements DatabaseLiaison
|
||||
public class DefaultLiaison extends BaseLiaison
|
||||
{
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public int lastInsertedId (Connection conn) throws SQLException
|
||||
{
|
||||
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;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2007 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
|
||||
@@ -23,30 +23,24 @@ package com.samskivert.jdbc;
|
||||
import java.sql.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* The liaison registry provides access to the appropriate database
|
||||
* liaison implementation for a particular database connection.
|
||||
* The liaison registry provides access to the appropriate database liaison implementation for a
|
||||
* particular database connection.
|
||||
*/
|
||||
public class LiaisonRegistry
|
||||
{
|
||||
/**
|
||||
* Fetch the appropriate database liaison for the supplied database
|
||||
* connection.
|
||||
* Fetch the appropriate database liaison for the supplied URL, which should be the same string
|
||||
* that would be used to configure a connection to the database.
|
||||
*/
|
||||
public static DatabaseLiaison getLiaison (Connection conn)
|
||||
throws SQLException
|
||||
public static DatabaseLiaison getLiaison (String url)
|
||||
{
|
||||
DatabaseMetaData dmd = conn.getMetaData();
|
||||
String url = dmd.getURL();
|
||||
|
||||
// see if we already have a liaison mapped for this connection
|
||||
DatabaseLiaison liaison = _mappings.get(url);
|
||||
|
||||
if (liaison == null) {
|
||||
// scan the list looking for a matching liaison
|
||||
for (DatabaseLiaison candidate : _liaisons) {
|
||||
@@ -58,8 +52,8 @@ public class LiaisonRegistry
|
||||
|
||||
// if we didn't find a matching liaison, use the default
|
||||
if (liaison == null) {
|
||||
Log.warning("Unable to match liaison for database " +
|
||||
"[url=" + url + "]. Using default.");
|
||||
Log.warning("Unable to match liaison for database [url=" + url + "]. " +
|
||||
"Using default.");
|
||||
liaison = new DefaultLiaison();
|
||||
}
|
||||
|
||||
@@ -70,25 +64,34 @@ public class LiaisonRegistry
|
||||
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
|
||||
try {
|
||||
_liaisons.add(lclass.newInstance());
|
||||
} catch (Exception e) {
|
||||
Log.warning("Unable to instantiate liaison " +
|
||||
"[class=" + lclass.getName() + ", error=" + e + "].");
|
||||
Log.warning("Unable to instantiate liaison [class=" + lclass.getName() +
|
||||
", error=" + e + "].");
|
||||
}
|
||||
}
|
||||
|
||||
protected static ArrayList<DatabaseLiaison> _liaisons =
|
||||
new ArrayList<DatabaseLiaison>();
|
||||
protected static ArrayList<DatabaseLiaison> _liaisons = new ArrayList<DatabaseLiaison>();
|
||||
protected static HashMap<String,DatabaseLiaison> _mappings =
|
||||
new HashMap<String,DatabaseLiaison>();
|
||||
|
||||
// register our liaison classes
|
||||
static {
|
||||
registerLiaisonClass(MySQLLiaison.class);
|
||||
registerLiaisonClass(PostgreSQLLiaison.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2007 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
|
||||
@@ -22,25 +22,28 @@ package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return url.startsWith("jdbc:mysql");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && msg.indexOf("Duplicate entry") != -1);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
@@ -50,23 +53,84 @@ public class MySQLLiaison implements DatabaseLiaison
|
||||
msg.indexOf("Broken pipe") != -1));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
// from DatabaseLiaison
|
||||
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 {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rs.next() ? rs.getInt(1) : -1;
|
||||
} finally {
|
||||
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;
|
||||
|
||||
/**
|
||||
* The simple repository should be used for a repository that only needs
|
||||
* access to a single JDBC connection instance to perform its persistence
|
||||
* services.
|
||||
* The simple repository should be used for a repository that only needs access to a single JDBC
|
||||
* connection instance to perform its persistence services.
|
||||
*/
|
||||
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
|
||||
* of every database operation to validate whether some pre-condition
|
||||
* is met. This mainly exists for systems that wish to ensure that all
|
||||
* database operations take place on a particular thread (or not on a
|
||||
* particular thread) as database operations are generally slow and
|
||||
* blocking.
|
||||
* Configures an operation that will be invoked prior to the execution of every database
|
||||
* operation to validate whether some pre-condition is met. This mainly exists for systems that
|
||||
* wish to ensure that all database operations take place on a particular thread (or not on a
|
||||
* particular thread) as database operations are generally slow and blocking.
|
||||
*/
|
||||
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
|
||||
* database identified by the supplied database identifier.
|
||||
* Creates and initializes a simple repository which will access the database identified by the
|
||||
* supplied database identifier.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
* @param dbident the identifier of the database that will be accessed by
|
||||
* this repository or null if the derived class will call {@link
|
||||
* #configureDatabaseIdent} by hand later.
|
||||
* @param provider the connection provider which will be used to obtain our database
|
||||
* connection.
|
||||
* @param dbident the identifier of the database that will be accessed by this repository or
|
||||
* null if the derived class will call {@link #configureDatabaseIdent} by hand later.
|
||||
*/
|
||||
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,
|
||||
* but a derived class can pass null to its constructor and then call this
|
||||
* method itself later if it wishes to obtain its database identifier from
|
||||
* an overridable method which could not otherwise be called at construct
|
||||
* time.
|
||||
* This is called automatically if a dbident is provided at construct time, but a derived class
|
||||
* can pass null to its constructor and then call this method itself later if it wishes to
|
||||
* obtain its database identifier from an overridable method which could not otherwise be
|
||||
* called at construct time.
|
||||
*/
|
||||
protected void configureDatabaseIdent (String dbident)
|
||||
{
|
||||
_dbident = dbident;
|
||||
|
||||
// give the repository a chance to do any schema migration before
|
||||
// things get further underway
|
||||
// give the repository a chance to do any schema migration before things get further
|
||||
// underway
|
||||
try {
|
||||
executeUpdate(new Operation<Object>() {
|
||||
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
|
||||
* failure, the repository will attempt to reestablish the database
|
||||
* connection and try the operation again.
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* failure, the repository will attempt to reestablish the database
|
||||
* connection and try the operation again.
|
||||
* 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.
|
||||
*/
|
||||
@@ -127,22 +120,18 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied operation followed by a call to
|
||||
* <code>commit()</code> on the connection unless a
|
||||
* <code>PersistenceException</code> or runtime error occurs, in which
|
||||
* case a call to <code>rollback()</code> is executed on the
|
||||
* connection.
|
||||
* Executes the supplied operation followed by a call to <code>commit()</code> on the
|
||||
* connection unless a <code>PersistenceException</code> or runtime error occurs, in which case
|
||||
* a call to <code>rollback()</code> is executed on the connection.
|
||||
*
|
||||
* @param retryOnTransientFailure if true and the operation fails due to a
|
||||
* transient failure (like losing the connection to the database or
|
||||
* deadlock detection), the connection to the database will be
|
||||
* reestablished (if necessary) and the operation attempted once more.
|
||||
* @param retryOnTransientFailure if true and the operation fails due to a transient failure
|
||||
* (like losing the connection to the database or deadlock detection), the connection to the
|
||||
* database will be reestablished (if necessary) and the operation attempted once more.
|
||||
* @param readOnly whether or not to request a read-only connection.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected <V> V execute (Operation<V> op, boolean retryOnTransientFailure,
|
||||
boolean readOnly)
|
||||
protected <V> V execute (Operation<V> op, boolean retryOnTransientFailure, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
Connection conn = null;
|
||||
@@ -153,16 +142,16 @@ public class SimpleRepository extends Repository
|
||||
|
||||
// check our pre-condition
|
||||
if (_precond != null && !_precond.validate(_dbident, op)) {
|
||||
Log.warning("Repository operation failed pre-condition check! " +
|
||||
"[dbident=" + _dbident + ", op=" + op + "].");
|
||||
Log.warning("Repository operation failed pre-condition check! [dbident=" + _dbident +
|
||||
", op=" + op + "].");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
|
||||
// obtain our database connection and associated goodies
|
||||
conn = _provider.getConnection(_dbident, readOnly);
|
||||
|
||||
// make sure that no one else performs a database operation using the
|
||||
// same connection until we're done
|
||||
// make sure that no one else performs a database operation using the same connection until
|
||||
// we're done
|
||||
synchronized (conn) {
|
||||
try {
|
||||
liaison = LiaisonRegistry.getLiaison(conn);
|
||||
@@ -193,15 +182,15 @@ public class SimpleRepository extends Repository
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (attemptedOperation) {
|
||||
// back out our changes if something got hosed (but not if
|
||||
// the hosage was a result of losing our connection)
|
||||
// back out our changes if something got hosed (but not if the hosage was a
|
||||
// result of losing our connection)
|
||||
try {
|
||||
if (supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[err=" + sqe + ", rberr=" + rbe + "].");
|
||||
Log.warning("Unable to roll back operation [err=" + sqe +
|
||||
", rberr=" + rbe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,13 +207,11 @@ public class SimpleRepository extends Repository
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
|
||||
// the MySQL JDBC driver has the annoying habit of including
|
||||
// the embedded exception stack trace in the message of their
|
||||
// outer exception; if I want a fucking stack trace, I'll call
|
||||
// printStackTrace() thanksverymuch
|
||||
// the MySQL JDBC driver has the annoying habit of including the embedded exception
|
||||
// stack trace in the message of their outer exception; if I want a fucking stack
|
||||
// trace, I'll call printStackTrace() thanksverymuch
|
||||
String msg = StringUtil.split("" + sqe, "\n")[0];
|
||||
Log.info("Transient failure executing operation, " +
|
||||
"retrying [error=" + msg + "].");
|
||||
Log.info("Transient failure executing operation, retrying [error=" + msg + "].");
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
// back out our changes if something got hosed
|
||||
@@ -233,8 +220,8 @@ public class SimpleRepository extends Repository
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[origerr=" + pe + ", rberr=" + rbe + "].");
|
||||
Log.warning("Unable to roll back operation [origerr=" + pe +
|
||||
", rberr=" + rbe + "].");
|
||||
}
|
||||
throw pe;
|
||||
|
||||
@@ -246,8 +233,8 @@ public class SimpleRepository extends Repository
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[origerr=" + rte + ", rberr=" + rbe + "].");
|
||||
Log.warning("Unable to roll back operation [origerr=" + rte +
|
||||
", rberr=" + rbe + "].");
|
||||
}
|
||||
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
|
||||
// transient exception (the connection was closed for being idle, for
|
||||
// example) and we've been asked to retry; so let's do so
|
||||
// we'll only fall through here if the above code failed due to a transient exception (the
|
||||
// connection was closed for being idle, for example) and we've been asked to retry; so
|
||||
// let's do so
|
||||
return execute(op, false, readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, returning
|
||||
* the number of rows modified.
|
||||
* Executes the supplied update query in this repository, returning the number of rows
|
||||
* modified.
|
||||
*/
|
||||
protected int update (final String query)
|
||||
throws PersistenceException
|
||||
@@ -288,9 +275,8 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, throwing an
|
||||
* exception if the modification count is not equal to the specified
|
||||
* count.
|
||||
* Executes the supplied update query in this repository, throwing an exception if the
|
||||
* modification count is not equal to the specified count.
|
||||
*/
|
||||
protected void checkedUpdate (final String query, final int count)
|
||||
throws PersistenceException
|
||||
@@ -312,8 +298,8 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, logging a warning
|
||||
* if the modification count is not equal to the specified count.
|
||||
* Executes the supplied update query in this repository, logging a warning if the modification
|
||||
* count is not equal to the specified count.
|
||||
*/
|
||||
protected void warnedUpdate (final String query, final int count)
|
||||
throws PersistenceException
|
||||
@@ -335,15 +321,12 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs MySQL to perform table maintenance on the specified
|
||||
* table.
|
||||
* Instructs MySQL to perform table maintenance on the specified table.
|
||||
*
|
||||
* @param action <code>analyze</code> recomputes the distribution of
|
||||
* the keys for the specified table. This can help certain joins to be
|
||||
* performed more efficiently. <code>optimize</code> instructs MySQL
|
||||
* to coalesce fragmented records and reclaim space left by deleted
|
||||
* records. This can improve a tables efficiency but can take a long
|
||||
* time to run on large tables.
|
||||
* @param action <code>analyze</code> recomputes the distribution of the keys for the specified
|
||||
* table. This can help certain joins to be performed more efficiently. <code>optimize</code>
|
||||
* instructs MySQL to coalesce fragmented records and reclaim space left by deleted 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)
|
||||
throws PersistenceException
|
||||
@@ -355,15 +338,12 @@ public class SimpleRepository extends Repository
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(
|
||||
action + " table " + table);
|
||||
ResultSet rs = stmt.executeQuery(action + " table " + table);
|
||||
while (rs.next()) {
|
||||
String result = rs.getString("Msg_text");
|
||||
if (result == null ||
|
||||
result.indexOf("up to date") == -1 &&
|
||||
!result.equals("OK")) {
|
||||
Log.info("Table maintenance [" +
|
||||
SimpleRepository.toString(rs) + "].");
|
||||
(result.indexOf("up to date") == -1 && !result.equals("OK"))) {
|
||||
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
|
||||
* migration they might need (using the idempotent {@link JDBCUtil} schema
|
||||
* migration methods). This is called during the repository's constructor
|
||||
* and will thus take place before derived classes (like the {@link
|
||||
* JORARepository} introspect on the schema to match it up to associated
|
||||
* Java classes).
|
||||
* Derived classes can override this method and perform any schema migration they might need
|
||||
* (using the idempotent {@link JDBCUtil} schema migration methods). This is called during the
|
||||
* repository's constructor and will thus take place before derived classes (like the {@link
|
||||
* JORARepository} introspect on the schema to match it up to associated Java classes).
|
||||
*/
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
@@ -389,17 +367,17 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we fetch a connection from the provider. This gives
|
||||
* derived classes an opportunity to configure whatever internals they
|
||||
* might be using with the connection that was fetched.
|
||||
* Called when we fetch a connection from the provider. This gives derived classes an
|
||||
* opportunity to configure whatever internals they might be using with the connection that was
|
||||
* fetched.
|
||||
*/
|
||||
protected void gotConnection (Connection conn)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a row of a result set to a string, prepending each column
|
||||
* with the column name from the result set metadata.
|
||||
* Converts a row of a result set to a string, prepending each column with the column name from
|
||||
* the result set metadata.
|
||||
*/
|
||||
protected static String toString (ResultSet rs)
|
||||
throws SQLException
|
||||
@@ -418,5 +396,6 @@ public class SimpleRepository extends Repository
|
||||
}
|
||||
|
||||
protected String _dbident;
|
||||
|
||||
protected static PreCondition _precond;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2007 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
|
||||
@@ -31,15 +31,12 @@ import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The static connection provider generates JDBC connections based on
|
||||
* configuration information provided via a properties file. It does no
|
||||
* connection pooling and always returns the same connection for a
|
||||
* particular identifier (unless that connection need be closed because of
|
||||
* a connection failure, in which case it opens a new one the next time
|
||||
* the connection is requested).
|
||||
* The static connection provider generates JDBC connections based on configuration information
|
||||
* provided via a properties file. It does no connection pooling and always returns the same
|
||||
* connection for a particular identifier (unless that connection need be closed because of 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
|
||||
* information:
|
||||
* <p> The configuration properties file should contain the following information:
|
||||
*
|
||||
* <pre>
|
||||
* IDENT.driver=[jdbc driver class]
|
||||
@@ -50,14 +47,13 @@ import com.samskivert.util.StringUtil;
|
||||
* [...]
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>IDENT</code> is the database identifier for a particular
|
||||
* database connection. When a particular database identifier is
|
||||
* requested, the configuration information will be fetched from the
|
||||
* properties.
|
||||
* Where <code>IDENT</code> is the database identifier for a particular database connection. When a
|
||||
* particular database identifier is requested, the configuration information will be fetched from
|
||||
* the properties.
|
||||
*
|
||||
* <p> Additionally, a default set of properties can be provided using the
|
||||
* identifier <code>default</code>. Values not provided for a specific
|
||||
* identifier will be sought from the defaults. For example:
|
||||
* <p> Additionally, a default set of properties can be provided using the identifier
|
||||
* <code>default</code>. Values not provided for a specific identifier will be sought from the
|
||||
* defaults. For example:
|
||||
*
|
||||
* <pre>
|
||||
* default.driver=[jdbc driver class]
|
||||
@@ -75,15 +71,15 @@ import com.samskivert.util.StringUtil;
|
||||
public class StaticConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Constructs a static connection provider which will load its
|
||||
* configuration from a properties file accessible via the classpath
|
||||
* of the running application and identified by the specified path.
|
||||
* Constructs a static connection provider which will load its configuration from a properties
|
||||
* file accessible via the classpath of the running application and identified by the specified
|
||||
* path.
|
||||
*
|
||||
* @param propPath the path (relative to the classpath) to the
|
||||
* properties file that will be used for configuration information.
|
||||
* @param propPath the path (relative to the classpath) to the properties file that will be
|
||||
* used for configuration information.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs locating or
|
||||
* loading the specified properties file.
|
||||
* @exception IOException thrown if an error occurs locating or loading the specified
|
||||
* properties file.
|
||||
*/
|
||||
public StaticConnectionProvider (String propPath)
|
||||
throws IOException
|
||||
@@ -92,8 +88,8 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a static connection provider which will fetch its
|
||||
* configuration information from the specified properties object.
|
||||
* Constructs a static connection provider which will fetch its configuration information from
|
||||
* the specified properties object.
|
||||
*
|
||||
* @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
|
||||
* shutting down.
|
||||
* Closes all of the open database connections in preparation for shutting down.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
@@ -112,12 +107,11 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
Iterator iter = _keys.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
String key = (String)iter.next();
|
||||
Mapping conmap = (Mapping)_keys.get(key);
|
||||
Mapping conmap = _keys.get(key);
|
||||
try {
|
||||
conmap.connection.close();
|
||||
} catch (SQLException sqe) {
|
||||
Log.warning("Error shutting down connection " +
|
||||
"[key=" + key + ", err=" + sqe + "].");
|
||||
Log.warning("Error shutting down connection [key=" + key + ", err=" + sqe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +120,13 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
_idents.clear();
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public String getURL (String ident)
|
||||
{
|
||||
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
return props.getProperty("url");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
@@ -135,9 +136,7 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
|
||||
// open the connection if we haven't already
|
||||
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
|
||||
String err = "No driver class specified [ident=" + ident + "].";
|
||||
@@ -149,33 +148,30 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
err = "No driver password specified [ident=" + ident + "].";
|
||||
String password = requireProp(props, "password", err);
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
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)
|
||||
// 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 + "].";
|
||||
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 + ".");
|
||||
}
|
||||
@@ -185,19 +181,25 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
_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;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void releaseConnection (String ident, boolean readOnly,
|
||||
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, boolean readOnly,
|
||||
Connection conn, SQLException error)
|
||||
public void connectionFailed (
|
||||
String ident, boolean readOnly, Connection conn, SQLException error)
|
||||
{
|
||||
String mapkey = ident + ":" + readOnly;
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
@@ -216,8 +218,7 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
_keys.remove(conmap.key);
|
||||
}
|
||||
|
||||
protected Connection openConnection (
|
||||
String driver, String url, String username, String password)
|
||||
protected Connection openConnection (String driver, String url, String username, String passwd)
|
||||
throws PersistenceException
|
||||
{
|
||||
// create an instance of the driver
|
||||
@@ -233,11 +234,11 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.put("user", username);
|
||||
props.put("password", password);
|
||||
props.put("password", passwd);
|
||||
return jdriver.connect(url, props);
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
String err = "Error creating database connection " +
|
||||
"[driver=" + driver + ", url=" + url +
|
||||
String err = "Error creating database connection [driver=" + driver + ", url=" + url +
|
||||
", username=" + username + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
@@ -253,25 +254,23 @@ public class StaticConnectionProvider implements ConnectionProvider
|
||||
}
|
||||
}
|
||||
|
||||
protected static String requireProp (
|
||||
Properties props, String name, String errmsg)
|
||||
protected static String requireProp (Properties props, String name, String errmsg)
|
||||
throws PersistenceException
|
||||
{
|
||||
String value = props.getProperty(name);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
// augment the error message
|
||||
errmsg = "Unable to get connection. " + errmsg;
|
||||
errmsg = "Unable to get connection. " + errmsg; // augment the error message
|
||||
throw new PersistenceException(errmsg);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Contains information on a particular connection to which any
|
||||
* number of database identifiers can be mapped. */
|
||||
/** Contains information on a particular connection to which any number of database identifiers
|
||||
* can be mapped. */
|
||||
protected static class Mapping
|
||||
{
|
||||
/** The combination of username and JDBC url that uniquely
|
||||
* identifies our database connection. */
|
||||
/** The combination of username and JDBC url that uniquely identifies our database
|
||||
* connection. */
|
||||
public String key;
|
||||
|
||||
/** The connection itself. */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2007 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
|
||||
@@ -28,9 +28,8 @@ import java.sql.SQLException;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* Used to note that transitionary code has been run to migrate persistent
|
||||
* data. The TransitionRepository is especially useful for data that
|
||||
* one cannot examine to determine if it's been transitioned.
|
||||
* Used to note that transitionary code has been run to migrate persistent data. This is especially
|
||||
* useful for data that one cannot examine to determine if it's been transitioned.
|
||||
*/
|
||||
public class TransitionRepository extends SimpleRepository
|
||||
{
|
||||
@@ -81,18 +80,20 @@ public class TransitionRepository extends SimpleRepository
|
||||
public Boolean invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Object found = null;
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement("select NAME " +
|
||||
"from TRANSITIONS where CLASS=? and NAME=?");
|
||||
stmt = conn.prepareStatement(
|
||||
" select " + liaison.columnSQL("NAME") +
|
||||
" from " + liaison.tableSQL("TRANSITIONS") +
|
||||
" where " + liaison.columnSQL("CLASS") + "=?" +
|
||||
" and " + liaison.columnSQL("NAME") + "=?");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
while (rs.next()); // is this really necessary?
|
||||
return true;
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
@@ -114,12 +115,14 @@ public class TransitionRepository extends SimpleRepository
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement("insert into TRANSITIONS " +
|
||||
"(CLASS, NAME) values (?, ?)");
|
||||
stmt = conn.prepareStatement(
|
||||
"insert into " + liaison.tableSQL("TRANSITIONS") + " (" +
|
||||
liaison.columnSQL("CLASS") + ", " + liaison.columnSQL("NAME") +
|
||||
") values (?, ?)");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
|
||||
JDBCUtil.checkedUpdate(stmt, 1);
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
@@ -141,8 +144,10 @@ public class TransitionRepository extends SimpleRepository
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement("delete from TRANSITIONS " +
|
||||
"where CLASS=? and NAME=?");
|
||||
stmt = conn.prepareStatement(
|
||||
" delete from " + liaison.tableSQL("TRANSITIONS") +
|
||||
" where " + liaison.columnSQL("CLASS") + "=? " +
|
||||
" and " + liaison.columnSQL("NAME") + "=?");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
|
||||
@@ -160,10 +165,12 @@ public class TransitionRepository extends SimpleRepository
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
JDBCUtil.createTableIfMissing(conn, "TRANSITIONS", new String[] {
|
||||
"CLASS varchar(200) not null",
|
||||
"NAME varchar(50) not null",
|
||||
"APPLIED timestamp not null",
|
||||
"primary key (CLASS, NAME)" }, "");
|
||||
liaison.createTableIfMissing(
|
||||
conn,
|
||||
"TRANSITIONS",
|
||||
new String[] { "CLASS", "NAME", "APPLIED" },
|
||||
new String[] { "VARCHAR(200)", "VARCHAR(50)", "TIMESTAMP NOT NULL" },
|
||||
null,
|
||||
new String[] { "CLASS", "NAME" });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user