mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Provide API to run ddl and migration manually (e.g. with offline DB)
This commit is contained in:
@@ -1741,4 +1741,26 @@ public interface Database {
|
||||
*/
|
||||
void truncate(Class<?>... tables);
|
||||
|
||||
/**
|
||||
* Runs the DDL manually. Can be used, if database is offline or if you use a
|
||||
* TenantMode that does not support DDL run on startup.
|
||||
* <p>
|
||||
* Note: runDdl is normally executed at startup automatically, if
|
||||
* <code>ebean.db.runDdl=true</code> is set. Calling this method, will not check
|
||||
* that flag.
|
||||
* </p>
|
||||
*/
|
||||
void runDdl();
|
||||
|
||||
/**
|
||||
* Runs the migration manually. Can be used, if database is offline or if you use a
|
||||
* TenantMode that does not support DDL run on startup.
|
||||
* <p>
|
||||
* Note: runDdl is normally executed at startup automatically, if
|
||||
* <code>ebean.db.migration.run=true</code> is set. Calling this method, will not check
|
||||
* that flag.
|
||||
* </p>
|
||||
**/
|
||||
void runMigration();
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,14 @@ public interface SpiDdlGenerator {
|
||||
/**
|
||||
* Generate and run the DDL for drop-all and create-all scripts.
|
||||
* <p>
|
||||
* Run based on on property settings for ebean.ddl.generate and ebean.ddl.run etc.
|
||||
* Run based on on property settings for ebean.ddl.generate
|
||||
*/
|
||||
void execute(boolean online);
|
||||
void generateDdl();
|
||||
|
||||
/**
|
||||
* Runs the created DDL files.
|
||||
* <p>
|
||||
* Run based on on property settings for ebean.ddl.run etc.
|
||||
*/
|
||||
void runDdl();
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
public void executePlugins(boolean online) {
|
||||
if (!config.isDocStoreOnly()) {
|
||||
ddlGenerator.execute(online);
|
||||
ddlGenerator.generateDdl();
|
||||
}
|
||||
for (Plugin plugin : serverPlugins) {
|
||||
plugin.online(online);
|
||||
@@ -359,27 +359,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
serverCacheManager.enabledRegions(config.getEnabledL2Regions());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start any services after registering with the ClusterManager.
|
||||
*/
|
||||
public void start() {
|
||||
if (config.isRunMigration() && TenantMode.DB != config.getTenantMode()) {
|
||||
final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class);
|
||||
if (migrationRunner == null) {
|
||||
throw new IllegalStateException("No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?");
|
||||
|
||||
// check, if DB is available to run DDL and migration
|
||||
boolean dbAvailable = !config.isDocStoreOnly()
|
||||
&& !config.isDbOffline()
|
||||
&& config.getDataSource() != null
|
||||
&& config.getTenantMode().isDdlEnabled();
|
||||
|
||||
if (dbAvailable) {
|
||||
if (config.isDdlRun()) {
|
||||
runDdl();
|
||||
}
|
||||
final String dbSchema = config.getDbSchema();
|
||||
if (dbSchema != null) {
|
||||
migrationRunner.setDefaultDbSchema(dbSchema);
|
||||
if (config.isRunMigration()) {
|
||||
runMigration();
|
||||
}
|
||||
migrationRunner.setName(config.getName());
|
||||
migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase());
|
||||
migrationRunner.loadProperties(config.getProperties());
|
||||
migrationRunner.run(config.getDataSource());
|
||||
} else if (config.isDdlRun() || config.isRunMigration()) {
|
||||
log.warn("There was a request to run DDL or migration, but this is currently not possible, as the database is not available");
|
||||
}
|
||||
startQueryPlanCapture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runDdl() {
|
||||
ddlGenerator.runDdl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runMigration() {
|
||||
final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class);
|
||||
if (migrationRunner == null) {
|
||||
throw new IllegalStateException(
|
||||
"No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?");
|
||||
}
|
||||
|
||||
final String dbSchema = config.getDbSchema();
|
||||
if (dbSchema != null) {
|
||||
migrationRunner.setDefaultDbSchema(dbSchema);
|
||||
}
|
||||
migrationRunner.setName(config.getName());
|
||||
migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase());
|
||||
migrationRunner.loadProperties(config.getProperties());
|
||||
migrationRunner.run(dataSource());
|
||||
}
|
||||
|
||||
private void startQueryPlanCapture() {
|
||||
if (config.isQueryPlanCapture()) {
|
||||
long secs = config.getQueryPlanCapturePeriodSecs();
|
||||
|
||||
@@ -610,8 +610,13 @@ public final class InternalConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(boolean online) {
|
||||
if (online && ddlRun) {
|
||||
public void generateDdl() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runDdl() {
|
||||
if (ddlRun) {
|
||||
CoreLog.log.error("Configured to run DDL but ebean-ddl-generator is not in the classpath (or ebean-test in the test classpath?)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final boolean generateDdl;
|
||||
private final boolean runDdl;
|
||||
private final boolean extraDdl;
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
@@ -67,14 +66,7 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
final DatabasePlatform databasePlatform = server.databasePlatform();
|
||||
this.platform = databasePlatform.getPlatform();
|
||||
this.platformName = platform.base().name();
|
||||
if (!config.getTenantMode().isDdlEnabled() && config.isDdlRun()) {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + config.getTenantMode());
|
||||
this.runDdl = false;
|
||||
this.ddlAutoCommit = false;
|
||||
} else {
|
||||
this.runDdl = config.isDdlRun();
|
||||
this.ddlAutoCommit = databasePlatform.isDdlAutoCommit();
|
||||
}
|
||||
this.ddlAutoCommit = databasePlatform.isDdlAutoCommit();
|
||||
this.scriptTransform = createScriptTransform(config);
|
||||
this.baseDir = initBaseDir();
|
||||
}
|
||||
@@ -89,18 +81,11 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(boolean online) {
|
||||
generateDdl();
|
||||
if (online) {
|
||||
runDdl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the DDL drop and create scripts if the properties have been set.
|
||||
*/
|
||||
protected void generateDdl() {
|
||||
@Override
|
||||
public void generateDdl() {
|
||||
if (generateDdl) {
|
||||
if (!createOnly) {
|
||||
writeDrop(getDropFileName());
|
||||
@@ -112,16 +97,15 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
/**
|
||||
* Run the DDL drop and DDL create scripts if properties have been set.
|
||||
*/
|
||||
protected void runDdl() {
|
||||
if (runDdl) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = obtainConnection();
|
||||
runDdlWith(connection);
|
||||
} finally {
|
||||
JdbcClose.rollback(connection);
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
@Override
|
||||
public void runDdl() {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = obtainConnection();
|
||||
runDdlWith(connection);
|
||||
} finally {
|
||||
JdbcClose.rollback(connection);
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package io.ebean.config;
|
||||
|
||||
|
||||
import io.ebean.Database;
|
||||
import io.ebean.DatabaseFactory;
|
||||
import io.ebean.annotation.ForPlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.datasource.DataSourceAlert;
|
||||
import io.ebean.datasource.DataSourceInitialiseException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.basic.EBasicVer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
public class TestServerOffline {
|
||||
|
||||
@Test
|
||||
@ForPlatform({Platform.H2})
|
||||
public void testOffline_default() throws SQLException {
|
||||
|
||||
String url = "jdbc:h2:mem:testoffline1";
|
||||
try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) {
|
||||
Properties props = props(url);
|
||||
DatabaseConfig config = config(props);
|
||||
|
||||
assertThatThrownBy(() -> DatabaseFactory.create(config))
|
||||
.isInstanceOf(DataSourceInitialiseException.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class LazyDatasourceInitializer implements DataSourceAlert {
|
||||
|
||||
public Database server;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
@Override
|
||||
public void dataSourceUp(DataSource dataSource) {
|
||||
if (!initialized) {
|
||||
initDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void initDatabase() {
|
||||
if (!initialized) {
|
||||
server.runDdl();
|
||||
server.runMigration();
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataSourceDown(DataSource dataSource, SQLException reason) {}
|
||||
|
||||
@Override
|
||||
public void dataSourceWarning(DataSource dataSource, String msg) {}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ForPlatform({Platform.H2})
|
||||
public void testOffline_recovery() throws SQLException {
|
||||
|
||||
String url = "jdbc:h2:mem:testoffline3";
|
||||
try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) {
|
||||
|
||||
Properties props = props(url);
|
||||
|
||||
// to bring up ebean without a database, we must disable varous things
|
||||
// that happen on startup
|
||||
props.setProperty("datasource.h2_offline.failOnStart", "false");
|
||||
props.setProperty("ebean.h2_offline.skipDataSourceCheck", "true");
|
||||
props.setProperty("ebean.h2_offline.ddl.run", "false");
|
||||
DatabaseConfig config = config(props);
|
||||
|
||||
LazyDatasourceInitializer alert = new LazyDatasourceInitializer() ;
|
||||
config.getDataSourceConfig().setAlert(alert);
|
||||
config.getDataSourceConfig().setHeartbeatFreqSecs(1);
|
||||
|
||||
Database h2Offline = DatabaseFactory.create(config);
|
||||
alert.server = h2Offline;
|
||||
assertThat(h2Offline).isNotNull();
|
||||
// DB is online now in offline mode
|
||||
|
||||
// Accessing the DB will throw a PE
|
||||
assertThatThrownBy(() -> alert.initDatabase())
|
||||
.isInstanceOf(PersistenceException.class)
|
||||
.hasMessageContaining("Failed to obtain connection to run DDL");
|
||||
|
||||
assertThatThrownBy(() -> h2Offline.find(EBasicVer.class).findCount()).isInstanceOf(PersistenceException.class);
|
||||
|
||||
// so - reset the password so that the server can reconnect
|
||||
try (Statement stmt = bootup.createStatement()) {
|
||||
stmt.execute("alter user sa set password 'sa'");
|
||||
}
|
||||
|
||||
assertThat(alert.initialized).isFalse();
|
||||
|
||||
// next access to ebean should bring DS online
|
||||
h2Offline.find(EBasicVer.class).findCount();
|
||||
assertThat(alert.initialized).isTrue();
|
||||
|
||||
// check if server is working (ie ddl was run)
|
||||
EBasicVer bean = new EBasicVer("foo");
|
||||
h2Offline.save(bean);
|
||||
assertThat(h2Offline.find(EBasicVer.class).findCount()).isEqualTo(1);
|
||||
h2Offline.delete(bean);
|
||||
}
|
||||
}
|
||||
|
||||
private Properties props(String url) {
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
props.setProperty("datasource.h2_offline.username", "sa");
|
||||
props.setProperty("datasource.h2_offline.password", "sa");
|
||||
props.setProperty("datasource.h2_offline.url", url);
|
||||
props.setProperty("datasource.h2_offline.driver", "org.h2.Driver");
|
||||
|
||||
props.setProperty("ebean.h2_offline.databasePlatformName", "h2");
|
||||
props.setProperty("ebean.h2_offline.ddl.extra", "false");
|
||||
|
||||
props.setProperty("ebean.h2_offline.ddl.generate", "true");
|
||||
props.setProperty("ebean.h2_offline.ddl.run", "true");
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
private DatabaseConfig config(Properties props) {
|
||||
DatabaseConfig config = new DatabaseConfig();
|
||||
config.setName("h2_offline");
|
||||
config.loadFromProperties(props);
|
||||
config.setDefaultServer(false);
|
||||
config.setRegister(false);
|
||||
config.getClasses().add(EBasicVer.class);
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -642,4 +642,14 @@ public class TDSpiServer implements SpiServer {
|
||||
public void loadBean(EntityBeanIntercept ebi) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runDdl() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runMigration() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user