Set up database connection pool (#234)

Set up database connection pool
This commit is contained in:
Shicong Huang 2019-08-29 16:12:28 -04:00 committed by GitHub
parent 4c089b1694
commit 28c5fec788
8 changed files with 233 additions and 23 deletions

View file

@ -218,7 +218,6 @@ dependencies {
compile deps['org.bouncycastle:bcpg-jdk15on']
testCompile deps['org.bouncycastle:bcpkix-jdk15on']
compile deps['org.bouncycastle:bcprov-jdk15on']
compile deps['org.hibernate:hibernate-core']
compile deps['org.joda:joda-money']
compile deps['org.json:json']
testCompile deps['org.mortbay.jetty:jetty']
@ -254,7 +253,7 @@ dependencies {
testCompile deps['org.hamcrest:hamcrest-all']
testCompile deps['org.hamcrest:hamcrest-core']
testCompile deps['org.hamcrest:hamcrest-library']
compile deps['org.hibernate:hibernate-core']
compile deps['org.hibernate:hibernate-hikaricp']
testCompile deps['junit:junit']
testCompile deps['org.mockito:mockito-core']
runtime deps['org.postgresql:postgresql']

View file

@ -112,7 +112,7 @@ public final class RegistryConfig {
}
/**
* The product name of this specific registry. Used throughout the registrar console.
* The product name of this specific registry. Used throughout the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@ -123,11 +123,11 @@ public final class RegistryConfig {
}
/**
* Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of
* Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of
* "ROID" would end up creating roids that look like "ABC123-ROID".
*
* @see <a href="http://www.iana.org/assignments/epp-repository-ids/epp-repository-ids.xhtml">
* Extensible Provisioning Protocol (EPP) Repository Identifiers</a>
* Extensible Provisioning Protocol (EPP) Repository Identifiers</a>
*/
@Provides
@Config("contactAndHostRoidSuffix")
@ -136,7 +136,7 @@ public final class RegistryConfig {
}
/**
* The e-mail address for questions about integrating with the registry. Used in the
* The e-mail address for questions about integrating with the registry. Used in the
* "contact-us" section of the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
@ -148,7 +148,7 @@ public final class RegistryConfig {
}
/**
* The e-mail address for general support. Used in the "contact-us" section of the registrar
* The e-mail address for general support. Used in the "contact-us" section of the registrar
* console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
@ -160,7 +160,7 @@ public final class RegistryConfig {
}
/**
* The "From" e-mail address for announcements. Used in the "contact-us" section of the
* The "From" e-mail address for announcements. Used in the "contact-us" section of the
* registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
@ -172,7 +172,7 @@ public final class RegistryConfig {
}
/**
* The contact phone number. Used in the "contact-us" section of the registrar console.
* The contact phone number. Used in the "contact-us" section of the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@ -1040,8 +1040,8 @@ public final class RegistryConfig {
}
/**
* The global automatic transfer length for contacts. After this amount of time has
* elapsed, the transfer is automatically approved.
* The global automatic transfer length for contacts. After this amount of time has elapsed, the
* transfer is automatically approved.
*
* @see google.registry.flows.contact.ContactTransferRequestFlow
*/
@ -1196,7 +1196,7 @@ public final class RegistryConfig {
/**
* Provides the OAuth scopes that authentication logic should detect on access tokens.
*
* <p>This list should be a superset of the required OAuth scope set provided below. Note that
* <p>This list should be a superset of the required OAuth scope set provided below. Note that
* ideally, this setting would not be required and all scopes on an access token would be
* detected automatically, but that is not the case due to the way {@code OAuthService} works.
*
@ -1297,9 +1297,7 @@ public final class RegistryConfig {
}
}
/**
* Returns the App Engine project ID, which is based off the environment name.
*/
/** Returns the App Engine project ID, which is based off the environment name. */
public static String getProjectId() {
return CONFIG_SETTINGS.get().appEngine.projectId;
}
@ -1451,20 +1449,51 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
}
/**
* Returns the number of {@code EppResourceIndex} buckets to be used.
*/
/** Returns the number of {@code EppResourceIndex} buckets to be used. */
public static int getEppResourceIndexBucketCount() {
return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum;
}
/**
* Returns the base retry duration that gets doubled after each failure within {@code Ofy}.
*/
/** Returns the base retry duration that gets doubled after each failure within {@code Ofy}. */
public static Duration getBaseOfyRetryDuration() {
return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis);
}
/** Returns the default database transaction isolation. */
public static String getHibernateConnectionIsolation() {
return CONFIG_SETTINGS.get().hibernate.connectionIsolation;
}
/** Returns true if hibernate.show_sql is enabled. */
public static String getHibernateLogSqlQueries() {
return CONFIG_SETTINGS.get().hibernate.logSqlQueries;
}
/** Returns true if schema modification is allowed. */
public static String getHibernateHbm2ddlAuto() {
return CONFIG_SETTINGS.get().hibernate.hbm2ddlAuto;
}
/** Returns the connection timeout for HikariCP. */
public static String getHibernateHikariConnectionTimeout() {
return CONFIG_SETTINGS.get().hibernate.hikariConnectionTimeout;
}
/** Returns the minimum idle connections for HikariCP. */
public static String getHibernateHikariMinimumIdle() {
return CONFIG_SETTINGS.get().hibernate.hikariMinimumIdle;
}
/** Returns the maximum pool size for HikariCP. */
public static String getHibernateHikariMaximumPoolSize() {
return CONFIG_SETTINGS.get().hibernate.hikariMaximumPoolSize;
}
/** Returns the idle timeout for HikariCP. */
public static String getHibernateHikariIdleTimeout() {
return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout;
}
/** Returns the roid suffix to be used for the roids of all contacts and hosts. */
public static String getContactAndHostRoidSuffix() {
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;

View file

@ -25,6 +25,7 @@ public class RegistryConfigSettings {
public CredentialOAuth credentialOAuth;
public RegistryPolicy registryPolicy;
public Datastore datastore;
public Hibernate hibernate;
public CloudDns cloudDns;
public Caching caching;
public IcannReporting icannReporting;
@ -105,6 +106,17 @@ public class RegistryConfigSettings {
public int baseOfyRetryMillis;
}
/** Configuration for Hibernate. */
public static class Hibernate {
public String connectionIsolation;
public String logSqlQueries;
public String hbm2ddlAuto;
public String hikariConnectionTimeout;
public String hikariMinimumIdle;
public String hikariMaximumPoolSize;
public String hikariIdleTimeout;
}
/** Configuration for Apache Beam (Cloud Dataflow). */
public static class Beam {
public String defaultJobZone;

View file

@ -191,6 +191,26 @@ datastore:
# doubles after each failure).
baseOfyRetryMillis: 100
hibernate:
# Make 'SERIALIZABLE' the default isolation level to ensure correctness.
#
# Entities that are never involved in multi-table transactions may use optimistic
# locks and a less strict isolation level. We may lower individual transaction's
# isolation level using a framework-dependent method.
#
# Alternatively, if a use case calls for, we may also use a lower isolation level
# but lock tables explicitly, either using framework-dependent API, or execute
# "select table for update" statements directly.
connectionIsolation: TRANSACTION_SERIALIZABLE
# Whether to log all SQL queries to App Engine logs. Overridable at runtime.
logSqlQueries: false
# Connection pool configurations.
hikariConnectionTimeout: 20000
hikariMinimumIdle: 0
hikariMaximumPoolSize: 20
hikariIdleTimeout: 300000
cloudDns:
# Set both properties to null in Production.
# The root url for the Cloud DNS API. Set this to a non-null value to

View file

@ -0,0 +1,67 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence;
import static google.registry.config.RegistryConfig.getHibernateConnectionIsolation;
import static google.registry.config.RegistryConfig.getHibernateHikariConnectionTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize;
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;
import com.google.common.collect.ImmutableMap;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.hibernate.cfg.Environment;
/** Factory class to provide {@link EntityManagerFactory} instance. */
public class EntityManagerFactoryProvider {
// This name must be the same as the one defined in persistence.xml.
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout";
public static final String HIKARI_MINIMUM_IDLE = "hibernate.hikari.minimumIdle";
public static final String HIKARI_MAXIMUM_POOL_SIZE = "hibernate.hikari.maximumPoolSize";
public static final String HIKARI_IDLE_TIMEOUT = "hibernate.hikari.idleTimeout";
private static ImmutableMap<String, String> getDefaultProperties() {
ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
properties.put(Environment.DRIVER, "org.postgresql.Driver");
properties.put(
Environment.CONNECTION_PROVIDER,
"org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
// Whether to automatically validate and export schema DDL to the database when the
// SessionFactory is created. Setting it to 'none' to turn off the feature.
properties.put(Environment.HBM2DDL_AUTO, "none");
properties.put(Environment.ISOLATION, getHibernateConnectionIsolation());
properties.put(Environment.SHOW_SQL, getHibernateLogSqlQueries());
properties.put(HIKARI_CONNECTION_TIMEOUT, getHibernateHikariConnectionTimeout());
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
return properties.build();
}
/** Constructs the {@link EntityManagerFactory} instance. */
public static EntityManagerFactory create(String jdbcUrl, String username, String password) {
ImmutableMap.Builder<String, String> properties = ImmutableMap.builder();
properties.putAll(getDefaultProperties());
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
return Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties.build());
}
}

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="nomulus" transaction-type="RESOURCE_LOCAL">
<description>
Persistence unit for the Nomulus Cloud SQL database.
</description>
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--
All JPA entities must be enumerated here. JPA does not support auto detection.
Note that Hibernate's auto detection functionality (hibernate.archive.autodection)
does not meet our needs. It only scans archives, not the 'classes' folders. So we
are left with two options:
* Move tests to another (sub)project. This is not a big problem, but feels unnatural.
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
-->
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.schema.tmch.ClaimsList</class>
<!-- TODO(weiminyu): check out application-layer validation. -->
<validation-mode>NONE</validation-mode>
</persistence-unit>
</persistence>

View file

@ -0,0 +1,55 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence;
import static com.google.common.truth.Truth.assertThat;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.testcontainers.containers.PostgreSQLContainer;
/** Unit tests for {@link EntityManagerFactoryProvider}. */
@RunWith(JUnit4.class)
public class EntityManagerFactoryProviderTest {
@Rule public PostgreSQLContainer database = new PostgreSQLContainer();
private EntityManagerFactory emf;
@Before
public void init() {
emf =
EntityManagerFactoryProvider.create(
database.getJdbcUrl(), database.getUsername(), database.getPassword());
}
@After
public void destroy() {
emf.close();
emf = null;
}
@Test
public void testConnectToDatabase_success() {
EntityManager em = emf.createEntityManager();
assertThat(em.isOpen()).isTrue();
em.close();
}
}

View file

@ -115,7 +115,7 @@ ext {
'org.hamcrest:hamcrest-all:1.3',
'org.hamcrest:hamcrest-core:1.3',
'org.hamcrest:hamcrest-library:1.3',
'org.hibernate:hibernate-core:5.4.4.Final',
'org.hibernate:hibernate-hikaricp:5.4.4.Final',
'org.joda:joda-money:0.10.0',
'org.json:json:20160810',
'org.mockito:mockito-core:2.25.0',
@ -125,8 +125,8 @@ ext {
'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59',
'org.seleniumhq.selenium:selenium-java:3.141.59',
'org.seleniumhq.selenium:selenium-remote-driver:3.141.59',
'org.testcontainers:postgresql:1.11.3',
'org.testcontainers:selenium:1.10.7',
'org.testcontainers:postgresql:1.8.3',
'org.yaml:snakeyaml:1.17',
'xerces:xmlParserAPIs:2.6.2',
'xpp3:xpp3:1.1.4c'