Factored out metadata handling into separate class. Modified repository

initialization to not hit the database at all in the up-to-date case (after a
single query to load the versions of all known records). This is about six
hundred fewer queries than we were doing on Whirled at server startup. To be
fair, they were pretty fast queries. :)
This commit is contained in:
Michael Bayne
2009-05-19 05:30:47 +00:00
parent 8b202f530e
commit 636e2d7046
3 changed files with 250 additions and 152 deletions
@@ -46,6 +46,7 @@ import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.CacheAdapter.CachedValue;
import com.samskivert.depot.annotation.TableGenerator;
import com.samskivert.depot.impl.DepotMarshaller;
import com.samskivert.depot.impl.DepotMetaData;
import com.samskivert.depot.impl.DepotTypes;
import com.samskivert.depot.impl.HSQLBuilder;
import com.samskivert.depot.impl.Modifier;
@@ -173,18 +174,8 @@ public class PersistenceContext
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
_cache = adapter;
// determine our JDBC major version
Connection conn = null;
try {
conn = _conprov.getConnection(_ident, true);
_jdbcMajorVersion = conn.getMetaData().getJDBCMajorVersion();
} catch (Exception e) {
throw new DatabaseException("getJDBCMajorVersion failed [ident=" + ident + "].", e);
} finally {
if (conn != null) {
_conprov.releaseConnection(_ident, true, conn);
}
}
// set up some basic meta-meta-data
_meta.init(this);
}
/**
@@ -212,26 +203,11 @@ public class PersistenceContext
}
/**
* Create and return a new {@link SQLBuilder} for the appropriate dialect.
*
* TODO: At some point perhaps use a more elegant way of discerning our dialect.
* Creates and return a new {@link SQLBuilder} for the appropriate dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types)
{
if (_liaison instanceof PostgreSQLLiaison) {
if (_jdbcMajorVersion >= 4) {
return new PostgreSQL4Builder(types);
} else {
return new PostgreSQLBuilder(types);
}
}
if (_liaison instanceof MySQLLiaison) {
return new MySQLBuilder(types);
}
if (_liaison instanceof HsqldbLiaison) {
return new HSQLBuilder(types);
}
throw new IllegalArgumentException("Unknown liaison type: " + _liaison.getClass());
return _meta.getSQLBuilder(types, _liaison);
}
/**
@@ -284,7 +260,7 @@ public class PersistenceContext
if (!marshaller.isInitialized()) {
// initialize the marshaller which may create or migrate the table for its
// underlying persistent object
marshaller.init(this);
marshaller.init(this, _meta);
if (marshaller.getTableName() != null && _warnOnLazyInit) {
log.warning("Record initialized lazily", "type", type.getName(),
new Exception());
@@ -621,8 +597,8 @@ public class PersistenceContext
protected String _ident;
protected ConnectionProvider _conprov;
protected DatabaseLiaison _liaison;
protected DepotMetaData _meta = new DepotMetaData();
protected boolean _warnOnLazyInit;
protected int _jdbcMajorVersion;
/** Used to track various statistics. */
protected Stats _stats = new Stats();
@@ -469,7 +469,7 @@ public class DepotMarshaller<T extends PersistentRecord>
*/
public boolean isInitialized ()
{
return _initialized;
return _meta != null;
}
/**
@@ -478,14 +478,14 @@ public class DepotMarshaller<T extends PersistentRecord>
* be created. If the schema version specified by the persistent object is newer than the
* database schema, it will be migrated.
*/
public void init (PersistenceContext ctx)
public void init (PersistenceContext ctx, DepotMetaData meta)
throws DatabaseException
{
if (_initialized) { // sanity check
if (_meta != null) { // sanity check
throw new IllegalStateException(
"Cannot re-initialize marshaller [type=" + _pClass + "].");
}
_initialized = true;
_meta = meta;
final SQLBuilder builder = ctx.getSQLBuilder(new DepotTypes(ctx, _pClass));
@@ -517,67 +517,26 @@ public class DepotMarshaller<T extends PersistentRecord>
_columnFields = ArrayUtil.splice(_columnFields, jj);
declarations = ArrayUtil.splice(declarations, jj);
// check to see if our schema version table exists, create it if not
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.createTableIfMissing(
conn, SCHEMA_VERSION_TABLE,
new String[] { P_COLUMN, V_COLUMN, MV_COLUMN },
new ColumnDefinition[] {
new ColumnDefinition("VARCHAR(255)", false, true, null),
new ColumnDefinition("INTEGER", false, false, null),
new ColumnDefinition("INTEGER", false, false, null)
},
null,
new String[] { P_COLUMN });
// add our new "migratingVersion" column if it's not already there
liaison.addColumn(conn, SCHEMA_VERSION_TABLE, MV_COLUMN,
"integer not null default 0", true);
return 0;
}
});
// fetch all relevant information regarding our table from the database
TableMetaData metaData = TableMetaData.load(ctx, getTableName());
// determine whether or not this record has ever been seen
int currentVersion = ctx.invoke(new ReadVersion());
int currentVersion = _meta.getVersion(getTableName(), false);
if (currentVersion == -1) {
log.info("Creating initial version record for " + _pClass.getName() + ".");
// if not, create a version entry with version zero
ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
try {
return stmt.executeUpdate(
"insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" values('" + getTableName() + "', 0 , 0)");
} catch (SQLException e) {
// someone else might be doing this at the exact same time which is OK,
// we'll coordinate with that other process in the next phase
if (liaison.isDuplicateRowException(e)) {
return 0;
} else {
throw e;
}
}
}
});
_meta.initializeVersion(getTableName());
}
// now check whether we need to migrate our database schema
boolean gotMigrationLock = false;
while (!gotMigrationLock) {
currentVersion = ctx.invoke(new ReadVersion());
while (true) {
if (currentVersion >= _schemaVersion) {
checkForStaleness(metaData, ctx, builder);
// TODO: we used to check for staleness here, but that's slow; currently we do it
// only after migration, we should reinstate a check maybe the first time a table
// is read or something so that developers are more likely to get the warning
return;
}
// try to update migratingVersion to the new version to indicate to other processes
// that we are handling the migration and that they should wait
if (ctx.invoke(new UpdateMigratingVersion(_schemaVersion, 0)) > 0) {
if (_meta.updateMigratingVersion(getTableName(), _schemaVersion, 0)) {
break; // we got the lock, let's go
}
@@ -589,8 +548,13 @@ public class DepotMarshaller<T extends PersistentRecord>
} catch (InterruptedException ie) {
throw new DatabaseException("Interrupted while waiting on migration lock.");
}
currentVersion = _meta.getVersion(getTableName(), true);
}
// fetch all relevant information regarding our table from the database
TableMetaData metaData = TableMetaData.load(ctx, getTableName());
try {
if (!metaData.tableExists) {
// if the table does not exist, create it
@@ -605,20 +569,12 @@ public class DepotMarshaller<T extends PersistentRecord>
checkForStaleness(metaData, ctx, builder);
// and update our version in the schema version table
ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(V_COLUMN) + " = " + _schemaVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + getTableName() + "'");
}
});
_meta.updateVersion(getTableName(), _schemaVersion);
} finally {
// set our migrating version back to zero
try {
if (ctx.invoke(new UpdateMigratingVersion(0, _schemaVersion)) == 0) {
if (!_meta.updateMigratingVersion(getTableName(), 0, _schemaVersion)) {
log.warning("Failed to restore migrating version to zero!", "record", _pClass);
}
} catch (Exception e) {
@@ -1020,50 +976,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
protected abstract class SimpleModifier extends Modifier {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return invoke(liaison, stmt);
} finally {
stmt.close();
}
}
protected abstract int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException;
}
// this is a Modifier not a Query because we want to be sure we're talking to the database
// server to whom we would talk if we were doing a modification (ie. the master, not a
// read-only slave)
protected class ReadVersion extends SimpleModifier {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(
" select " + liaison.columnSQL(V_COLUMN) +
" from " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + getTableName() + "'");
return (rs.next()) ? rs.getInt(1) : -1;
}
}
protected class UpdateMigratingVersion extends SimpleModifier {
public UpdateMigratingVersion (int newMigratingVersion, int guardVersion) {
_newMigratingVersion = newMigratingVersion;
_guardVersion = guardVersion;
}
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(MV_COLUMN) + " = " + _newMigratingVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + getTableName() + "'" +
" and " + liaison.columnSQL(MV_COLUMN) + " = " + _guardVersion);
}
protected int _newMigratingVersion, _guardVersion;
}
protected static class TableMetaData
{
public boolean tableExists;
@@ -1133,6 +1045,9 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
/** Provides access to certain internal metadata. */
protected DepotMetaData _meta;
/** The persistent object class that we manage. */
protected Class<T> _pClass;
@@ -1167,21 +1082,6 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The version of our persistent object schema as specified in the class definition. */
protected int _schemaVersion = -1;
/** Indicates that we have been initialized (created or migrated our tables). */
protected boolean _initialized;
/** A list of hand registered schema migrations to run prior to doing the default migration. */
protected List<SchemaMigration> _schemaMigs = Lists.newArrayList();
/** The name of the table we use to track schema versions. */
protected static final String SCHEMA_VERSION_TABLE = "DepotSchemaVersion";
/** The name of the 'persistentClass' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String P_COLUMN = "persistentClass";
/** The name of the 'version' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String V_COLUMN = "version";
/** The name of the 'migratingVersion' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String MV_COLUMN = "migratingVersion";
}
@@ -0,0 +1,222 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import com.google.common.collect.Maps;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.HsqldbLiaison;
import com.samskivert.jdbc.MySQLLiaison;
import com.samskivert.jdbc.PostgreSQLLiaison;
import com.samskivert.depot.PersistenceContext;
/**
* Does something extraordinary.
*/
public class DepotMetaData
{
public void init (PersistenceContext ctx)
{
_ctx = ctx;
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
// determine our JDBC major version
_jdbcMajorVersion = stmt.getConnection().getMetaData().getJDBCMajorVersion();
// create our schema version table if needed
liaison.createTableIfMissing(
stmt.getConnection(), SCHEMA_VERSION_TABLE,
new String[] { P_COLUMN, V_COLUMN, MV_COLUMN },
new ColumnDefinition[] {
new ColumnDefinition("VARCHAR(255)", false, true, null),
new ColumnDefinition("INTEGER", false, false, null),
new ColumnDefinition("INTEGER", false, false, null)
},
null,
new String[] { P_COLUMN });
// slurp in the current versions of all records
readVersions(liaison, stmt);
return 0;
}
});
}
/**
* Returns the current version of the specified persistent class.
*
* @param forceUpdate if true the latest version will be read from the database rather than
* returned from memory. Only force an update when you know that the value may have changed in
* the database since Depot was initialized.
*
* @return the current version of the specified persistent class or -1 if the class has no
* recorded version.
*/
public int getVersion (String pClass, boolean forceUpdate)
{
if (forceUpdate) {
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
readVersions(liaison, stmt);
return 0;
}
});
}
Integer curvers = _curvers.get(pClass);
return (curvers == null) ? -1 : curvers;
}
/**
* Initializes the version of the specified persistent class to zero.
*/
public void initializeVersion (final String pClass)
{
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
try {
return stmt.executeUpdate(
"insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" values('" + pClass + "', 0 , 0)");
} catch (SQLException e) {
// someone else might be doing this at the exact same time which is OK,
// we'll coordinate with that other process in the next phase
if (liaison.isDuplicateRowException(e)) {
return 0;
} else {
throw e;
}
}
}
});
}
/**
* Updates the version of the specified persistent class to the specified version.
*/
public void updateVersion (final String pClass, final int newVersion)
{
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(V_COLUMN) + " = " + newVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + pClass + "'");
}
});
}
/**
* Updates the current migrating version of the supplied persistent class.
*
* @return true if the migration lock was obtained and the migrating version was updated, false
* if some other process acquired the lock.
*/
public boolean updateMigratingVersion (
final String pClass, final int newMigratingVersion, final int guardVersion)
{
return _ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(MV_COLUMN) + " = " + newMigratingVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + pClass + "'" +
" and " + liaison.columnSQL(MV_COLUMN) + " = " + guardVersion);
}
}) > 0;
}
/**
* Creates and return a new {@link SQLBuilder} for the appropriate dialect.
*
* TODO: At some point perhaps use a more elegant way of discerning our dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types, DatabaseLiaison liaison)
{
if (liaison instanceof PostgreSQLLiaison) {
if (_jdbcMajorVersion >= 4) {
return new PostgreSQL4Builder(types);
} else {
return new PostgreSQLBuilder(types);
}
}
if (liaison instanceof MySQLLiaison) {
return new MySQLBuilder(types);
}
if (liaison instanceof HsqldbLiaison) {
return new HSQLBuilder(types);
}
throw new IllegalArgumentException("Unknown liaison type: " + liaison.getClass());
}
protected void readVersions (DatabaseLiaison liaison, Statement stmt)
throws SQLException
{
ResultSet rs = stmt.executeQuery(
" select " + liaison.columnSQL(P_COLUMN) + ", " + liaison.columnSQL(V_COLUMN) +
" from " + liaison.tableSQL(SCHEMA_VERSION_TABLE));
while (rs.next()) {
_curvers.put(rs.getString(1), rs.getInt(2));
}
}
protected abstract class SimpleModifier extends Modifier {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return invoke(liaison, stmt);
} finally {
stmt.close();
}
}
protected abstract int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException;
}
protected PersistenceContext _ctx;
protected int _jdbcMajorVersion;
protected Map<String, Integer> _curvers = Maps.newHashMap();
/** The name of the table we use to track schema versions. */
protected static final String SCHEMA_VERSION_TABLE = "DepotSchemaVersion";
/** The name of the 'persistentClass' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String P_COLUMN = "persistentClass";
/** The name of the 'version' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String V_COLUMN = "version";
/** The name of the 'migratingVersion' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String MV_COLUMN = "migratingVersion";
}