/*
* Copyright 2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.versioned;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.sql.DataSource;
/**
* A group of changes that should be applied atomically to upgrade the database from one version to another.
* The changes are applied in the order they are added to this change set.
* @author Keith Donald
*/
public class DatabaseChangeSet implements Comparable<DatabaseChangeSet> {
private final Set<DatabaseChange> changes = new LinkedHashSet<DatabaseChange>();
private final DatabaseVersion version;
/**
* Constructs a DatabaseChangeSet that upgrades the Database to version.
*/
public DatabaseChangeSet(DatabaseVersion version) {
this.version = version;
}
/**
* Add a change to this change set.
*/
public void add(DatabaseChange change) {
changes.add(change);
}
/**
* The version the Database will be at after applying this change set.
*/
public DatabaseVersion getVersion() {
return version;
}
public int hashCode() {
return version.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof DatabaseChangeSet)) {
return false;
}
DatabaseChangeSet changeSet = (DatabaseChangeSet) o;
return version.equals(changeSet.version);
}
public int compareTo(DatabaseChangeSet changeSet) {
if (version.lessThan(changeSet.getVersion())) {
return -1;
} else if (version.greaterThan(changeSet.getVersion())){
return 1;
} else {
return 0;
}
}
/**
* Apply the changes in this change set to upgrade the database to {@link #getVersion() version}.
* Should only be called if the version of the Database is less than the version of this change set.
* Only upgrade the Database version if all changes in this set are applied successfully.
* If any change fails, the entire set will be rolled back and no changes will be applied.
*/
public DatabaseVersion apply(DataSource dataSource) {
Connection connection = getTransactionalConnection(dataSource);
try {
try {
for (DatabaseChange change : changes) {
change.apply(connection);
}
updateDatabaseVersion(version, connection);
} catch (RuntimeException e) {
rollbackTransaction(connection);
throw e;
}
commitTransaction(connection);
return version;
} finally {
closeConnection(connection);
}
}
// internal helpers
private Connection getTransactionalConnection(DataSource dataSource) {
try {
Connection connection = dataSource.getConnection();
beginTransaction(connection);
return connection;
}
catch (SQLException e) {
throw new RuntimeException("Could not get JDBC Connection", e);
}
}
private void beginTransaction(Connection connection) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException("Could not begin database transaction", e);
}
}
private void updateDatabaseVersion(DatabaseVersion version, Connection connection) {
try {
PreparedStatement statement = connection.prepareStatement("update DatabaseVersion set value = ?");
statement.setString(1, version.toString());
statement.execute();
} catch (SQLException e) {
throw new RuntimeException("Failed to update database version", e);
}
}
private void commitTransaction(Connection connection) {
try {
connection.commit();
} catch (SQLException e) {
throw new RuntimeException("Could not commit", e);
}
}
private void rollbackTransaction(Connection connection) {
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException("Could not rollback", e);
}
}
private void closeConnection(Connection connection) {
try {
connection.close();
}
catch (SQLException ex) {
// ignore
}
}
}