/* * MIT License * * Copyright (c) 2019 Choko ([email protected]) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package org.curioswitch.common.server.framework.database; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.typesafe.config.Config; import com.typesafe.config.ConfigBeanFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory; import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; import dagger.multibindings.IntoSet; import java.io.Closeable; import java.util.Set; import java.util.concurrent.Executors; import javax.inject.Singleton; import javax.sql.DataSource; import org.curioswitch.common.server.framework.ApplicationModule; import org.curioswitch.common.server.framework.armeria.CurrentRequestContextForwardingExecutorService; import org.curioswitch.common.server.framework.config.DatabaseConfig; import org.curioswitch.common.server.framework.config.ModifiableDatabaseConfig; import org.curioswitch.common.server.framework.inject.CloseOnStop; import org.curioswitch.common.server.framework.inject.EagerInit; import org.jooq.Configuration; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.conf.Settings; import org.jooq.impl.DSL; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; @Module(includes = ApplicationModule.class) public abstract class DatabaseModule { @Provides @Singleton static DatabaseConfig dbConfig(Config config) { return ConfigBeanFactory.create(config.getConfig("database"), ModifiableDatabaseConfig.class) .toImmutable(); } @Provides @ForDatabase @Singleton static ListeningExecutorService dbExecutor() { return new CurrentRequestContextForwardingExecutorService( Executors.newFixedThreadPool( 20, new ThreadFactoryBuilder().setNameFormat("dbio-%d").setDaemon(true).build())); } @Provides @Singleton static DataSource dataSource(DatabaseConfig config) { HikariConfig hikari = new HikariConfig(); hikari.setJdbcUrl(config.getJdbcUrl()); hikari.setUsername(config.getUsername()); hikari.setPassword(config.getPassword()); hikari.addDataSourceProperty("logger", "com.mysql.cj.log.Slf4JLogger"); hikari.addDataSourceProperty("maxLifetime", config.getConnectionMaxLifetime().getSeconds()); hikari.addDataSourceProperty("cachePrepStmts", true); hikari.addDataSourceProperty("prepStmtCacheSize", 250); hikari.addDataSourceProperty("prepStmtCacheSqlLimit", 2048); hikari.addDataSourceProperty("useServerPrepStmts", true); hikari.addDataSourceProperty("useLocalSessionState", true); hikari.addDataSourceProperty("useLocalTransactionState", true); hikari.addDataSourceProperty("rewriteBatchedStatements", true); hikari.addDataSourceProperty("cacheResultSetMetadata", true); hikari.addDataSourceProperty("cacheServerConfiguration", true); hikari.addDataSourceProperty("elideSetAutoCommits", true); hikari.addDataSourceProperty("maintainTimeStats", false); hikari.addDataSourceProperty("queryInterceptors", "brave.mysql8.TracingQueryInterceptor"); hikari.addDataSourceProperty( "exceptionInterceptors", "brave.mysql8.TracingExceptionInterceptor"); hikari.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory()); if (!config.getLeakDetectionThreshold().isZero()) { hikari.addDataSourceProperty( "leakDetectionThreshold", config.getLeakDetectionThreshold().getSeconds()); } hikari.addDataSourceProperty("connectTimeout", config.getConnectTimeout().getSeconds()); hikari.addDataSourceProperty("socketTimeout", config.getSocketTimeout().getSeconds()); return new HikariDataSource(hikari); } @Provides @Singleton static DSLContext dbContext( DataSource dataSource, DatabaseConfig config, @ForDatabase ListeningExecutorService dbExecutor) { Configuration configuration = new DefaultConfiguration() .set(dbExecutor) .set(SQLDialect.MYSQL) .set(new Settings().withRenderSchema(false)) .set(new DataSourceConnectionProvider(dataSource)) .set(DatabaseUtil.sfmRecordMapperProvider()); if (config.getLogQueries()) { configuration.set(new QueryLogger()); } DSLContext ctx = DSL.using(configuration); // Eagerly trigger JOOQ classinit for better startup performance. ctx.select().from("curio_server_framework_init").getSQL(); return ctx; } @Binds @EagerInit @IntoSet abstract Object init(DSLContext dslContext); @Provides @ElementsIntoSet @CloseOnStop static Set<Closeable> close( DataSource dataSource, @ForDatabase ListeningExecutorService executor) { return ImmutableSet.of((HikariDataSource) dataSource, executor::shutdownNow); } private DatabaseModule() {} }