All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.susom.database.DatabaseProvider Maven / Gradle / Ivy

/*
 * Copyright 2014 The Board of Trustees of The Leland Stanford Junior University.
 *
 * 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 com.github.susom.database;

import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import javax.annotation.CheckReturnValue;
import javax.inject.Provider;
import javax.naming.Context;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zaxxer.hikari.HikariDataSource;

/**
 * This is a lazy provider for Database instances. It helps avoid allocating connection
 * or transaction resources until (or if) we actually need a Database. As a consequence
 * of this laziness, the underlying resources require explicit cleanup by calling either
 * commitAndClose() or rollbackAndClose().
 *
 * @author garricko
 */
public final class DatabaseProvider implements Provider, Supplier {
  private static final Logger log = LoggerFactory.getLogger(DatabaseProvider.class);
  private static final AtomicInteger poolNameCounter = new AtomicInteger(1);
  private DatabaseProvider delegateTo = null;
  private Provider connectionProvider;
  private boolean txStarted = false;
  private Connection connection = null;
  private Database database = null;
  private final Options options;

  public DatabaseProvider(Provider connectionProvider, Options options) {
    if (connectionProvider == null) {
      throw new IllegalArgumentException("Connection provider cannot be null");
    }
    this.connectionProvider = connectionProvider;
    this.options = options;
  }

  private DatabaseProvider(DatabaseProvider delegateTo) {
    this.delegateTo = delegateTo;
    this.options = delegateTo.options;
  }

  /**
   * Configure the database from the following properties read from the provided configuration:
   * 
*
   *   database.url=...       Database connect string (required)
   *   database.user=...      Authenticate as this user (optional if provided in url)
   *   database.password=...  User password (optional if user and password provided in
   *                          url; prompted on standard input if user is provided and
   *                          password is not)
   *   database.pool.size=... How many connections in the connection pool (default 10).
   *   database.driver.class  The driver to initialize with Class.forName(). This will
   *                          be guessed from the database.url if not provided.
   *   database.flavor        One of the enumerated values in {@link Flavor}. If this
   *                          is not provided the flavor will be guessed based on the
   *                          value for database.url, if possible.
   * 
* *

The database flavor will be guessed based on the URL.

* *

A database pool will be created using HikariCP.

* *

Be sure to retain a copy of the builder so you can call close() later to * destroy the pool. You will most likely want to register a JVM shutdown hook * to make sure this happens. See VertxServer.java in the demo directory for * an example of how to do this.

*/ @CheckReturnValue public static Builder pooledBuilder(Config config) { return fromPool(createPool(config)); } /** * Use an externally configured DataSource, Flavor, and optionally a shutdown hook. * The shutdown hook may be null if you don't want calls to Builder.close() to attempt * any shutdown. The DataSource and Flavor are mandatory. */ @CheckReturnValue public static Builder fromPool(Pool pool) { return new BuilderImpl(pool.poolShutdown, () -> { try { return pool.dataSource.getConnection(); } catch (Exception e) { throw new DatabaseException("Unable to obtain a connection from the DataSource", e); } }, new OptionsDefault(pool.flavor)); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. The url parameter will be inspected * to determine the Flavor for this database. */ @CheckReturnValue public static Builder fromDriverManager(String url) { return fromDriverManager(url, Flavor.fromJdbcUrl(url), null, null, null); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. * * @param flavor use this flavor rather than guessing based on the url */ @CheckReturnValue public static Builder fromDriverManager(String url, Flavor flavor) { return fromDriverManager(url, flavor, null, null, null); } public static Builder fromDriverManager(Config config) { return fromDriverManager(config.getString("database.url"), config.getString("database.user"), config.getString("database.password")); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. The url parameter will be inspected * to determine the Flavor for this database. */ @CheckReturnValue public static Builder fromDriverManager(String url, Properties info) { return fromDriverManager(url, Flavor.fromJdbcUrl(url), info, null, null); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. * * @param flavor use this flavor rather than guessing based on the url */ @CheckReturnValue public static Builder fromDriverManager(String url, Flavor flavor, Properties info) { return fromDriverManager(url, flavor, info, null, null); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. The url parameter will be inspected * to determine the Flavor for this database. */ @CheckReturnValue public static Builder fromDriverManager(String url, String user, String password) { return fromDriverManager(url, Flavor.fromJdbcUrl(url), null, user, password); } /** * Builder method to create and initialize an instance of this class using * the JDBC standard DriverManager method. * * @param flavor use this flavor rather than guessing based on the url */ @CheckReturnValue public static Builder fromDriverManager(String url, Flavor flavor, String user, String password) { return fromDriverManager(url, flavor, null, user, password); } private static Builder fromDriverManager(final String url, Flavor flavor, final Properties info, final String user, final String password) { Options options = new OptionsDefault(flavor); // Make sure DriverManager can locate the driver try { DriverManager.getDriver(url); } catch (SQLException e) { try { Class.forName(Flavor.driverForJdbcUrl(url)); } catch (ClassNotFoundException e1) { throw new DatabaseException("Couldn't locate JDBC driver - try setting -Djdbc.drivers=some.Driver", e1); } } return new BuilderImpl(null, () -> { try { if (info != null) { return DriverManager.getConnection(url, info); } else if (user != null) { return DriverManager.getConnection(url, user, password); } return DriverManager.getConnection(url); } catch (Exception e) { throw new DatabaseException("Unable to obtain a connection from DriverManager", e); } }, options); } /** * Builder method to create and initialize an instance of this class using * a JNDI resource. To use this method you must explicitly indicate what * Flavor of database we are dealing with. */ @CheckReturnValue public static Builder fromJndi(final Context context, final String lookupKey, Flavor flavor) { Options options = new OptionsDefault(flavor); return new BuilderImpl(null, new Provider() { @Override public Connection get() { DataSource ds; try { ds = (DataSource) context.lookup(lookupKey); } catch (Exception e) { throw new DatabaseException("Unable to locate the DataSource in JNDI using key " + lookupKey, e); } try { return ds.getConnection(); } catch (Exception e) { throw new DatabaseException("Unable to obtain a connection from JNDI DataSource " + lookupKey, e); } } }, options); } /** * Configure the database from up to five properties read from a file: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
*

This will use the JVM default character encoding to read the property file.

* @param filename path to the properties file we will attempt to read * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromPropertyFile(String filename) { return fromPropertyFile(filename, Charset.defaultCharset().newDecoder()); } /** * Configure the database from up to five properties read from a file: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param filename path to the properties file we will attempt to read * @param decoder character encoding to use when reading the property file * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromPropertyFile(String filename, CharsetDecoder decoder) { Properties properties = new Properties(); if (filename != null && filename.length() > 0) { try { properties.load(new InputStreamReader(new FileInputStream(filename), decoder)); } catch (Exception e) { throw new DatabaseException("Unable to read properties file: " + filename, e); } } return fromProperties(properties, "", true); } /** * Configure the database from up to five properties read from a file: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
*

This will use the JVM default character encoding to read the property file.

* @param filename path to the properties file we will attempt to read * @param propertyPrefix if this is null or empty the properties above will be read; * if a value is provided it will be prefixed to each property * (exactly, so if you want to use "my.database.url" you must * pass "my." as the prefix) * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromPropertyFile(String filename, String propertyPrefix) { return fromPropertyFile(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); } /** * Configure the database from up to five properties read from a file: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param filename path to the properties file we will attempt to read * @param propertyPrefix if this is null or empty the properties above will be read; * if a value is provided it will be prefixed to each property * (exactly, so if you want to use "my.database.url" you must * pass "my." as the prefix) * @param decoder character encoding to use when reading the property file * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromPropertyFile(String filename, String propertyPrefix, CharsetDecoder decoder) { Properties properties = new Properties(); if (filename != null && filename.length() > 0) { try { properties.load(new InputStreamReader(new FileInputStream(filename), decoder)); } catch (Exception e) { throw new DatabaseException("Unable to read properties file: " + filename, e); } } return fromProperties(properties, propertyPrefix, true); } /** * Configure the database from up to five properties read from the provided properties: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param properties properties will be read from here * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromProperties(Properties properties) { return fromProperties(properties, "", false); } /** * Configure the database from up to five properties read from the provided properties: *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param properties properties will be read from here * @param propertyPrefix if this is null or empty the properties above will be read; * if a value is provided it will be prefixed to each property * (exactly, so if you want to use "my.database.url" you must * pass "my." as the prefix) * @throws DatabaseException if the property file could not be read for any reason */ public static Builder fromProperties(Properties properties, String propertyPrefix) { return fromProperties(properties, propertyPrefix, false); } /** * Configure the database from up to five properties read from the specified * properties file, or from the system properties (system properties will take * precedence over the file): *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
*

This will use the JVM default character encoding to read the property file.

* @param filename path to the properties file we will attempt to read; if the file * cannot be read for any reason (e.g. does not exist) a debug level * log entry will be entered, but it will attempt to proceed using * solely the system properties */ public static Builder fromPropertyFileOrSystemProperties(String filename) { return fromPropertyFileOrSystemProperties(filename, Charset.defaultCharset().newDecoder()); } /** * Configure the database from up to five properties read from the specified * properties file, or from the system properties (system properties will take * precedence over the file): *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param filename path to the properties file we will attempt to read; if the file * cannot be read for any reason (e.g. does not exist) a debug level * log entry will be entered, but it will attempt to proceed using * solely the system properties * @param decoder character encoding to use when reading the property file */ public static Builder fromPropertyFileOrSystemProperties(String filename, CharsetDecoder decoder) { Properties properties = new Properties(); if (filename != null && filename.length() > 0) { try { properties.load(new InputStreamReader(new FileInputStream(filename), decoder)); } catch (Exception e) { log.debug("Trying system properties - unable to read properties file: " + filename); } } return fromProperties(properties, "", true); } /** * Configure the database from up to five properties read from the specified * properties file, or from the system properties (system properties will take * precedence over the file): *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
*

This will use the JVM default character encoding to read the property file.

* @param filename path to the properties file we will attempt to read; if the file * cannot be read for any reason (e.g. does not exist) a debug level * log entry will be entered, but it will attempt to proceed using * solely the system properties * @param propertyPrefix if this is null or empty the properties above will be read; * if a value is provided it will be prefixed to each property * (exactly, so if you want to use "my.database.url" you must * pass "my." as the prefix) */ public static Builder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix) { return fromPropertyFileOrSystemProperties(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); } /** * Configure the database from up to five properties read from the specified * properties file, or from the system properties (system properties will take * precedence over the file): *
*
   *   database.url=...      Database connect string (required)
   *   database.user=...     Authenticate as this user (optional if provided in url)
   *   database.password=... User password (optional if user and password provided in
   *                         url; prompted on standard input if user is provided and
   *                         password is not)
   *   database.flavor=...   What kind of database it is (optional, will guess based
   *                         on the url if this is not provided)
   *   database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                         guess based on the flavor if this is not provided)
   * 
* @param filename path to the properties file we will attempt to read; if the file * cannot be read for any reason (e.g. does not exist) a debug level * log entry will be entered, but it will attempt to proceed using * solely the system properties * @param propertyPrefix if this is null or empty the properties above will be read; * if a value is provided it will be prefixed to each property * (exactly, so if you want to use "my.database.url" you must * pass "my." as the prefix) * @param decoder character encoding to use when reading the property file */ public static Builder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix, CharsetDecoder decoder) { Properties properties = new Properties(); if (filename != null && filename.length() > 0) { try { properties.load(new InputStreamReader(new FileInputStream(filename), decoder)); } catch (Exception e) { log.debug("Trying system properties - unable to read properties file: " + filename); } } return fromProperties(properties, propertyPrefix, true); } /** * Configure the database from up to five system properties: *
*
   *   -Ddatabase.url=...      Database connect string (required)
   *   -Ddatabase.user=...     Authenticate as this user (optional if provided in url)
   *   -Ddatabase.password=... User password (optional if user and password provided in
   *                           url; prompted on standard input if user is provided and
   *                           password is not)
   *   -Ddatabase.flavor=...   What kind of database it is (optional, will guess based
   *                           on the url if this is not provided)
   *   -Ddatabase.driver=...   The Java class of the JDBC driver to load (optional, will
   *                           guess based on the flavor if this is not provided)
   * 
*/ @CheckReturnValue public static Builder fromSystemProperties() { return fromProperties(null, "", true); } /** * Configure the database from up to five system properties: *
*
   *   -D{prefix}database.url=...      Database connect string (required)
   *   -D{prefix}database.user=...     Authenticate as this user (optional if provided in url)
   *   -D{prefix}database.password=... User password (optional if user and password provided in
   *                                   url; prompted on standard input if user is provided and
   *                                   password is not)
   *   -D{prefix}database.flavor=...   What kind of database it is (optional, will guess based
   *                                   on the url if this is not provided)
   *   -D{prefix}database.driver=...   The Java class of the JDBC driver to load (optional, will
   *                                   guess based on the flavor if this is not provided)
   * 
* @param propertyPrefix a prefix to attach to each system property - be sure to include the * dot if desired (e.g. "mydb." for properties like -Dmydb.database.url) */ @CheckReturnValue public static Builder fromSystemProperties(String propertyPrefix) { return fromProperties(null, propertyPrefix, true); } private static Builder fromProperties(Properties properties, String propertyPrefix, boolean useSystemProperties) { if (propertyPrefix == null) { propertyPrefix = ""; } String driver; String flavorStr; String url; String user; String password; if (useSystemProperties) { if (properties == null) { properties = new Properties(); } driver = System.getProperty(propertyPrefix + "database.driver", properties.getProperty(propertyPrefix + "database.driver")); flavorStr = System.getProperty(propertyPrefix + "database.flavor", properties.getProperty(propertyPrefix + "database.flavor")); url = System.getProperty(propertyPrefix + "database.url", properties.getProperty(propertyPrefix + "database.url")); user = System.getProperty(propertyPrefix + "database.user", properties.getProperty(propertyPrefix + "database.user")); password = System.getProperty(propertyPrefix + "database.password", properties.getProperty(propertyPrefix + "database.password")); } else { if (properties == null) { throw new DatabaseException("No properties were provided"); } driver = properties.getProperty(propertyPrefix + "database.driver"); flavorStr = properties.getProperty(propertyPrefix + "database.flavor"); url = properties.getProperty(propertyPrefix + "database.url"); user = properties.getProperty(propertyPrefix + "database.user"); password = properties.getProperty(propertyPrefix + "database.password"); } if (url == null) { throw new DatabaseException("You must use -D" + propertyPrefix + "database.url=..."); } if (user != null && password == null) { System.out.println("Enter database password for user " + user + ":"); byte[] input = new byte[256]; try { int bytesRead = System.in.read(input); password = new String(input, 0, bytesRead-1, Charset.defaultCharset()); } catch (IOException e) { throw new DatabaseException("Error reading password from standard input", e); } } Flavor flavor; if (flavorStr != null) { flavor = Flavor.valueOf(flavorStr); } else { flavor = Flavor.fromJdbcUrl(url); } if (driver == null) { if (flavor == Flavor.oracle) { driver = "oracle.jdbc.OracleDriver"; } else if (flavor == Flavor.postgresql) { driver = "org.postgresql.Driver"; } else if (flavor == Flavor.derby) { driver = "org.apache.derby.jdbc.EmbeddedDriver"; } } if (driver != null) { try { Class.forName(driver).newInstance(); } catch (Exception e) { throw new DatabaseException("Unable to load JDBC driver: " + driver, e); } } if (user == null) { return fromDriverManager(url, flavor); } else { return fromDriverManager(url, flavor, user, password); } } /** * You most likely want to use {@link com.github.susom.database.DatabaseProvider.Builder#transact(DbRun)} * instead! * * @deprecated Replace with {@link #transact(DbCodeTx)} and a call to * {@link Transaction#setRollbackOnError(boolean)} * providing a value of {@code false}. */ @Deprecated public void transact(DbRun run) { boolean complete = false; try { run.run(this); complete = true; } catch (ThreadDeath|DatabaseException t) { throw t; } catch (Throwable t) { throw new DatabaseException("Error during transaction", t); } finally { if (run.isRollbackOnly() || (run.isRollbackOnError() && !complete)) { rollbackAndClose(); } else { commitAndClose(); } } } public void transact(final DbCode code) { boolean complete = false; try { code.run(this); complete = true; } catch (ThreadDeath|DatabaseException t) { throw t; } catch (Throwable t) { throw new DatabaseException("Error during transaction", t); } finally { if (!complete) { rollbackAndClose(); } else { commitAndClose(); } } } public void transact(final DbCodeTx code) { Transaction tx = new TransactionImpl(); tx.setRollbackOnError(true); tx.setRollbackOnly(false); boolean complete = false; try { code.run(this, tx); complete = true; } catch (ThreadDeath|DatabaseException t) { throw t; } catch (Throwable t) { throw new DatabaseException("Error during transaction", t); } finally { if ((!complete && tx.isRollbackOnError()) || tx.isRollbackOnly()) { rollbackAndClose(); } else { commitAndClose(); } } } /** * This builder is immutable, so setting various options does not affect * the previous instance. This is intended to make it safe to pass builders * around without risk someone will reconfigure it. */ public interface Builder { @CheckReturnValue Builder withOptions(OptionsOverride options); /** * Enable logging of parameter values along with the SQL. */ @CheckReturnValue Builder withSqlParameterLogging(); /** * Include SQL in exception messages. This will also include parameters in the * exception messages if SQL parameter logging is enabled. This is handy for * development, but be careful as this is an information disclosure risk, * dependent on how the exception are caught and handled. */ @CheckReturnValue Builder withSqlInExceptionMessages(); /** * Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is * useful for testing purposes as you can use OptionsOverride to provide your * own system clock that will be used for time travel. */ @CheckReturnValue Builder withDatePerAppOnly(); /** * Allow provided Database instances to explicitly control transactions using the * commitNow() and rollbackNow() methods. Otherwise calling those methods would * throw an exception. */ @CheckReturnValue Builder withTransactionControl(); /** * This can be useful when testing code, as it can pretend to use transactions, * while giving you control over whether it actually commits or rolls back. */ @CheckReturnValue Builder withTransactionControlSilentlyIgnored(); /** * Allow direct access to the underlying database connection. Normally this is * not allowed, and is a bad idea, but it can be helpful when migrating from * legacy code that works with raw JDBC. */ @CheckReturnValue Builder withConnectionAccess(); /** * WARNING: You should try to avoid using this method. If you use it more * that once or twice in your entire codebase you are probably doing * something wrong. * *

If you use this method you are responsible for managing * the transaction and commit/rollback/close.

*/ @CheckReturnValue DatabaseProvider create(); /** * This method runs the provided block of code, and commits the transaction * after either successful completion of the block or an exceptional condition. * * @deprecated Replace with {@link #transact(DbCodeTx)} and a call to * {@link Transaction#setRollbackOnError(boolean)} * providing a value of {@code false}. */ @Deprecated void transact(DbRun run); /** * This is a convenience method to eliminate the need for explicitly * managing the resources (and error handling) for this class. After * the run block is complete commit() will be called unless either the * {@link DbCode#run(Supplier)} method threw a {@link Throwable}. * * @param code the code you want to run as a transaction with a Database * @see {@link #transact(DbCodeTx)} if you want to explicitly manage * when the transaction commits or rolls back */ void transact(DbCode code); /** * This is a convenience method to eliminate the need for explicitly * managing the resources (and error handling) for this class. After * the run block is complete commit() will be called unless either the * {@link DbCodeTx#run(Supplier, Transaction)} method threw a {@link Throwable} * while {@link Transaction#isRollbackOnError()} returns true, or * {@link Transaction#isRollbackOnly()} returns a true value. * * @param code the code you want to run as a transaction with a Database */ void transact(DbCodeTx code); void close(); } private static class BuilderImpl implements Builder { private Closeable pool; private final Provider connectionProvider; private final Options options; private BuilderImpl(Closeable pool, Provider connectionProvider, Options options) { this.pool = pool; this.connectionProvider = connectionProvider; this.options = options; } @Override public Builder withOptions(OptionsOverride options) { return new BuilderImpl(pool, connectionProvider, options.withParent(this.options)); } @Override public Builder withSqlParameterLogging() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean isLogParameters() { return true; } }.withParent(this.options)); } @Override public Builder withSqlInExceptionMessages() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean isDetailedExceptions() { return true; } }.withParent(this.options)); } @Override public Builder withDatePerAppOnly() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean useDatePerAppOnly() { return true; } }.withParent(this.options)); } @Override public Builder withTransactionControl() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean allowTransactionControl() { return true; } }.withParent(this.options)); } @Override public Builder withTransactionControlSilentlyIgnored() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean ignoreTransactionControl() { return true; } }.withParent(this.options)); } @Override public Builder withConnectionAccess() { return new BuilderImpl(pool, connectionProvider, new OptionsOverride() { @Override public boolean allowConnectionAccess() { return true; } }.withParent(this.options)); } @Override public DatabaseProvider create() { return new DatabaseProvider(connectionProvider, options); } @Override public void transact(DbRun run) { create().transact(run); } @Override public void transact(DbCode tx) { create().transact(tx); } @Override public void transact(DbCodeTx tx) { create().transact(tx); } public void close() { if (pool != null) { try { pool.close(); } catch (IOException e) { log.warn("Unable to close connection pool", e); } pool = null; } } } public Database get() { if (delegateTo != null) { return delegateTo.get(); } if (database != null) { return database; } if (connectionProvider == null) { throw new DatabaseException("Called get() on a DatabaseProvider after close()"); } Metric metric = new Metric(log.isDebugEnabled()); try { connection = connectionProvider.get(); txStarted = true; metric.checkpoint("getConn"); try { // Generally check autocommit before setting because databases like // Oracle can get grumpy if you change it (depending on how your connection // has been initialized by say JNDI), but PostgresSQL seems to // require calling setAutoCommit() every time // Commenting as the Oracle 12.1.0.2 driver now seems to require this as well // (the getAutoCommit() call here will return false and then it will blow up // on commit complaining you can't commit with autocommit on) // if (options.flavor() == Flavor.postgresql || !connection.getAutoCommit()) { connection.setAutoCommit(false); metric.checkpoint("setAutoCommit"); // } else { // metric.checkpoint("checkAutoCommit"); // } } catch (SQLException e) { throw new DatabaseException("Unable to check/set autoCommit for the connection", e); } database = new DatabaseImpl(connection, options); metric.checkpoint("dbInit"); } catch (RuntimeException e) { metric.checkpoint("fail"); throw e; } finally { metric.done(); if (log.isDebugEnabled()) { StringBuilder buf = new StringBuilder("Get ").append(options.flavor()).append(" database: "); metric.printMessage(buf); log.debug(buf.toString()); } } return database; } public Builder fakeBuilder() { return new Builder() { @Override public Builder withOptions(OptionsOverride optionsOverride) { return this; } @Override public Builder withSqlParameterLogging() { return this; } @Override public Builder withSqlInExceptionMessages() { return this; } @Override public Builder withDatePerAppOnly() { return this; } @Override public Builder withTransactionControl() { return this; } @Override public Builder withTransactionControlSilentlyIgnored() { return this; } @Override public Builder withConnectionAccess() { return this; } @Override public DatabaseProvider create() { return new DatabaseProvider(DatabaseProvider.this); } @Override public void transact(DbRun dbRun) { create().transact(dbRun); } @Override public void transact(DbCode tx) { create().transact(tx); } @Override public void transact(DbCodeTx tx) { create().transact(tx); } @Override public void close() { log.debug("Ignoring close call on fakeBuilder"); } }; } public void commitAndClose() { if (delegateTo != null) { log.debug("Ignoring commitAndClose() because this is a fake provider"); return; } if (txStarted) { try { connection.commit(); } catch (Exception e) { throw new DatabaseException("Unable to commit the transaction", e); } close(); } } public void rollbackAndClose() { if (delegateTo != null) { log.debug("Ignoring rollbackAndClose() because this is a fake provider"); return; } if (txStarted) { try { connection.rollback(); } catch (Exception e) { log.error("Unable to rollback the transaction", e); } close(); } } private void close() { try { connection.close(); } catch (Exception e) { log.error("Unable to close the database connection", e); } connection = null; database = null; txStarted = false; connectionProvider = null; } public static class Pool { public DataSource dataSource; public int size; public Flavor flavor; public Closeable poolShutdown; public Pool(DataSource dataSource, int size, Flavor flavor, Closeable poolShutdown) { this.dataSource = dataSource; this.size = size; this.flavor = flavor; this.poolShutdown = poolShutdown; } } public static Pool createPool(Config config) { String url = config.getString("database.url"); if (url == null) { throw new DatabaseException("You must provide database.url"); } HikariDataSource ds = new HikariDataSource(); // If we don't provide a pool name it will automatically generate one, but // the way it does that requires PropertyPermission("*", "read,write") and // will fail if the security sandbox is enabled ds.setPoolName(config.getString("database.pool.name", "HikariPool-" + poolNameCounter.getAndAdd(1))); ds.setJdbcUrl(url); String driverClassName = config.getString("database.driver.class", Flavor.driverForJdbcUrl(url)); ds.setDriverClassName(driverClassName); ds.setUsername(config.getString("database.user")); ds.setPassword(config.getString("database.password")); int poolSize = config.getInteger("database.pool.size", 10); ds.setMaximumPoolSize(poolSize); ds.setAutoCommit(false); Flavor flavor; String flavorString = config.getString("database.flavor"); if (flavorString != null) { flavor = Flavor.valueOf(flavorString); } else { flavor = Flavor.fromJdbcUrl(url); } log.debug("Created '" + flavor + "' connection pool of size " + poolSize + " using driver " + driverClassName); return new Pool(ds, poolSize, flavor, ds); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy