001package io.ebean.dbmigration; 002 003import io.ebean.dbmigration.runner.LocalMigrationResource; 004import io.ebean.dbmigration.runner.LocalMigrationResources; 005import io.ebean.dbmigration.runner.MigrationTable; 006import io.ebean.dbmigration.runner.MigrationSchema; 007import io.ebean.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(MigrationRunner.class); 023 024 private final MigrationConfig migrationConfig; 025 026 /** 027 * Set to true when only checking as to what migrations will run. 028 */ 029 private boolean checkStateMode; 030 031 private List<LocalMigrationResource> checkMigrations; 032 033 public MigrationRunner(MigrationConfig migrationConfig) { 034 this.migrationConfig = migrationConfig; 035 } 036 037 /** 038 * Run by creating a DB connection from driver, url, username defined in MigrationConfig. 039 */ 040 public void run() { 041 this.checkStateMode = false; 042 run(migrationConfig.createConnection()); 043 } 044 045 /** 046 * Return the migrations that would be applied if the migration is run. 047 */ 048 public List<LocalMigrationResource> checkState() { 049 this.checkStateMode = true; 050 run(migrationConfig.createConnection()); 051 return checkMigrations; 052 } 053 054 /** 055 * Run using the connection from the DataSource. 056 */ 057 public void run(DataSource dataSource) { 058 run(getConnection(dataSource)); 059 } 060 061 private Connection getConnection(DataSource dataSource) { 062 063 String username = migrationConfig.getDbUsername(); 064 try { 065 if (username == null) { 066 return dataSource.getConnection(); 067 } 068 logger.debug("using db user [{}] to run migrations ...", username); 069 return dataSource.getConnection(username, migrationConfig.getDbPassword()); 070 } catch (SQLException e) { 071 String msgSuffix = (username == null) ? "" : " using user [" + username + "]"; 072 throw new IllegalArgumentException("Error trying to connect to database for DB Migration" + msgSuffix, e); 073 } 074 } 075 076 /** 077 * Run the migrations if there are any that need running. 078 */ 079 public void run(Connection connection) { 080 081 LocalMigrationResources resources = new LocalMigrationResources(migrationConfig); 082 if (!resources.readResources()) { 083 logger.debug("no migrations to check"); 084 return; 085 } 086 087 try { 088 connection.setAutoCommit(false); 089 090 MigrationSchema schema = new MigrationSchema(migrationConfig, connection); 091 schema.createAndSetIfNeeded(); 092 093 runMigrations(resources, connection); 094 connection.commit(); 095 096 } catch (MigrationException e) { 097 JdbcClose.rollback(connection); 098 throw e; 099 100 } catch (Exception e) { 101 JdbcClose.rollback(connection); 102 throw new RuntimeException(e); 103 104 } finally { 105 JdbcClose.close(connection); 106 } 107 } 108 109 /** 110 * Run all the migrations as needed. 111 */ 112 private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException { 113 114 derivePlatformName(migrationConfig, connection); 115 116 MigrationTable table = new MigrationTable(migrationConfig, connection, checkStateMode); 117 table.createIfNeededAndLock(); 118 119 // get the migrations in version order 120 List<LocalMigrationResource> localVersions = resources.getVersions(); 121 122 logger.info("local migrations:{} existing migrations:{}", localVersions.size(), table.size()); 123 124 LocalMigrationResource priorVersion = null; 125 126 // run migrations in order 127 for (LocalMigrationResource localVersion : localVersions) { 128 if (!table.shouldRun(localVersion, priorVersion)) { 129 break; 130 } 131 priorVersion = localVersion; 132 } 133 if (checkStateMode) { 134 checkMigrations = table.ran(); 135 } 136 } 137 138 /** 139 * Derive and set the platform name if required. 140 */ 141 private void derivePlatformName(MigrationConfig migrationConfig, Connection connection) { 142 143 if (migrationConfig.getPlatformName() == null) { 144 migrationConfig.setPlatformName(DbNameUtil.normalise(connection)); 145 } 146 } 147 148}