Skip to content

Commit

Permalink
Add support for Test Containers
Browse files Browse the repository at this point in the history
  • Loading branch information
vladmihalcea committed Aug 15, 2022
1 parent 7a78103 commit 14bcaaf
Show file tree
Hide file tree
Showing 52 changed files with 1,280 additions and 523 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,16 @@ Or, if you prefer reading books, you are going to love my [High-Performance Java

#### Contributing Guide

The project uses [Maven Toolchains](https://maven.apache.org/guides/mini/guide-using-toolchains.html) as different modules are compiled and tested using different Java versions. Hibernate Types 6 requires Java 17 while Hibernate Types 4 compiles with Java 1.6.
The project uses [Maven Toolchains](https://maven.apache.org/guides/mini/guide-using-toolchains.html) as different modules are compiled and tested using different Java versions. Hibernate Types 6 requires Java 17 while the other modules are compiled with either Java 1.8 or 1.6.

To see how to configure Maven Toolchains, check out [this article](https://vladmihalcea.com/maven-and-java-multi-version-modules/).

The project uses various database systems for integration testing, and you can configure the JDBC connection settings using the
`DatasourceProvider` instances (e.g., `PostgreSQLDataSourceProvider`).
`DatasourceProvider` instances (e.g., `PostgreSQLDataSourceProvider`), and the project uses Testcontainers to bootstrap a Docker container
with the required Oracle, SQL Server, PostgreSQL, or MySQL instance on demand.

> If you are a regular contributor, it's advisable to set up the required database locally or use the Docker Compose configuration provided in the `docker` folder,
> as bootstrapping the containers on demand is slower, and your tests are going to take longer to run.
If you want to fix an issue or add support for a new feature, please provide the associated integration test case that proves the improvement is working as expected.

4 changes: 2 additions & 2 deletions hibernate-types-4/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<version>${jackson-databind.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
Expand Down Expand Up @@ -77,7 +77,7 @@
<hibernate.version>4.2.21.Final</hibernate.version>
<postgresql.version>9.4-1202-jdbc4</postgresql.version>

<jackson.version>2.7.9.6</jackson.version>
<jackson-databind.version>2.12.6.1</jackson-databind.version>
<guava.version>12.0</guava.version>
<moneta.version>1.4.2</moneta.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.vladmihalcea.hibernate.util.providers;

import org.testcontainers.containers.JdbcDatabaseContainer;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
* @author Vlad Mihalcea
*/
public abstract class AbstractContainerDataSourceProvider implements DataSourceProvider {

@Override
public DataSource dataSource() {
DataSource dataSource = newDataSource();
Connection connection = null;
try {
connection = dataSource.getConnection();
return dataSource;
} catch (SQLException e) {
Database database = database();
if(database.getContainer() == null) {
database.initContainer(username(), password());
}
return newDataSource();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException ignore) {
}
}
}
}

@Override
public String url() {
JdbcDatabaseContainer container = database().getContainer();
return container != null ?
container.getJdbcUrl() :
defaultJdbcUrl();
}

protected abstract String defaultJdbcUrl();

protected abstract DataSource newDataSource();
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,89 @@
package com.vladmihalcea.hibernate.util.providers;

import org.testcontainers.containers.*;

import java.util.Collections;

/**
* @author Vlad Mihalcea
*/
public enum Database {
HSQLDB,
H2,
POSTGRESQL,
ORACLE,
MYSQL,
SQLSERVER,
COCKROACHDB
POSTGRESQL {
@Override
protected JdbcDatabaseContainer newJdbcDatabaseContainer() {
return new PostgreSQLContainer("postgres:13.7");
}
},
ORACLE {
@Override
protected JdbcDatabaseContainer newJdbcDatabaseContainer() {
return new OracleContainer("gvenzl/oracle-xe:21.3.0-slim");
}

@Override
protected boolean supportsDatabaseName() {
return false;
}
},
MYSQL {
@Override
protected JdbcDatabaseContainer newJdbcDatabaseContainer() {
return new MySQLContainer("mysql:8.0");
}
},
SQLSERVER {
@Override
protected JdbcDatabaseContainer newJdbcDatabaseContainer() {
return new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2019-latest");
}

@Override
protected boolean supportsDatabaseName() {
return false;
}

@Override
protected boolean supportsCredentials() {
return false;
}
},
COCKROACHDB;

private JdbcDatabaseContainer container;

public JdbcDatabaseContainer getContainer() {
return container;
}

public void initContainer(String username, String password) {
container = (JdbcDatabaseContainer) newJdbcDatabaseContainer()
.withEnv(Collections.singletonMap("ACCEPT_EULA", "Y"));
if(supportsDatabaseName()) {
container.withDatabaseName("high-performance-java-persistence");
}
if(supportsCredentials()) {
container.withUsername(username).withPassword(password);
}
container.withTmpFs(Collections.singletonMap("/testtmpfs", "rw"));
container.start();
}

protected JdbcDatabaseContainer newJdbcDatabaseContainer() {
throw new UnsupportedOperationException(
String.format(
"The [%s] database was not configured to use Testcontainers!",
name()
)
);
}

protected boolean supportsDatabaseName() {
return true;
}

protected boolean supportsCredentials() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* @author Vlad Mihalcea
*/
public class MySQLDataSourceProvider implements DataSourceProvider {
public class MySQLDataSourceProvider extends AbstractContainerDataSourceProvider {

private boolean rewriteBatchedStatements = true;

Expand Down Expand Up @@ -76,19 +76,19 @@ public String hibernateDialect() {
}

@Override
public DataSource dataSource() {
protected String defaultJdbcUrl() {
return "jdbc:mysql://localhost/high_performance_java_persistence?useSSL=false";
}

protected DataSource newDataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setURL("jdbc:mysql://localhost/high_performance_java_persistence?useSSL=false&" +
"rewriteBatchedStatements=" + rewriteBatchedStatements +
"&cachePrepStmts=" + cachePrepStmts +
"&useServerPrepStmts=" + useServerPrepStmts +
"&useTimezone=" + useTimezone +
"&useJDBCCompliantTimezoneShift=" + useJDBCCompliantTimezoneShift +
"&useLegacyDatetimeCode=" + useLegacyDatetimeCode

);
dataSource.setUser("mysql");
dataSource.setPassword("admin");
dataSource.setURL(url());
dataSource.setUser(username());
dataSource.setPassword(password());
dataSource.setRewriteBatchedStatements(rewriteBatchedStatements);
dataSource.setCachePrepStmts(cachePrepStmts);
dataSource.setUseServerPrepStmts(useServerPrepStmts);

return dataSource;
}

Expand All @@ -101,22 +101,19 @@ public Class<? extends DataSource> dataSourceClassName() {
public Properties dataSourceProperties() {
Properties properties = new Properties();
properties.setProperty("url", url());
properties.setProperty("user", username());
properties.setProperty("password", password());
return properties;
}

@Override
public String url() {
return "jdbc:mysql://localhost/high_performance_java_persistence?user=mysql&password=admin";
}

@Override
public String username() {
return null;
return "mysql";
}

@Override
public String password() {
return null;
return "admin";
}

@Override
Expand All @@ -127,12 +124,12 @@ public Database database() {
@Override
public String toString() {
return "MySQLDataSourceProvider{" +
"rewriteBatchedStatements=" + rewriteBatchedStatements +
", cachePrepStmts=" + cachePrepStmts +
", useServerPrepStmts=" + useServerPrepStmts +
", useTimezone=" + useTimezone +
", useJDBCCompliantTimezoneShift=" + useJDBCCompliantTimezoneShift +
", useLegacyDatetimeCode=" + useLegacyDatetimeCode +
'}';
"rewriteBatchedStatements=" + rewriteBatchedStatements +
", cachePrepStmts=" + cachePrepStmts +
", useServerPrepStmts=" + useServerPrepStmts +
", useTimezone=" + useTimezone +
", useJDBCCompliantTimezoneShift=" + useJDBCCompliantTimezoneShift +
", useLegacyDatetimeCode=" + useLegacyDatetimeCode +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
package com.vladmihalcea.hibernate.util.providers;

import com.vladmihalcea.hibernate.util.ReflectionUtils;
import oracle.jdbc.pool.OracleDataSource;
import org.hibernate.dialect.Oracle10gDialect;
import org.testcontainers.containers.JdbcDatabaseContainer;

import javax.sql.DataSource;
import java.util.Properties;

/**
* @author Vlad Mihalcea
*/
public class OracleDataSourceProvider implements DataSourceProvider {
public class OracleDataSourceProvider extends AbstractContainerDataSourceProvider {

@Override
public String hibernateDialect() {
return Oracle10gDialect.class.getName();
}

@Override
public DataSource dataSource() {
public String defaultJdbcUrl() {
return "jdbc:oracle:thin:@localhost:1521/xe";
}

@Override
public DataSource newDataSource() {
try {
DataSource dataSource = ReflectionUtils.newInstance("oracle.jdbc.pool.OracleDataSource");
ReflectionUtils.invokeSetter(dataSource, "databaseName", "high_performance_java_persistence");
ReflectionUtils.invokeSetter(dataSource, "URL", url());
ReflectionUtils.invokeSetter(dataSource, "user", "oracle");
ReflectionUtils.invokeSetter(dataSource, "password", "admin");
OracleDataSource dataSource = new OracleDataSource();
JdbcDatabaseContainer container = database().getContainer();
if(container == null) {
dataSource.setDatabaseName("high_performance_java_persistence");
} else {
dataSource.setDatabaseName(container.getDatabaseName());
}
dataSource.setURL(url());
dataSource.setUser(username());
dataSource.setPassword(password());
return dataSource;
} catch (Exception e) {
throw new IllegalStateException(e);
Expand All @@ -31,7 +43,7 @@ public DataSource dataSource() {

@Override
public Class<? extends DataSource> dataSourceClassName() {
return ReflectionUtils.getClass("oracle.jdbc.pool.OracleDataSource");
return OracleDataSource.class;
}

@Override
Expand All @@ -44,11 +56,6 @@ public Properties dataSourceProperties() {
return properties;
}

@Override
public String url() {
return "jdbc:oracle:thin:@localhost:1521/xe";
}

@Override
public String username() {
return "oracle";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@
/**
* @author Vlad Mihalcea
*/
public class PostgreSQLDataSourceProvider implements DataSourceProvider {
public class PostgreSQLDataSourceProvider extends AbstractContainerDataSourceProvider {

@Override
public String hibernateDialect() {
return PostgreSQL82Dialect.class.getName();
}

@Override
public DataSource dataSource() {
protected String defaultJdbcUrl() {
return "jdbc:postgresql://localhost/high_performance_java_persistence";
}

protected DataSource newDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setDatabaseName("high_performance_java_persistence");
dataSource.setServerName("localhost");
dataSource.setUser("postgres");
dataSource.setPassword("admin");
dataSource.setUrl(url());
dataSource.setUser(username());
dataSource.setPassword(password());

return dataSource;
}

Expand All @@ -41,11 +45,6 @@ public Properties dataSourceProperties() {
return properties;
}

@Override
public String url() {
return null;
}

@Override
public String username() {
return "postgres";
Expand Down
Loading

0 comments on commit 14bcaaf

Please sign in to comment.