Cannot migrate multiple times using one Liquibase instance
Description
Environment
Tomcat 8, Java 8, PostgreSQL 9.3
discovered while testing
Activity
Show:
Hendy Irawan June 30, 2015 at 8:18 AM
Update: A better workaround is possible: Use a single PostgresDatabase
and Liquibase
, but for each iteration/schema, create a Connection
and manually execute SET SCHEMA
as per CORE-1873, e.g.
/**
* Migrates using Liquibase for the specified {@code tenantId}.
* @param tenantId
* @throws SQLException
* @throws LiquibaseException
*/
public void migrate(Set<String> tenantIds) throws RepositoryException {
final ClassLoaderResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(this.entityClass.getClassLoader());
final PostgresDatabase db = new PostgresDatabase();
try {
final Liquibase liquibase;
try {
liquibase = new Liquibase(liquibasePath, resourceAccessor, db);
} catch (LiquibaseException e) {
throw new RepositoryException(e, "Cannot migrate tenants %s using '%s': %s", tenantIds, liquibasePath, e);
}
for (String tenantId : tenantIds) {
try (final Closeable cl = CommandRequestAttributes.withMdc(tenantId)) {
log.info("[{}] Migrating {}", tenantId, entityClass.getSimpleName());
final Contexts contexts = new Contexts();
try (final Connection conn = dataSource.getConnection()) {
// TODO: SET SCHEMA is workaround for Liquibase's not setting schema for <sql>. https://liquibase.jira.com/browse/CORE-1873
final Statement st = conn.createStatement();
st.executeUpdate("SET SCHEMA '" + tenantId + "'");
final JdbcConnection jdbc = new JdbcConnection(conn);
db.setDefaultSchemaName(tenantId);
try {
db.setConnection(jdbc);
liquibase.update(contexts);
} finally {
st.executeUpdate("SET SCHEMA 'public'");
}
}
} catch (Exception e) {
throw new RepositoryException(e, "Cannot migrate '%s' using '%s': %s", tenantId, liquibasePath, e);
}
}
} finally {
try {
db.close();
} catch (Exception e) {
log.debug("Ignoring close database error: " + e);
}
}
}
Details
Details
Reporter
Hendy Irawan
Hendy IrawanComponents
Fix versions
Affects versions
Priority
Created June 28, 2014 at 10:52 AM
Updated March 15, 2016 at 3:04 AM
The following code does not work as intended.
protected void migrate(Set<String> tenantIds) throws SQLException, LiquibaseException, DatabaseException { final ClassLoader classLoader = QuikdoJpaConfig.class.getClassLoader(); Preconditions.checkNotNull(classLoader.getResource(QUIKDO_LIQUIBASE_PATH), "Liquibase file '%s' not found using %s", QUIKDO_LIQUIBASE_PATH, classLoader); final ClassLoaderResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classLoader); try (final Connection conn = dataSource.getConnection()) { final JdbcConnection jdbc = new JdbcConnection(conn); final PostgresDatabase db = new PostgresDatabase(); try { db.setConnection(jdbc); final Liquibase liquibase = new Liquibase(QUIKDO_LIQUIBASE_PATH, resourceAccessor, db); for (final String tenantId : tenantIds) { try (final Closeable cl = CommandRequestAttributes.withMdc(tenantId)) { log.info("{}» Migrating using {}", tenantId, classLoader); // TODO: SET SCHEMA is workaround for Liquibase's not setting schema for <sql>. https://liquibase.jira.com/browse/CORE-1873 final Statement st = conn.createStatement(); st.executeUpdate("SET SCHEMA '" + tenantId + "'"); db.setDefaultSchemaName(tenantId); try { final Contexts contexts = new Contexts(); liquibase.update(contexts); } finally { st.executeUpdate("SET SCHEMA 'public'"); } } catch (Exception e) { throw new RepositoryException(e, "%s» Cannot migrate '%s' using %s: %s", tenantId, QUIKDO_LIQUIBASE_PATH, classLoader, e); } } } finally { db.close(); } } }
Given several schemas, Liquibase will only migrate the first schema. And "ignore" the rest. Note that from the logs, Liquibase seems to be trying to read the
databasechangelog
tables of each respective schema, but it seems it's misleading:06:42:49.500 | INFO | host-startStop-1 | c.q.g.c.a.GuardianAnalyticsImpl | vertical» Migrating Guardian Analytics PostgreSQL schema for tenant 'vertical' 06:42:49.576 | INFO | host-startStop-1 | liquibase | Successfully acquired change log lock 06:42:49.610 | INFO | host-startStop-1 | liquibase | Reading from vertical.databasechangelog 06:42:49.632 | INFO | host-startStop-1 | liquibase | Successfully released change log lock 06:42:49.632 | INFO | host-startStop-1 | c.q.g.c.a.GuardianAnalyticsImpl | acme» Migrating Guardian Analytics PostgreSQL schema for tenant 'acme' 06:42:49.649 | INFO | host-startStop-1 | liquibase | Successfully acquired change log lock 06:42:49.674 | INFO | host-startStop-1 | liquibase | Reading from acme.databasechangelog 06:42:49.693 | INFO | host-startStop-1 | liquibase | Successfully released change log lock 06:42:49.693 | INFO | host-startStop-1 | c.q.g.c.a.GuardianAnalyticsImpl | adena» Migrating Guardian Analytics PostgreSQL schema for tenant 'adena' 06:42:49.710 | INFO | host-startStop-1 | liquibase | Successfully acquired change log lock 06:42:49.735 | INFO | host-startStop-1 | liquibase | Reading from adena.databasechangelog 06:42:49.757 | INFO | host-startStop-1 | liquibase | Successfully released change log lock
The workaround is to create a fresh
PostgresDatabase
instance andLiquibase
instance for each iteration.However the goal is to reuse the parsing and preprocessing, and reuse as much as possible (including the JDBC connection).