Ivan Suftin
Ivan Suftin

Reputation: 31

Liquibase Rollback in Java Doesn't Seem To Be Working

I am using Liquibase as part of my integration tests in Java. During the integration-test phase, I use Liquibase to create and populate my tables in the @BeforeClass function. My changesets are tagged once they are applied. In the @After function, I run a rollback to a specific tag.

I am then able to run unit tests to SELECT from the database as well as INSERT. However, I then try to rollback the changes after each test and am finding that Liquibase is not rolling back my changes.

I am using Liquibase 3.1.1 against an embedded Derby database.

My changeset file:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">

<include file="create-table-event-type.xml" relativeToChangelogFile="true"/>
<include file="load-table-event-type.xml" relativeToChangelogFile="true"/>

<include file="create-table-event.xml" relativeToChangelogFile="true"/>
<include file="load-table-event.xml" relativeToChangelogFile="true"/>

<changeSet id="tag-integration-test-database" author="Ivan Suftin" context="integration-test">
    <tagDatabase tag="integration-tests-tag" />
</changeSet>

</databaseChangeLog>

My unit test:

@Category(IntegrationTest.class)
public class EventDAOTest {

private static Connection conn;
private static SqlSessionFactory sqlSessionFactory;
private static Liquibase liquibase;
private EventDAO instance = null;
private static final String rollbackTag = "integration-tests-tag";
private static final Contexts contexts = new Contexts("integration-test");

public EventDAOTest() {
}

@BeforeClass
public static void setUpClass() throws ClassNotFoundException, SQLException, DatabaseException, LiquibaseException, InstantiationException, IllegalAccessException, IOException {
    String port = System.getProperty("db.twitter.integration-test.port");
    String driver = System.getProperty("db.twitter.integration-test.driver");
    String dbType = System.getProperty("db.twitter.integration-test.dbtype");
    String schema = System.getProperty("db.twitter.integration-test.schema");
    if (StringUtils.isBlank(port)) {
        throw new NullPointerException("System property \"db.twitter.integration-test.port\" not found");
    }
    if (StringUtils.isBlank(driver)) {
        throw new NullPointerException("System property \"db.twitter.integration-test.driver\" not found");
    }
    if (StringUtils.isBlank(dbType)) {
        throw new NullPointerException("System property \"db.twitter.integration-test.dbType\" not found");
    }
    if (StringUtils.isBlank(schema)) {
        throw new NullPointerException("System property \"db.twitter.integration-test.schema\" not found");
    }

    Class.forName(driver).newInstance();

    conn = DriverManager.getConnection("jdbc:" + dbType + "://localhost:" + port + "/" + schema + ";create=true", "test", "test");

    Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(conn));
    liquibase = new Liquibase("src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml", new FileSystemResourceAccessor(), database);

    liquibase.update(contexts);

    try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "integration-test");
    }
}


@AfterClass
public static void tearDownClass() throws SQLException {
    conn.close();
}

@Before
public void beforeTest() {
    instance = new EventDAO(sqlSessionFactory);
}

@After
public void afterTest() throws DatabaseException, LiquibaseException {
    liquibase.rollback(rollbackTag, contexts);
}

[...]

The rest of the test is just me using MyBatis to SELECT and INSERT.

Here's what the logs look like (just pasting the important parts)

INFO 3/8/14 7:23 PM:liquibase: Successfully acquired change log lock
INFO 3/8/14 7:23 PM:liquibase: Creating database history table with name: TEST.DATABASECHANGELOG
INFO 3/8/14 7:23 PM:liquibase: Reading from TEST.DATABASECHANGELOG

[ ... runs the changesets ... ]

INFO 3/8/14 7:23 PM:liquibase: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml: tag-integration-test-database::Ivan Suftin: Reading from TEST.DATABASECHANGELOG
INFO 3/8/14 7:23 PM:liquibase: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml: tag-integration-test-database::Ivan Suftin: Tag 'integration-tests-tag' applied to database
INFO 3/8/14 7:23 PM:liquibase: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml: tag-integration-test-database::Ivan Suftin: ChangeSet src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml::tag-integration-test-database::Ivan Suftin ran successfully in 11ms
INFO 3/8/14 7:23 PM:liquibase: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml: tag-integration-test-database::Ivan Suftin: Reading from TEST.DATABASECHANGELOG
INFO 3/8/14 7:23 PM:liquibase: Successfully released change log lock

Between each test, I try to run a rollback so I get a freshly loaded database to play with and the logs are shown here:

INFO 3/8/14 7:23 PM:liquibase: Successfully acquired change log lock
INFO 3/8/14 7:23 PM:liquibase: Reading from TEST.DATABASECHANGELOG
INFO 3/8/14 7:23 PM:liquibase: Reading from TEST.DATABASECHANGELOG
INFO 3/8/14 7:23 PM:liquibase: Successfully released change log lock
-- *********************************************************************
-- Rollback to 'integration-tests-tag' Script
-- *********************************************************************
-- Change Log: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml
-- Ran at: 3/8/14 7:23 PM
-- Against: test@jdbc:derby://localhost:59527/twitter;create=true
-- Liquibase version: 3.1.1
-- *********************************************************************

-- Lock Database
-- Rolling Back ChangeSet: src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml::tag-integration-test-database::Ivan Suftin
DELETE FROM TEST.DATABASECHANGELOG  WHERE ID='tag-integration-test-database' AND AUTHOR='Ivan Suftin' AND FILENAME='src/main/resources/liquibase/changelogs/create-table-parent-changeLog.xml';

-- Release Database Lock

The table I test against initially has two rows. I have two tests that do an insert. They run one after the other. In the first test, I run an INSERT for one row and then immediately run a SELECT to count the amount of rows. The result shows 3 rows, which is correct.

The next test also runs an INSERT for one row and again I run a SELECT to test the row count and it should be 3 rows but now I get 4 rows. This tells me that the rollback in the @After function is effectively doing nothing.

Is it me doing something wrong here?

I've also tried using the Java API to do create a tag immediately after the changelogs are run by running:

liquibase.tag(rollbackTag); 

... but I get the same result. Nothing gets rolled back.

Upvotes: 2

Views: 4143

Answers (1)

SteveDonie
SteveDonie

Reputation: 9016

Liquibase doesn't automatically generate rollback commands for insert statements, so unless you put those in manually Liquibase won't do anything. See http://www.liquibase.org/documentation/rollback.html for the docs on that.

Many refactorings such as “create table”, “rename column”, and “add column” can automatically create rollback statements. If your change log contains only statements that fit into this category, your rollback commands will be generated automatically.

Other refactorings such as “drop table” and “insert data” have no corresponding rollback commands that can be automatically generated. In these cases, and cases where you want to override the default generated rollback commands, you can specify the rollback commands via the tag within the changeSet tag. If you do not want anything done to undo a change in rollback mode, use an empty tag.

Upvotes: 7

Related Questions