Skip to content

Commit 2022af9

Browse files
committed
#173 implement support for H2 database
1 parent 18ce04b commit 2022af9

File tree

9 files changed

+388
-19
lines changed

9 files changed

+388
-19
lines changed

build.gradle

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ ext {
1212

1313
testSuites = [
1414
[name: 'default', versions: [
15-
[name: 'suite', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default']
15+
[name: 'suite', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default']
1616
]],
1717
[name: 'spring', versions: [
18-
[name: '4.3.8', spring: '4.3.8.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default'],
19-
[name: '4.3.29', spring: '4.3.29.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default'],
20-
[name: '5.0.19', spring: '5.0.19.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default'],
21-
[name: '5.1.19', spring: '5.1.19.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default'],
22-
[name: '5.2.11', spring: '5.2.11.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default'],
23-
[name: '5.3.1', spring: '5.3.1', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default']
18+
[name: '4.3.8', spring: '4.3.8.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default'],
19+
[name: '4.3.29', spring: '4.3.29.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default'],
20+
[name: '5.0.19', spring: '5.0.19.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default'],
21+
[name: '5.1.19', spring: '5.1.19.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default'],
22+
[name: '5.2.11', spring: '5.2.11.RELEASE', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default'],
23+
[name: '5.3.1', spring: '5.3.1', 'zonky-postgres': 'default', opentable: 'default', yandex: 'default', 'mssql-driver': 'default', 'mysql-driver': 'default', 'mariadb-driver': 'default', 'h2': 'default']
2424
]],
2525
[name: 'flyway', versions: [
2626
[name: '4.0.3', flyway: '4.0.3', 'flyway-test': '4.0.1', spring: '4.3.25.RELEASE', 'spring-boot': '1.5.22.RELEASE', 'zonky-postgres': 'default'],
@@ -89,6 +89,10 @@ ext {
8989
[name: '10.3', 'mariadb': '10.3', 'mariadb-driver': 'default'],
9090
[name: '10.4', 'mariadb': '10.4', 'mariadb-driver': 'default'],
9191
[name: '10.5', 'mariadb': '10.5', 'mariadb-driver': 'default']
92+
]],
93+
[name: 'h2', versions: [
94+
[name: '1.3.176', 'h2': '1.3.176'],
95+
[name: '1.4.200', 'h2': '1.4.200']
9296
]]
9397
]
9498
}
@@ -219,6 +223,7 @@ project(':embedded-database-spring-test') {
219223
compile 'com.microsoft.sqlserver:mssql-jdbc:8.4.1.jre8', optional
220224
compile 'mysql:mysql-connector-java:8.0.22', optional
221225
compile 'org.mariadb.jdbc:mariadb-java-client:2.7.0', optional
226+
compile 'com.h2database:h2:1.4.200', optional
222227

223228
compile 'org.flywaydb:flyway-core:7.13.0', optional
224229
compile 'org.flywaydb.flyway-test-extensions:flyway-spring-test:7.0.0', optional
@@ -357,6 +362,16 @@ project(':embedded-database-spring-test') {
357362
}
358363
}
359364
}
365+
366+
if (version.h2 == null) { // optional dependencies are implicitly excluded
367+
exclude group: 'com.h2database', module: 'h2'
368+
} else if (version.h2 != 'default') {
369+
eachDependency { details ->
370+
if (details.requested.group == 'com.h2database' && details.requested.name == 'h2') {
371+
details.useVersion "${version.h2}"
372+
}
373+
}
374+
}
360375
}
361376
}
362377
}
@@ -440,6 +455,8 @@ project(':embedded-database-spring-test') {
440455
includeCategories 'io.zonky.test.category.MySQLTestSuite'
441456
} else if (suite.name == 'mariadb') {
442457
includeCategories 'io.zonky.test.category.MariaDBTestSuite'
458+
} else if (suite.name == 'h2') {
459+
includeCategories 'io.zonky.test.category.H2TestSuite'
443460
}
444461
}
445462
}

embedded-database-spring-test/src/main/java/io/zonky/test/db/AutoConfigureEmbeddedDatabase.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,12 @@ enum DatabaseType {
150150
/**
151151
* MariaDB Database
152152
*/
153-
MARIADB
153+
MARIADB,
154+
155+
/**
156+
* H2 Database
157+
*/
158+
H2
154159

155160
}
156161

@@ -170,6 +175,11 @@ enum DatabaseProvider {
170175
*/
171176
DOCKER,
172177

178+
/**
179+
* Use an embedded database provider.
180+
*/
181+
EMBEDDED,
182+
173183
/**
174184
* Use Zonky's fork of OpenTable Embedded PostgreSQL Component to create the embedded database
175185
* (<a href="https://github.com/zonkyio/embedded-postgres">https://github.com/zonkyio/embedded-postgres</a>).
@@ -186,7 +196,7 @@ enum DatabaseProvider {
186196
* Use Yandex's Embedded PostgreSQL Server to create the embedded database
187197
* (<a href="https://github.com/yandex-qatools/postgresql-embedded">https://github.com/yandex-qatools/postgresql-embedded</a>).
188198
*/
189-
YANDEX,
199+
YANDEX
190200

191201
}
192202

embedded-database-spring-test/src/main/java/io/zonky/test/db/config/EmbeddedDatabaseAutoConfiguration.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.zonky.test.db.liquibase.LiquibaseDatabaseExtension;
2222
import io.zonky.test.db.liquibase.LiquibasePropertiesPostProcessor;
2323
import io.zonky.test.db.provider.DatabaseProvider;
24+
import io.zonky.test.db.provider.h2.H2DatabaseProvider;
2425
import io.zonky.test.db.provider.mariadb.DockerMariaDBDatabaseProvider;
2526
import io.zonky.test.db.provider.mssql.DockerMSSQLDatabaseProvider;
2627
import io.zonky.test.db.provider.mysql.DockerMySQLDatabaseProvider;
@@ -114,6 +115,14 @@ public DatabaseProvider dockerMariaDbDatabaseProvider(DatabaseProviderFactory ma
114115
return mariaDbDatabaseProviderFactory.createProvider(DockerMariaDBDatabaseProvider.class);
115116
}
116117

118+
@Bean
119+
@Provider(type = "embedded", database = "h2")
120+
@ConditionalOnMissingBean(name = "h2DatabaseProvider")
121+
public DatabaseProvider h2DatabaseProvider(DatabaseProviderFactory h2DatabaseProviderFactory) {
122+
checkDependency("com.h2database", "h2", "org.h2.Driver");
123+
return h2DatabaseProviderFactory.createProvider(H2DatabaseProvider.class);
124+
}
125+
117126
@Bean
118127
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
119128
@ConditionalOnMissingBean(name = "postgresDatabaseProviderFactory")
@@ -150,6 +159,15 @@ public DatabaseProviderFactory mariaDbDatabaseProviderFactory(DatabaseProviderFa
150159
builder.optimizingProvider(provider));
151160
}
152161

162+
@Bean
163+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
164+
@ConditionalOnMissingBean(name = "h2DatabaseProviderFactory")
165+
public DatabaseProviderFactory h2DatabaseProviderFactory(DatabaseProviderFactory defaultDatabaseProviderFactory) {
166+
return defaultDatabaseProviderFactory.customizeProvider((builder, provider) ->
167+
builder.optimizingProvider(
168+
builder.prefetchingProvider(provider)));
169+
}
170+
153171
@Bean
154172
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
155173
@ConditionalOnMissingBean(name = "defaultDatabaseProviderFactory")
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.zonky.test.db.provider.h2;
18+
19+
import io.zonky.test.db.preparer.DatabasePreparer;
20+
import io.zonky.test.db.provider.DatabaseProvider;
21+
import io.zonky.test.db.provider.EmbeddedDatabase;
22+
import io.zonky.test.db.provider.ProviderException;
23+
import org.h2.tools.Server;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
27+
28+
import javax.sql.DataSource;
29+
import java.sql.Connection;
30+
import java.sql.SQLException;
31+
import java.sql.Statement;
32+
import java.util.Objects;
33+
import java.util.UUID;
34+
import java.util.concurrent.CompletableFuture;
35+
36+
public class H2DatabaseProvider implements DatabaseProvider {
37+
38+
private static final Logger logger = LoggerFactory.getLogger(H2DatabaseProvider.class);
39+
private static final Server server = startServer();
40+
41+
private static Server startServer() {
42+
try {
43+
Server server = Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "0").start();
44+
Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown));
45+
return server;
46+
} catch (SQLException e) {
47+
return null;
48+
}
49+
}
50+
51+
@Override
52+
public EmbeddedDatabase createDatabase(DatabasePreparer preparer) throws ProviderException {
53+
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
54+
String databaseName = UUID.randomUUID().toString();
55+
56+
dataSource.setDriverClass(org.h2.Driver.class);
57+
dataSource.setUrl(String.format("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1", databaseName));
58+
dataSource.setUsername("sa");
59+
dataSource.setPassword("");
60+
61+
H2EmbeddedDatabase database = new H2EmbeddedDatabase(server, dataSource, databaseName,
62+
() -> shutdownDatabase(dataSource, databaseName));
63+
try {
64+
if (preparer != null) {
65+
preparer.prepare(database);
66+
}
67+
} catch (SQLException e) {
68+
throw new ProviderException("Unexpected error when creating a database", e);
69+
}
70+
return database;
71+
}
72+
73+
@Override
74+
public boolean equals(Object o) {
75+
if (this == o) return true;
76+
if (o == null || getClass() != o.getClass()) return false;
77+
return true;
78+
}
79+
80+
@Override
81+
public int hashCode() {
82+
return Objects.hash(H2DatabaseProvider.class);
83+
}
84+
85+
private static void shutdownDatabase(DataSource dataSource, String dbName) {
86+
CompletableFuture.runAsync(() -> {
87+
try {
88+
executeStatement(dataSource, "SHUTDOWN");
89+
} catch (Exception e) {
90+
if (logger.isTraceEnabled()) {
91+
logger.warn("Unable to release '{}' database", dbName, e);
92+
} else {
93+
logger.warn("Unable to release '{}' database", dbName);
94+
}
95+
}
96+
});
97+
}
98+
99+
private static void executeStatement(DataSource dataSource, String ddlStatement) throws SQLException {
100+
try (Connection connection = dataSource.getConnection(); Statement stmt = connection.createStatement()) {
101+
stmt.execute(ddlStatement);
102+
}
103+
}
104+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.zonky.test.db.provider.h2;
18+
19+
import io.zonky.test.db.provider.support.AbstractEmbeddedDatabase;
20+
import org.h2.tools.Server;
21+
22+
import javax.sql.DataSource;
23+
24+
public class H2EmbeddedDatabase extends AbstractEmbeddedDatabase {
25+
26+
private final Server server;
27+
private final DataSource dataSource;
28+
private final String dbName;
29+
30+
public H2EmbeddedDatabase(Server server, DataSource dataSource, String dbName, Runnable closeCallback) {
31+
super(closeCallback);
32+
this.server = server;
33+
this.dataSource = dataSource;
34+
this.dbName = dbName;
35+
}
36+
37+
@Override
38+
protected DataSource getDataSource() {
39+
return dataSource;
40+
}
41+
42+
@Override
43+
public String getJdbcUrl() {
44+
if (server != null) {
45+
return String.format("jdbc:h2:tcp://localhost:%s/mem:%s;USER=sa", server.getPort(), dbName);
46+
} else {
47+
return String.format("jdbc:h2:mem:%s;USER=sa", dbName);
48+
}
49+
}
50+
51+
public String getDatabaseName() {
52+
return dbName;
53+
}
54+
55+
public int getPortNumber() {
56+
if (server != null) {
57+
return server.getPort();
58+
} else {
59+
return 0;
60+
}
61+
}
62+
}

embedded-database-spring-test/src/main/java/io/zonky/test/db/support/DefaultProviderResolver.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.zonky.test.db.support;
22

33
import io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseProvider;
4+
import io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseType;
45
import org.slf4j.Logger;
56
import org.slf4j.LoggerFactory;
67
import org.springframework.core.env.Environment;
@@ -10,11 +11,6 @@
1011
import java.util.LinkedHashSet;
1112
import java.util.Set;
1213

13-
import static io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseProvider.DEFAULT;
14-
import static io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseProvider.DOCKER;
15-
import static io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseType;
16-
import static io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseType.AUTO;
17-
1814
public class DefaultProviderResolver implements ProviderResolver {
1915

2016
private static final Logger logger = LoggerFactory.getLogger(DefaultProviderResolver.class);
@@ -31,6 +27,15 @@ public DefaultProviderResolver(Environment environment, ClassLoader classLoader)
3127
public ProviderDescriptor getDescriptor(DatabaseDefinition definition) {
3228
String providerName = getProviderName(definition.getProviderType());
3329
String databaseName = getDatabaseName(definition.getDatabaseType());
30+
31+
if (providerName == null) {
32+
if (DatabaseType.H2.name().equalsIgnoreCase(databaseName)) {
33+
providerName = DatabaseProvider.EMBEDDED.name();
34+
} else {
35+
providerName = DatabaseProvider.DOCKER.name();
36+
}
37+
}
38+
3439
ProviderDescriptor descriptor = ProviderDescriptor.of(providerName, databaseName);
3540

3641
if (StringUtils.hasText(definition.getBeanName())) {
@@ -43,25 +48,25 @@ public ProviderDescriptor getDescriptor(DatabaseDefinition definition) {
4348
}
4449

4550
protected String getProviderName(DatabaseProvider providerType) {
46-
if (providerType != DEFAULT) {
51+
if (providerType != DatabaseProvider.DEFAULT) {
4752
return providerType.name();
4853
}
4954

5055
String providerName = environment.getProperty("zonky.test.database.provider");
51-
if (providerName != null && !providerName.equalsIgnoreCase(DEFAULT.name())) {
56+
if (providerName != null && !providerName.equalsIgnoreCase(DatabaseProvider.DEFAULT.name())) {
5257
return providerName;
5358
}
5459

55-
return DOCKER.name();
60+
return null;
5661
}
5762

5863
protected String getDatabaseName(DatabaseType databaseType) {
59-
if (databaseType != AUTO) {
64+
if (databaseType != DatabaseType.AUTO) {
6065
return databaseType.name();
6166
}
6267

6368
String databaseName = environment.getProperty("zonky.test.database.type");
64-
if (databaseName != null && !databaseName.equalsIgnoreCase(AUTO.name())) {
69+
if (databaseName != null && !databaseName.equalsIgnoreCase(DatabaseType.AUTO.name())) {
6570
return databaseName;
6671
}
6772

@@ -79,6 +84,9 @@ protected String getDatabaseName(DatabaseType databaseType) {
7984
if (ClassUtils.isPresent("org.mariadb.jdbc.MariaDbDataSource", classLoader)) {
8085
detectedTypes.add(DatabaseType.MARIADB);
8186
}
87+
if (ClassUtils.isPresent("org.h2.Driver", classLoader)) {
88+
detectedTypes.add(DatabaseType.H2);
89+
}
8290

8391
if (detectedTypes.isEmpty()) {
8492
throw new IllegalStateException("Database auto-detection failed, no database driver detected. " +

0 commit comments

Comments
 (0)