Cannot migrate multiple times using one Liquibase instance

Description

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 and Liquibase instance for each iteration.

However the goal is to reuse the parsing and preprocessing, and reuse as much as possible (including the JDBC connection).

Environment

Tomcat 8, Java 8, PostgreSQL 9.3

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

Reporter

Fix versions

Affects versions

Priority

Created June 28, 2014 at 10:52 AM
Updated March 15, 2016 at 3:04 AM

Flag notifications