mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Fixed AWC-1254: Schema changes run in auto-commit mode without rollback.
Some databases support transactional changes of the schema, but most don't. Some don't update the schema metadata until the transaction ends. To workaround all of these issues, a lock table is created at the beginning of the schema bootstrap and removed afterwards. Each statement is executed in auto-commit mode. If there is a failure, there is no alternative but to revert to the original data and try again. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6215 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Schema update messages
|
# Schema update messages
|
||||||
|
|
||||||
|
schema.update.msg.dialect_used=Schema managed by database dialect {0}.
|
||||||
schema.update.msg.bypassing=Bypassing schema update checks.
|
schema.update.msg.bypassing=Bypassing schema update checks.
|
||||||
schema.update.msg.all_statements=All executed statements written to file {0}.
|
schema.update.msg.all_statements=All executed statements written to file {0}.
|
||||||
schema.update.msg.no_changes=No changes were made to the schema.
|
schema.update.msg.no_changes=No changes were made to the schema.
|
||||||
@@ -7,6 +8,8 @@ schema.update.msg.executing_generated_script=Executing database script {0} (Gene
|
|||||||
schema.update.msg.executing_copied_script=Executing database script {0} (Copied from {1}).
|
schema.update.msg.executing_copied_script=Executing database script {0} (Copied from {1}).
|
||||||
schema.update.msg.executing_statement= Executing statement: {0}
|
schema.update.msg.executing_statement= Executing statement: {0}
|
||||||
schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3}
|
schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3}
|
||||||
|
schema.update.warn.dialect_unsupported=Alfresco should not be used with database dialect {0}.
|
||||||
|
schema.update.err.previous_failed=A previous schema upgrade failed. Revert to the original database before attempting the upgrade again.
|
||||||
schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3}
|
schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3}
|
||||||
schema.update.err.update_failed=Schema auto-update failed
|
schema.update.err.update_failed=Schema auto-update failed
|
||||||
schema.update.err.validation_failed=Schema validation failed
|
schema.update.err.validation_failed=Schema validation failed
|
||||||
|
@@ -57,11 +57,12 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.SessionFactory;
|
import org.hibernate.SessionFactory;
|
||||||
import org.hibernate.Transaction;
|
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.cfg.Environment;
|
import org.hibernate.cfg.Environment;
|
||||||
import org.hibernate.connection.UserSuppliedConnectionProvider;
|
import org.hibernate.connection.UserSuppliedConnectionProvider;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.dialect.MySQL5Dialect;
|
||||||
|
import org.hibernate.dialect.MySQLDialect;
|
||||||
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
|
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
|
||||||
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
@@ -82,6 +83,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
/** The placeholder for the configured <code>Dialect</code> class name: <b>${db.script.dialect}</b> */
|
/** The placeholder for the configured <code>Dialect</code> class name: <b>${db.script.dialect}</b> */
|
||||||
private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}";
|
private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}";
|
||||||
|
|
||||||
|
private static final String MSG_DIALECT_USED = "schema.update.msg.dialect_used";
|
||||||
private static final String MSG_BYPASSING_SCHEMA_UPDATE = "schema.update.msg.bypassing";
|
private static final String MSG_BYPASSING_SCHEMA_UPDATE = "schema.update.msg.bypassing";
|
||||||
private static final String MSG_NO_CHANGES = "schema.update.msg.no_changes";
|
private static final String MSG_NO_CHANGES = "schema.update.msg.no_changes";
|
||||||
private static final String MSG_ALL_STATEMENTS = "schema.update.msg.all_statements";
|
private static final String MSG_ALL_STATEMENTS = "schema.update.msg.all_statements";
|
||||||
@@ -89,6 +91,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
private static final String MSG_EXECUTING_COPIED_SCRIPT = "schema.update.msg.executing_copied_script";
|
private static final String MSG_EXECUTING_COPIED_SCRIPT = "schema.update.msg.executing_copied_script";
|
||||||
private static final String MSG_EXECUTING_STATEMENT = "schema.update.msg.executing_statement";
|
private static final String MSG_EXECUTING_STATEMENT = "schema.update.msg.executing_statement";
|
||||||
private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed";
|
private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed";
|
||||||
|
private static final String WARN_DIALECT_UNSUPPORTED = "schema.update.warn.dialect_unsupported";
|
||||||
|
private static final String ERR_PREVIOUS_FAILED_BOOTSTRAP = "schema.update.err.previous_failed";
|
||||||
private static final String ERR_STATEMENT_FAILED = "schema.update.err.statement_failed";
|
private static final String ERR_STATEMENT_FAILED = "schema.update.err.statement_failed";
|
||||||
private static final String ERR_UPDATE_FAILED = "schema.update.err.update_failed";
|
private static final String ERR_UPDATE_FAILED = "schema.update.err.update_failed";
|
||||||
private static final String ERR_VALIDATION_FAILED = "schema.update.err.validation_failed";
|
private static final String ERR_VALIDATION_FAILED = "schema.update.err.validation_failed";
|
||||||
@@ -293,20 +297,15 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the number of applied patches
|
* @return Returns the name of the applied patch table, or <tt>null</tt> if the table doesn't exist
|
||||||
*/
|
*/
|
||||||
private boolean didPatchSucceed(Connection connection, String patchId) throws Exception
|
private String getAppliedPatchTableName(Connection connection) throws Exception
|
||||||
{
|
{
|
||||||
Statement stmt = connection.createStatement();
|
Statement stmt = connection.createStatement();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ResultSet rs = stmt.executeQuery("select succeeded from alf_applied_patch where id = '" + patchId + "'");
|
stmt.executeQuery("select * from alf_applied_patch");
|
||||||
if (!rs.next())
|
return "alf_applied_patch";
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean succeeded = rs.getBoolean(1);
|
|
||||||
return succeeded;
|
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
@@ -320,7 +319,35 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
stmt = connection.createStatement();
|
stmt = connection.createStatement();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ResultSet rs = stmt.executeQuery("select succeeded from applied_patch where id = '" + patchId + "'");
|
stmt.executeQuery("select * from applied_patch");
|
||||||
|
return "applied_patch";
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// It is not there
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { stmt.close(); } catch (Throwable e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the number of applied patches
|
||||||
|
*/
|
||||||
|
private boolean didPatchSucceed(Connection connection, String patchId) throws Exception
|
||||||
|
{
|
||||||
|
String patchTableName = getAppliedPatchTableName(connection);
|
||||||
|
if (patchTableName == null)
|
||||||
|
{
|
||||||
|
// Table doesn't exist, yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Statement stmt = connection.createStatement();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ResultSet rs = stmt.executeQuery("select succeeded from " + patchTableName + " where id = '" + patchId + "'");
|
||||||
if (!rs.next())
|
if (!rs.next())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -334,6 +361,58 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records that the bootstrap process has started
|
||||||
|
*/
|
||||||
|
private synchronized void setBootstrapStarted(Connection connection) throws Exception
|
||||||
|
{
|
||||||
|
// We wait a for a minute to give other instances starting against the same database a
|
||||||
|
// chance to get through this process
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
// Create the marker table
|
||||||
|
Statement stmt = connection.createStatement();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stmt.executeUpdate("create table alf_bootstrap_lock (charval CHAR(1) NOT NULL)");
|
||||||
|
// Success
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Table exists - wait a bit
|
||||||
|
try { this.wait(5000L); } catch (InterruptedException ee) {}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { stmt.close(); } catch (Throwable e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw AlfrescoRuntimeException.create(ERR_PREVIOUS_FAILED_BOOTSTRAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records that the bootstrap process has finished
|
||||||
|
*/
|
||||||
|
private void setBootstrapCompleted(Connection connection) throws Exception
|
||||||
|
{
|
||||||
|
// Create the marker table
|
||||||
|
Statement stmt = connection.createStatement();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stmt.executeUpdate("drop table alf_bootstrap_lock");
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Table exists
|
||||||
|
throw AlfrescoRuntimeException.create(ERR_PREVIOUS_FAILED_BOOTSTRAP);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { stmt.close(); } catch (Throwable e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the schema from scratch or applies the necessary patches to the schema.
|
* Builds the schema from scratch or applies the necessary patches to the schema.
|
||||||
*/
|
*/
|
||||||
@@ -647,14 +726,23 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
{
|
{
|
||||||
// do everything in a transaction
|
// do everything in a transaction
|
||||||
Session session = getSessionFactory().openSession();
|
Session session = getSessionFactory().openSession();
|
||||||
Transaction transaction = session.beginTransaction();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// make sure that we don't autocommit
|
// make sure that we AUTO-COMMIT
|
||||||
Connection connection = session.connection();
|
Connection connection = session.connection();
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(true);
|
||||||
|
|
||||||
Configuration cfg = localSessionFactory.getConfiguration();
|
Configuration cfg = localSessionFactory.getConfiguration();
|
||||||
|
|
||||||
|
// Check and dump the dialect being used
|
||||||
|
Dialect dialect = Dialect.getDialect(cfg.getProperties());
|
||||||
|
Class dialectClazz = dialect.getClass();
|
||||||
|
LogUtil.info(logger, MSG_DIALECT_USED, dialectClazz.getName());
|
||||||
|
if (dialectClazz.equals(MySQLDialect.class) || dialectClazz.equals(MySQL5Dialect.class))
|
||||||
|
{
|
||||||
|
LogUtil.warn(logger, WARN_DIALECT_UNSUPPORTED, dialectClazz.getName());
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that our static connection provider is used
|
// Ensure that our static connection provider is used
|
||||||
String defaultConnectionProviderFactoryClass = cfg.getProperty(Environment.CONNECTION_PROVIDER);
|
String defaultConnectionProviderFactoryClass = cfg.getProperty(Environment.CONNECTION_PROVIDER);
|
||||||
cfg.setProperty(Environment.CONNECTION_PROVIDER, SchemaBootstrapConnectionProvider.class.getName());
|
cfg.setProperty(Environment.CONNECTION_PROVIDER, SchemaBootstrapConnectionProvider.class.getName());
|
||||||
@@ -663,6 +751,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
// update the schema, if required
|
// update the schema, if required
|
||||||
if (updateSchema)
|
if (updateSchema)
|
||||||
{
|
{
|
||||||
|
// Check and record that the bootstrap has started
|
||||||
|
setBootstrapStarted(connection);
|
||||||
|
|
||||||
// Allocate buffer for executed statements
|
// Allocate buffer for executed statements
|
||||||
executedStatementsThreadLocal.set(new StringBuilder(1024));
|
executedStatementsThreadLocal.set(new StringBuilder(1024));
|
||||||
|
|
||||||
@@ -695,6 +786,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts
|
checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts
|
||||||
checkSchemaPatchScripts(cfg, session, connection, preUpdateScriptPatches, false); // check scripts
|
checkSchemaPatchScripts(cfg, session, connection, preUpdateScriptPatches, false); // check scripts
|
||||||
checkSchemaPatchScripts(cfg, session, connection, postUpdateScriptPatches, false); // check scripts
|
checkSchemaPatchScripts(cfg, session, connection, postUpdateScriptPatches, false); // check scripts
|
||||||
|
|
||||||
|
// Remove the flag indicating a running bootstrap
|
||||||
|
setBootstrapCompleted(connection);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -705,12 +799,10 @@ public class SchemaBootstrap extends AbstractLifecycleBean
|
|||||||
cfg.setProperty(Environment.CONNECTION_PROVIDER, defaultConnectionProviderFactoryClass);
|
cfg.setProperty(Environment.CONNECTION_PROVIDER, defaultConnectionProviderFactoryClass);
|
||||||
|
|
||||||
// all done successfully
|
// all done successfully
|
||||||
transaction.commit();
|
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
LogUtil.error(logger, e, ERR_UPDATE_FAILED);
|
LogUtil.error(logger, e, ERR_UPDATE_FAILED);
|
||||||
try { transaction.rollback(); } catch (Throwable ee) {}
|
|
||||||
if (updateSchema)
|
if (updateSchema)
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException(ERR_UPDATE_FAILED, e);
|
throw new AlfrescoRuntimeException(ERR_UPDATE_FAILED, e);
|
||||||
|
Reference in New Issue
Block a user