diff --git a/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java b/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java index 51e6f9618b..c1b2d9f25b 100644 --- a/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java +++ b/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java @@ -61,6 +61,10 @@ public interface DatabaseBuilder { /** * Build and return the Database instance. + *
+ * When {@link #setRegister(boolean)} is set to true, and a database with the same + * name is already registered, this may return the existing registered database + * rather than creating a new one. */ Database build(); diff --git a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java index 51b6282c05..e3044854f0 100644 --- a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java +++ b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java @@ -7,6 +7,8 @@ import java.util.concurrent.locks.ReentrantLock; +import static java.lang.System.Logger.Level.WARNING; + /** * Low-level factory for creating {@link Database} instances. *
@@ -71,18 +73,28 @@ public static Database create(DatabaseBuilder builder) { lock.lock(); try { var config = builder.settings(); - if (config.getName() == null) { + var name = config.getName(); + if (name == null) { throw new PersistenceException("The name is null (it is required)"); } + if (config.isRegister()) { + // We're explicitly creating a database to be registered, so avoid + // triggering DbContext static initialisation to auto-create a default one. + DbPrimary.setSkip(true); + Database existing = DbContext.getInstance().getRegistered(name); + if (existing != null) { + EbeanVersion.log.log(WARNING, "Using existing database with name:{0}", name); + return existing; + } + } Database server = createInternal(config); if (config.isRegister()) { if (config.isDefaultServer()) { - if (defaultServerName != null && !defaultServerName.equals(config.getName())) { - throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default"); + if (defaultServerName != null && !defaultServerName.equals(name)) { + throw new IllegalStateException("Registering [" + name + "] as the default server but [" + defaultServerName + "] is already registered as the default"); } - defaultServerName = config.getName(); + defaultServerName = name; } - DbPrimary.setSkip(true); DbContext.getInstance().register(server, config.isDefaultServer()); } return server; diff --git a/ebean-api/src/main/java/io/ebean/DbContext.java b/ebean-api/src/main/java/io/ebean/DbContext.java index eddc890203..19a444848a 100644 --- a/ebean-api/src/main/java/io/ebean/DbContext.java +++ b/ebean-api/src/main/java/io/ebean/DbContext.java @@ -4,6 +4,8 @@ import io.ebean.datasource.DataSourceConfigurationException; import jakarta.persistence.PersistenceException; +import org.jspecify.annotations.Nullable; + import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; @@ -75,6 +77,11 @@ Database getDefault() { return defaultDatabase; } + @Nullable + Database getRegistered(String name) { + return concMap.get(name); + } + /** * Return the database by name. */ diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java index fe90e61905..db1854ea91 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java +++ b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; @@ -79,6 +80,59 @@ public void test() throws InterruptedException { restartedServer.shutdown(true, false); } + @Test + public void create_registeredDatabase_twice_returnsExistingInstance() { + + DatabaseBuilder config = new DatabaseConfig(); + config.setName("h2"); + config.loadFromProperties(); + config.setName("dup-" + System.nanoTime()); + config.setDdlGenerate(false); + config.setDdlRun(false); + config.setDdlExtra(false); + config.setDefaultServer(false); + config.setRegister(true); + config.addClass(UTDetail.class); + + AtomicInteger startupCount = new AtomicInteger(); + config.addServerConfigStartup(serverConfig -> startupCount.incrementAndGet()); + + Database db = DatabaseFactory.create(config); + Database existing = DatabaseFactory.create(config); + + assertThat(existing).isSameAs(db); + assertThat(startupCount.get()).isEqualTo(1); + + db.shutdown(true, false); + } + + @Test + public void create_unregisteredDatabase_twice_returnsDifferentInstances() { + + DatabaseBuilder config = new DatabaseConfig(); + config.setName("h2"); + config.loadFromProperties(); + config.setName("dup-unregistered-" + System.nanoTime()); + config.setDdlGenerate(false); + config.setDdlRun(false); + config.setDdlExtra(false); + config.setDefaultServer(false); + config.setRegister(false); + config.addClass(UTDetail.class); + + AtomicInteger startupCount = new AtomicInteger(); + config.addServerConfigStartup(serverConfig -> startupCount.incrementAndGet()); + + Database db = DatabaseFactory.create(config); + Database other = DatabaseFactory.create(config); + + assertThat(other).isNotSameAs(db); + assertThat(startupCount.get()).isEqualTo(2); + + db.shutdown(true, false); + other.shutdown(true, false); + } + public static class OnStartup implements ServerConfigStartup { DatabaseBuilder calledWithConfig;