diff --git a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java index b9b6a994f..13a0c4125 100644 --- a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java +++ b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java @@ -9,6 +9,7 @@ import javax.persistence.PersistenceException; import java.util.Iterator; import java.util.Properties; import java.util.ServiceLoader; +import java.util.concurrent.locks.ReentrantLock; /** * Creates Database instances. @@ -30,6 +31,7 @@ import java.util.ServiceLoader; */ public class DatabaseFactory { + private static final ReentrantLock lock = new ReentrantLock(false); private static SpiContainer container; static { @@ -42,44 +44,63 @@ public class DatabaseFactory { * Call this prior to creating any Database instances or alternatively set the * ContainerConfig on the DatabaseConfig when creating the first Database instance. */ - public static synchronized void initialiseContainer(ContainerConfig containerConfig) { - getContainer(containerConfig); + public static void initialiseContainer(ContainerConfig containerConfig) { + lock.lock(); + try { + getContainer(containerConfig); + } finally { + lock.unlock(); + } } /** * Create using properties to configure the database. */ - public static synchronized Database create(String name) { - // construct based on loading properties files - return getContainer(null).createServer(name); + public static Database create(String name) { + lock.lock(); + try { + return getContainer(null).createServer(name); + } finally { + lock.unlock(); + } } /** * Create using the DatabaseConfig object to configure the database. */ - public static synchronized Database create(DatabaseConfig config) { - if (config.getName() == null) { - throw new PersistenceException("The name is null (it is required)"); + public static Database create(DatabaseConfig config) { + lock.lock(); + try { + if (config.getName() == null) { + throw new PersistenceException("The name is null (it is required)"); + } + Database server = createInternal(config); + if (config.isRegister()) { + DbPrimary.setSkip(true); + DbContext.getInstance().register(server, config.isDefaultServer()); + } + return server; + } finally { + lock.unlock(); } - Database server = createInternal(config); - if (config.isRegister()) { - DbPrimary.setSkip(true); - DbContext.getInstance().register(server, config.isDefaultServer()); - } - return server; } /** * Create using the DatabaseConfig additionally specifying a classLoader to use as the context class loader. */ - public static synchronized Database createWithContextClassLoader(DatabaseConfig config, ClassLoader classLoader) { - ClassLoader currentContextLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(classLoader); + public static Database createWithContextClassLoader(DatabaseConfig config, ClassLoader classLoader) { + lock.lock(); try { - return DatabaseFactory.create(config); + ClassLoader currentContextLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader); + try { + return DatabaseFactory.create(config); + } finally { + // set the currentContextLoader back + Thread.currentThread().setContextClassLoader(currentContextLoader); + } } finally { - // set the currentContextLoader back - Thread.currentThread().setContextClassLoader(currentContextLoader); + lock.unlock(); } } @@ -89,8 +110,13 @@ public class DatabaseFactory { * This is typically invoked via JVM shutdown hook and not explicitly called. *

*/ - public static synchronized void shutdown() { - container.shutdown(); + public static void shutdown() { + lock.lock(); + try { + container.shutdown(); + } finally { + lock.unlock(); + } } private static Database createInternal(DatabaseConfig config) { @@ -103,8 +129,7 @@ public class DatabaseFactory { * @param containerConfig the configuration controlling clustering communication */ private static SpiContainer getContainer(ContainerConfig containerConfig) { - - // thread safe in that all calling methods are synchronized + // thread safe in that all calling methods hold lock if (container != null) { return container; } @@ -123,7 +148,6 @@ public class DatabaseFactory { * Create the container instance using the configuration. */ protected static SpiContainer createContainer(ContainerConfig containerConfig) { - Iterator factories = ServiceLoader.load(SpiContainerFactory.class).iterator(); if (factories.hasNext()) { return factories.next().create(containerConfig); diff --git a/ebean-api/src/main/java/io/ebean/DbPrimary.java b/ebean-api/src/main/java/io/ebean/DbPrimary.java index 2bad03025..9cbdc2eba 100644 --- a/ebean-api/src/main/java/io/ebean/DbPrimary.java +++ b/ebean-api/src/main/java/io/ebean/DbPrimary.java @@ -3,6 +3,7 @@ package io.ebean; import io.avaje.config.Config; import java.util.Properties; +import java.util.concurrent.locks.ReentrantLock; /** * Provides singleton state for the default database. @@ -11,47 +12,66 @@ import java.util.Properties; */ class DbPrimary { + private static final ReentrantLock lock = new ReentrantLock(false); private static String defaultServerName; - private static boolean skip; /** * Set whether to skip automatically creating the primary database. */ - static synchronized void setSkip(boolean skip) { - DbPrimary.skip = skip; + static void setSkip(boolean skip) { + lock.lock(); + try { + DbPrimary.skip = skip; + } finally { + lock.unlock(); + } } /** * Return true to skip automatically creating the primary database. */ - static synchronized boolean isSkip() { - return skip; + static boolean isSkip() { + lock.lock(); + try { + return skip; + } finally { + lock.unlock(); + } } /** * Return the default database name. */ - static synchronized String getDefaultServerName() { - getProperties(); - return defaultServerName; + static String getDefaultServerName() { + lock.lock(); + try { + getProperties(); + return defaultServerName; + } finally { + lock.unlock(); + } } /** * Return the default configuration Properties. */ - static synchronized Properties getProperties() { - if (defaultServerName == null) { - defaultServerName = determineDefaultServerName(); + static Properties getProperties() { + lock.lock(); + try { + if (defaultServerName == null) { + defaultServerName = determineDefaultServerName(); + } + return Config.asProperties(); + } finally { + lock.unlock(); } - return Config.asProperties(); } /** * Determine and return the default server name checking system environment variables and then global properties. */ private static String determineDefaultServerName() { - String defaultServerName = System.getenv("EBEAN_DB"); defaultServerName = System.getProperty("db", defaultServerName); defaultServerName = System.getProperty("ebean_db", defaultServerName); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java index fba258d02..9b3204f48 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.EMPTY_LIST; @@ -33,6 +34,7 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase implements Sc static class Factory implements PlatformArrayTypeFactory { + private final ReentrantLock lock = new ReentrantLock(false); private final Map cache = new HashMap<>(); /** @@ -40,7 +42,8 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase implements Sc */ @Override public ScalarTypeArrayList typeFor(Type valueType, boolean nullable) { - synchronized (this) { + lock.lock(); + try { String key = valueType + ":" + nullable; if (valueType.equals(UUID.class)) { return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); @@ -58,6 +61,8 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase implements Sc return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); + } finally { + lock.unlock(); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java index 22d6e932d..192da6742 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.EMPTY_LIST; @@ -24,6 +25,7 @@ class ScalarTypeArrayListH2 extends ScalarTypeArrayList { static class Factory implements PlatformArrayTypeFactory { + private final ReentrantLock lock = new ReentrantLock(false); private final Map cache = new HashMap<>(); /** @@ -31,7 +33,8 @@ class ScalarTypeArrayListH2 extends ScalarTypeArrayList { */ @Override public ScalarType typeFor(Type valueType, boolean nullable) { - synchronized (this) { + lock.lock(); + try { String key = valueType + ":" + nullable; if (valueType.equals(UUID.class)) { return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); @@ -49,6 +52,8 @@ class ScalarTypeArrayListH2 extends ScalarTypeArrayList { return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); + } finally { + lock.unlock(); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java index 10825e0b0..ba1f25805 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java @@ -18,6 +18,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.EMPTY_SET; @@ -33,6 +34,7 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase implements Scal static class Factory implements PlatformArrayTypeFactory { + private final ReentrantLock lock = new ReentrantLock(false); private final Map cache = new HashMap<>(); /** @@ -40,7 +42,8 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase implements Scal */ @Override public ScalarType typeFor(Type valueType, boolean nullable) { - synchronized (this) { + lock.lock(); + try { String key = valueType + ":" + nullable; if (valueType.equals(UUID.class)) { return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); @@ -58,6 +61,8 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase implements Scal return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); + } finally { + lock.unlock(); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java index 1c6e39c6d..e893dcd6e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.EMPTY_SET; @@ -24,6 +25,7 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { static class Factory implements PlatformArrayTypeFactory { + private final ReentrantLock lock = new ReentrantLock(false); private final Map cache = new HashMap<>(); /** @@ -31,7 +33,8 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { */ @Override public ScalarType typeFor(Type valueType, boolean nullable) { - synchronized (this) { + lock.lock(); + try { String key = valueType + ":" + nullable; if (valueType.equals(UUID.class)) { return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); @@ -49,6 +52,8 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); + } finally { + lock.unlock(); } } diff --git a/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java b/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java index 42bcd846d..3b2cfc5f5 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java +++ b/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java @@ -30,7 +30,7 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase { return server().getServerCacheManager(); } - private static synchronized void insertSome() { + private static void insertSome() { if (!loadOnce) { Ebean.find(OCachedNatKeyBean.class).delete(); for (String store : Arrays.asList("abc", "def")) { diff --git a/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java b/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java index 463f73d88..236b2545e 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java +++ b/ebean-core/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java @@ -28,7 +28,7 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase { return server().getServerCacheManager(); } - private static synchronized void insertSome() { + private static void insertSome() { if (!loadOnce) { Ebean.find(OCachedNatKeyBean3.class).delete(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java b/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java index a8eea6904..31f534c24 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java +++ b/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java @@ -2,15 +2,23 @@ package io.ebean.test.config; import io.ebeaninternal.api.DbOffline; +import java.util.concurrent.locks.ReentrantLock; + class RunOnceMarker { + private static final ReentrantLock lock = new ReentrantLock(false); private static boolean hasRun; - static synchronized boolean isRun() { - if (DbOffline.isSet() || hasRun) { - return false; + static boolean isRun() { + lock.lock(); + try { + if (DbOffline.isSet() || hasRun) { + return false; + } + hasRun = true; + return true; + } finally { + lock.unlock(); } - hasRun = true; - return true; } }