/*
 * Copyright 2002-2018 the original author or authors.
 *
 * 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 org.springframework.jdbc.datasource.embedded;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.UUID;
import java.util.logging.Logger;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Factory for creating an EmbeddedDatabase instance.

Callers are guaranteed that the returned database has been fully initialized and populated.

The factory can be configured as follows:

After configuring the factory, call getDatabase() to obtain a reference to the EmbeddedDatabase instance.

Author:Keith Donald, Juergen Hoeller, Sam Brannen
Since:3.0
/** * Factory for creating an {@link EmbeddedDatabase} instance. * * <p>Callers are guaranteed that the returned database has been fully * initialized and populated. * * <p>The factory can be configured as follows: * <ul> * <li>Call {@link #generateUniqueDatabaseName} to set a unique, random name * for the database. * <li>Call {@link #setDatabaseName} to set an explicit name for the database. * <li>Call {@link #setDatabaseType} to set the database type if you wish to * use one of the supported types. * <li>Call {@link #setDatabaseConfigurer} to configure support for a custom * embedded database type. * <li>Call {@link #setDatabasePopulator} to change the algorithm used to * populate the database. * <li>Call {@link #setDataSourceFactory} to change the type of * {@link DataSource} used to connect to the database. * </ul> * * <p>After configuring the factory, call {@link #getDatabase()} to obtain * a reference to the {@link EmbeddedDatabase} instance. * * @author Keith Donald * @author Juergen Hoeller * @author Sam Brannen * @since 3.0 */
public class EmbeddedDatabaseFactory {
Default name for an embedded database: "testdb".
/** * Default name for an embedded database: {@value}. */
public static final String DEFAULT_DATABASE_NAME = "testdb"; private static final Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class); private boolean generateUniqueDatabaseName = false; private String databaseName = DEFAULT_DATABASE_NAME; private DataSourceFactory dataSourceFactory = new SimpleDriverDataSourceFactory(); @Nullable private EmbeddedDatabaseConfigurer databaseConfigurer; @Nullable private DatabasePopulator databasePopulator; @Nullable private DataSource dataSource;
Set the generateUniqueDatabaseName flag to enable or disable generation of a pseudo-random unique ID to be used as the database name.

Setting this flag to true overrides any explicit name set via setDatabaseName.

See Also:
Since:4.2
/** * Set the {@code generateUniqueDatabaseName} flag to enable or disable * generation of a pseudo-random unique ID to be used as the database name. * <p>Setting this flag to {@code true} overrides any explicit name set * via {@link #setDatabaseName}. * @since 4.2 * @see #setDatabaseName */
public void setGenerateUniqueDatabaseName(boolean generateUniqueDatabaseName) { this.generateUniqueDatabaseName = generateUniqueDatabaseName; }
Set the name of the database.

Defaults to "testdb".

Will be overridden if the generateUniqueDatabaseName flag has been set to true.

Params:
  • databaseName – name of the embedded database
See Also:
/** * Set the name of the database. * <p>Defaults to {@value #DEFAULT_DATABASE_NAME}. * <p>Will be overridden if the {@code generateUniqueDatabaseName} flag * has been set to {@code true}. * @param databaseName name of the embedded database * @see #setGenerateUniqueDatabaseName */
public void setDatabaseName(String databaseName) { Assert.hasText(databaseName, "Database name is required"); this.databaseName = databaseName; }
Set the factory to use to create the DataSource instance that connects to the embedded database.

Defaults to SimpleDriverDataSourceFactory.

/** * Set the factory to use to create the {@link DataSource} instance that * connects to the embedded database. * <p>Defaults to {@link SimpleDriverDataSourceFactory}. */
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) { Assert.notNull(dataSourceFactory, "DataSourceFactory is required"); this.dataSourceFactory = dataSourceFactory; }
Set the type of embedded database to use.

Call this when you wish to configure one of the pre-supported types.

Defaults to HSQL.

Params:
  • type – the database type
/** * Set the type of embedded database to use. * <p>Call this when you wish to configure one of the pre-supported types. * <p>Defaults to HSQL. * @param type the database type */
public void setDatabaseType(EmbeddedDatabaseType type) { this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type); }
Set the strategy that will be used to configure the embedded database instance.

Call this when you wish to use an embedded database type not already supported.

/** * Set the strategy that will be used to configure the embedded database instance. * <p>Call this when you wish to use an embedded database type not already supported. */
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) { this.databaseConfigurer = configurer; }
Set the strategy that will be used to initialize or populate the embedded database.

Defaults to null.

/** * Set the strategy that will be used to initialize or populate the embedded * database. * <p>Defaults to {@code null}. */
public void setDatabasePopulator(DatabasePopulator populator) { this.databasePopulator = populator; }
Factory method that returns the embedded database instance, which is also a DataSource.
/** * Factory method that returns the {@linkplain EmbeddedDatabase embedded database} * instance, which is also a {@link DataSource}. */
public EmbeddedDatabase getDatabase() { if (this.dataSource == null) { initDatabase(); } return new EmbeddedDataSourceProxy(this.dataSource); }
Hook to initialize the embedded database.

If the generateUniqueDatabaseName flag has been set to true, the current value of the database name will be overridden with an auto-generated name.

Subclasses may call this method to force initialization; however, this method should only be invoked once.

After calling this method, getDataSource() returns the DataSource providing connectivity to the database.

/** * Hook to initialize the embedded database. * <p>If the {@code generateUniqueDatabaseName} flag has been set to {@code true}, * the current value of the {@linkplain #setDatabaseName database name} will * be overridden with an auto-generated name. * <p>Subclasses may call this method to force initialization; however, * this method should only be invoked once. * <p>After calling this method, {@link #getDataSource()} returns the * {@link DataSource} providing connectivity to the database. */
protected void initDatabase() { if (this.generateUniqueDatabaseName) { setDatabaseName(UUID.randomUUID().toString()); } // Create the embedded database first if (this.databaseConfigurer == null) { this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL); } this.databaseConfigurer.configureConnectionProperties( this.dataSourceFactory.getConnectionProperties(), this.databaseName); this.dataSource = this.dataSourceFactory.getDataSource(); if (logger.isInfoEnabled()) { if (this.dataSource instanceof SimpleDriverDataSource) { SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource; logger.info(String.format("Starting embedded database: url='%s', username='%s'", simpleDriverDataSource.getUrl(), simpleDriverDataSource.getUsername())); } else { logger.info(String.format("Starting embedded database '%s'", this.databaseName)); } } // Now populate the database if (this.databasePopulator != null) { try { DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource); } catch (RuntimeException ex) { // failed to populate, so leave it as not initialized shutdownDatabase(); throw ex; } } }
Hook to shutdown the embedded database. Subclasses may call this method to force shutdown.

After calling, getDataSource() returns null.

Does nothing if no embedded database has been initialized.

/** * Hook to shutdown the embedded database. Subclasses may call this method * to force shutdown. * <p>After calling, {@link #getDataSource()} returns {@code null}. * <p>Does nothing if no embedded database has been initialized. */
protected void shutdownDatabase() { if (this.dataSource != null) { if (logger.isInfoEnabled()) { if (this.dataSource instanceof SimpleDriverDataSource) { logger.info(String.format("Shutting down embedded database: url='%s'", ((SimpleDriverDataSource) this.dataSource).getUrl())); } else { logger.info(String.format("Shutting down embedded database '%s'", this.databaseName)); } } if (this.databaseConfigurer != null) { this.databaseConfigurer.shutdown(this.dataSource, this.databaseName); } this.dataSource = null; } }
Hook that gets the DataSource that provides the connectivity to the embedded database.

Returns null if the DataSource has not been initialized or if the database has been shut down. Subclasses may call this method to access the DataSource instance directly.

/** * Hook that gets the {@link DataSource} that provides the connectivity to the * embedded database. * <p>Returns {@code null} if the {@code DataSource} has not been initialized * or if the database has been shut down. Subclasses may call this method to * access the {@code DataSource} instance directly. */
@Nullable protected final DataSource getDataSource() { return this.dataSource; } private class EmbeddedDataSourceProxy implements EmbeddedDatabase { private final DataSource dataSource; public EmbeddedDataSourceProxy(DataSource dataSource) { this.dataSource = dataSource; } @Override public Connection getConnection() throws SQLException { return this.dataSource.getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return this.dataSource.getConnection(username, password); } @Override public PrintWriter getLogWriter() throws SQLException { return this.dataSource.getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { this.dataSource.setLogWriter(out); } @Override public int getLoginTimeout() throws SQLException { return this.dataSource.getLoginTimeout(); } @Override public void setLoginTimeout(int seconds) throws SQLException { this.dataSource.setLoginTimeout(seconds); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return this.dataSource.unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return this.dataSource.isWrapperFor(iface); } // getParentLogger() is required for JDBC 4.1 compatibility @Override public Logger getParentLogger() { return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); } @Override public void shutdown() { shutdownDatabase(); } } }