001package org.avaje.dbmigration; 002 003import org.avaje.dbmigration.runner.LocalMigrationResource; 004import org.avaje.dbmigration.runner.LocalMigrationResources; 005import org.avaje.dbmigration.runner.MigrationTable; 006import org.avaje.dbmigration.runner.MigrationSchema; 007import org.avaje.dbmigration.util.JdbcClose; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import javax.sql.DataSource; 012import java.io.IOException; 013import java.sql.Connection; 014import java.sql.SQLException; 015import java.util.List; 016 017/** 018 * Runs the DB migration typically on application start. 019 */ 020public class MigrationRunner { 021 022 private static final Logger logger = LoggerFactory.getLogger("org.avaje.dbmigration.MigrationRunner"); 023 024 private final MigrationConfig migrationConfig; 025 026 public MigrationRunner(MigrationConfig migrationConfig) { 027 this.migrationConfig = migrationConfig; 028 } 029 030 /** 031 * Run by creating a DB connection from driver, url, username defined in MigrationConfig. 032 */ 033 public void run() { 034 035 Connection connection = migrationConfig.createConnection(); 036 run(connection); 037 } 038 039 /** 040 * Run using the connection from the DataSource. 041 */ 042 public void run(DataSource dataSource) { 043 run(getConnection(dataSource)); 044 } 045 046 private Connection getConnection(DataSource dataSource) { 047 048 String username = migrationConfig.getDbUsername(); 049 try { 050 if (username == null) { 051 return dataSource.getConnection(); 052 } 053 logger.debug("using db user [{}] to run migrations ...", username); 054 return dataSource.getConnection(username, migrationConfig.getDbPassword()); 055 } catch (SQLException e) { 056 String msgSuffix = (username == null) ? "" : " using user [" + username + "]"; 057 throw new IllegalArgumentException("Error trying to connect to database for DB Migration" + msgSuffix, e); 058 } 059 } 060 061 /** 062 * Run the migrations if there are any that need running. 063 */ 064 public void run(Connection connection) { 065 066 LocalMigrationResources resources = new LocalMigrationResources(migrationConfig); 067 if (!resources.readResources()) { 068 logger.debug("no migrations to check"); 069 return; 070 } 071 072 try { 073 connection.setAutoCommit(false); 074 075 MigrationSchema schema = new MigrationSchema(migrationConfig, connection); 076 schema.createAndSetIfNeeded(); 077 078 runMigrations(resources, connection); 079 080 connection.commit(); 081 082 } catch (Exception e) { 083 JdbcClose.rollback(connection); 084 throw new RuntimeException(e); 085 086 } finally { 087 JdbcClose.close(connection); 088 } 089 } 090 091 /** 092 * Run all the migrations as needed. 093 */ 094 private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException { 095 096 MigrationTable table = new MigrationTable(migrationConfig, connection); 097 table.createIfNeeded(); 098 099 // get the migrations in version order 100 List<LocalMigrationResource> localVersions = resources.getVersions(); 101 102 logger.info("local migrations:{} existing migrations:{}", localVersions.size(), table.size()); 103 104 LocalMigrationResource priorVersion = null; 105 106 // run migrations in order 107 for (int i = 0; i < localVersions.size(); i++) { 108 LocalMigrationResource localVersion = localVersions.get(i); 109 if (!table.shouldRun(localVersion, priorVersion)) { 110 break; 111 } 112 priorVersion = localVersion; 113 connection.commit(); 114 } 115 } 116 117}