Add support for Oracle's R2DBC driver.
We support Oracle's experimental R2DBC driver by providing a dialect including bind markers. Since the driver is not yet available from Maven Central and it requires module-path support for ServiceLoader discovery, we need to apply a few workarounds including absence check for our integration tests. See #230
This commit is contained in:
29
pom.xml
29
pom.xml
@@ -216,6 +216,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc11</artifactId>
|
||||
<version>21.1.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- R2DBC Drivers -->
|
||||
|
||||
<dependency>
|
||||
@@ -288,6 +295,12 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>oracle-xe</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
@@ -448,6 +461,22 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>java11</id>
|
||||
|
||||
<!-- enable once oracle-r2db is available from Maven Central
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.r2dbc</groupId>
|
||||
<artifactId>oracle-r2dbc</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
-->
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
[[new-features.1-2-0]]
|
||||
== What's New in Spring Data R2DBC 1.2.0
|
||||
|
||||
* Deprecate Spring Data R2DBC `DatabaseClient` and move off deprecated API in favor of Spring R2DBC. Consult the <<upgrading.1.1-1.2,Migration Guide>> for further details.
|
||||
* Deprecate Spring Data R2DBC `DatabaseClient` and move off deprecated API in favor of Spring R2DBC.
|
||||
Consult the <<upgrading.1.1-1.2,Migration Guide>> for further details.
|
||||
* Support for <<entity-callbacks>>.
|
||||
* <<r2dbc.auditing,Auditing>> through `@EnableR2dbcAuditing`.
|
||||
* Support for `@Value` in persistence constructors.
|
||||
* Support for Oracle's R2DBC driver.
|
||||
|
||||
[[new-features.1-1-0]]
|
||||
== What's New in Spring Data R2DBC 1.1.0
|
||||
|
||||
@@ -150,7 +150,7 @@ There is a https://github.com/spring-projects/spring-data-examples[GitHub reposi
|
||||
[[r2dbc.connecting]]
|
||||
== Connecting to a Relational Database with Spring
|
||||
|
||||
One of the first tasks when using relational databases and Spring is to create a `io.r2dbc.spi.ConnectionFactory` object by using the IoC container. Make sure to use a <<r2dbc.drivers,supported database and driver>>.
|
||||
One of the first tasks when using relational databases and Spring is to create a `io.r2dbc.spi.ConnectionFactory` object by using the IoC container.Make sure to use a <<r2dbc.drivers,supported database and driver>>.
|
||||
|
||||
[[r2dbc.connectionfactory]]
|
||||
=== Registering a `ConnectionFactory` Instance using Java-based Metadata
|
||||
@@ -173,7 +173,7 @@ public class ApplicationConfiguration extends AbstractR2dbcConfiguration {
|
||||
----
|
||||
====
|
||||
|
||||
This approach lets you use the standard `io.r2dbc.spi.ConnectionFactory` instance, with the container using Spring's `AbstractR2dbcConfiguration`. As compared to registering a `ConnectionFactory` instance directly, the configuration support has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates R2DBC exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and the use of `@Repository` is described in {spring-framework-ref}/data-access.html[Spring's DAO support features].
|
||||
This approach lets you use the standard `io.r2dbc.spi.ConnectionFactory` instance, with the container using Spring's `AbstractR2dbcConfiguration`.As compared to registering a `ConnectionFactory` instance directly, the configuration support has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates R2DBC exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation.This hierarchy and the use of `@Repository` is described in {spring-framework-ref}/data-access.html[Spring's DAO support features].
|
||||
|
||||
`AbstractR2dbcConfiguration` also registers `DatabaseClient`, which is required for database interaction and for Repository implementation.
|
||||
|
||||
@@ -191,11 +191,11 @@ Spring Data R2DBC ships with dialect impelemtations for the following drivers:
|
||||
* https://github.com/mirromutth/r2dbc-mysql[MySQL] (`dev.miku:r2dbc-mysql`)
|
||||
* https://github.com/jasync-sql/jasync-sql[jasync-sql MySQL] (`com.github.jasync-sql:jasync-r2dbc-mysql`)
|
||||
* https://github.com/r2dbc/r2dbc-postgresql[Postgres] (`io.r2dbc:r2dbc-postgresql`)
|
||||
* https://github.com/oracle/oracle-r2dbc[Oracle] (`com.oracle.database.r2dbc:oracle-r2dbc`)
|
||||
|
||||
Spring Data R2DBC reacts to database specifics by inspecting the `ConnectionFactory` and selects the appropriate database dialect accordingly.
|
||||
You need to configure your own {spring-data-r2dbc-javadoc}/api/org/springframework/data/r2dbc/dialect/R2dbcDialect.html[`R2dbcDialect`] if the driver you use is not yet known to Spring Data R2DBC.
|
||||
|
||||
TIP: Dialects are resolved by {spring-data-r2dbc-javadoc}/org/springframework/data/r2dbc/dialect/DialectResolver.html[`DialectResolver`] from a `ConnectionFactory`, typically by inspecting `ConnectionFactoryMetadata`.
|
||||
+
|
||||
You can let Spring auto-discover your `R2dbcDialect` by registering a class that implements `org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider` through `META-INF/spring.factories`.
|
||||
+ You can let Spring auto-discover your `R2dbcDialect` by registering a class that implements `org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider` through `META-INF/spring.factories`.
|
||||
`DialectResolver` discovers dialect provider implementations from the class path using Spring's `SpringFactoriesLoader`.
|
||||
|
||||
@@ -115,6 +115,7 @@ public class DialectResolver {
|
||||
BUILTIN.put("Microsoft SQL Server", SqlServerDialect.INSTANCE);
|
||||
BUILTIN.put("MySQL", MySqlDialect.INSTANCE);
|
||||
BUILTIN.put("MariaDB", MySqlDialect.INSTANCE);
|
||||
BUILTIN.put("Oracle", OracleDialect.INSTANCE);
|
||||
BUILTIN.put("PostgreSQL", PostgresDialect.INSTANCE);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.dialect;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Oracle.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2.6
|
||||
*/
|
||||
public class OracleDialect extends org.springframework.data.relational.core.dialect.OracleDialect
|
||||
implements R2dbcDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final OracleDialect INSTANCE = new OracleDialect();
|
||||
|
||||
private static final BindMarkersFactory NAMED = BindMarkersFactory.named(":", "P", 32,
|
||||
OracleDialect::filterBindMarker);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
|
||||
*/
|
||||
@Override
|
||||
public BindMarkersFactory getBindMarkersFactory() {
|
||||
return NAMED;
|
||||
}
|
||||
|
||||
private static String filterBindMarker(CharSequence input) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
|
||||
char ch = input.charAt(i);
|
||||
|
||||
// ascii letter or digit
|
||||
if (Character.isLetterOrDigit(ch) && ch < 127) {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "_" + builder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2018-2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import org.springframework.data.r2dbc.testing.EnabledOnClass;
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase;
|
||||
import org.springframework.data.r2dbc.testing.OracleTestSupport;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DatabaseClient} against Oracle.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EnabledOnClass("oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl")
|
||||
public class OracleDatabaseClientIntegrationTests extends AbstractDatabaseClientIntegrationTests {
|
||||
|
||||
@RegisterExtension public static final ExternalDatabase database = OracleTestSupport.database();
|
||||
|
||||
@Override
|
||||
protected DataSource createDataSource() {
|
||||
return OracleTestSupport.createDataSource(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectionFactory createConnectionFactory() {
|
||||
return OracleTestSupport.createConnectionFactory(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCreateTableStatement() {
|
||||
return OracleTestSupport.CREATE_TABLE_LEGOSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Disabled("https://github.com/oracle/oracle-r2dbc/issues/9")
|
||||
public void executeSelectNamedParameters() {}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarker;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link OracleDialect}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class OracleDialectUnitTests {
|
||||
|
||||
@Test // gh-230
|
||||
void shouldUseNamedPlaceholders() {
|
||||
|
||||
BindMarkers bindMarkers = OracleDialect.INSTANCE.getBindMarkersFactory().create();
|
||||
|
||||
BindMarker first = bindMarkers.next();
|
||||
BindMarker second = bindMarkers.next("'foo!bar");
|
||||
|
||||
assertThat(first.getPlaceholder()).isEqualTo(":P0");
|
||||
assertThat(second.getPlaceholder()).isEqualTo(":P1_foobar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.repository;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
|
||||
import org.springframework.data.r2dbc.testing.EnabledOnClass;
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase;
|
||||
import org.springframework.data.r2dbc.testing.OracleTestSupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory} against Oracle.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@EnabledOnClass("oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl")
|
||||
public class OracleR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIntegrationTests {
|
||||
|
||||
@RegisterExtension public static final ExternalDatabase database = OracleTestSupport.database();
|
||||
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories(considerNestedRepositories = true,
|
||||
includeFilters = @Filter(classes = OracleLegoSetRepository.class, type = FilterType.ASSIGNABLE_TYPE))
|
||||
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return OracleTestSupport.createConnectionFactory(database);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataSource createDataSource() {
|
||||
return OracleTestSupport.createDataSource(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectionFactory createConnectionFactory() {
|
||||
return OracleTestSupport.createConnectionFactory(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCreateTableStatement() {
|
||||
return OracleTestSupport.CREATE_TABLE_LEGOSET_WITH_ID_GENERATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends LegoSetRepository> getRepositoryInterfaceType() {
|
||||
return OracleLegoSetRepository.class;
|
||||
}
|
||||
|
||||
interface OracleLegoSetRepository extends LegoSetRepository {
|
||||
|
||||
@Override
|
||||
@Query("SELECT name FROM legoset")
|
||||
Flux<Named> findAsProjection();
|
||||
|
||||
@Override
|
||||
@Query("SELECT * FROM legoset WHERE manual = :manual")
|
||||
Mono<LegoSet> findByManual(int manual);
|
||||
|
||||
@Override
|
||||
@Query("SELECT id FROM legoset")
|
||||
Flux<Integer> findAllIds();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.testing;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* {@code @EnabledOnClass} is used to signal that the annotated test class or test method is only <em>enabled</em> if
|
||||
* the specified {@link #value() class} is present.
|
||||
* <p>
|
||||
* When applied at the class level, all test methods within that class will be enabled on presence of the specified
|
||||
* class.
|
||||
* <p>
|
||||
* If a test method is disabled via this annotation, that does not prevent the test class from being instantiated.
|
||||
* Rather, it prevents the execution of the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
|
||||
* methods, {@code @AfterEach} methods, and corresponding extension APIs.
|
||||
* <p>
|
||||
* This annotation may be used as a meta-annotation in order to create a custom <em>composed annotation</em> that
|
||||
* inherits the semantics of this annotation.
|
||||
*
|
||||
* @see JRE
|
||||
* @see org.junit.jupiter.api.Disabled
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@ExtendWith(EnabledOnClassCondition.class)
|
||||
public @interface EnabledOnClass {
|
||||
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.testing;
|
||||
|
||||
import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*;
|
||||
import static org.junit.platform.commons.util.AnnotationUtils.*;
|
||||
|
||||
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link ExecutionCondition} for {@link EnabledOnClass @EnabledOnClass}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see EnabledOnClass
|
||||
*/
|
||||
class EnabledOnClassCondition implements ExecutionCondition {
|
||||
|
||||
@Override
|
||||
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||
return findAnnotation(context.getElement(), EnabledOnClass.class) //
|
||||
.map(annotation -> isEnabled(annotation)
|
||||
? enabled(String.format("Class '%s' found on the class path.", annotation.value()))
|
||||
: disabled(String.format("Class '%s' not found on the class path.", annotation.value()))) //
|
||||
.orElseGet(this::enabledByDefault);
|
||||
}
|
||||
|
||||
private boolean isEnabled(EnabledOnClass annotation) {
|
||||
return ClassUtils.isPresent(annotation.value(), EnabledOnClassCondition.class.getClassLoader());
|
||||
}
|
||||
|
||||
private ConditionEvaluationResult enabledByDefault() {
|
||||
return enabled("@EnabledOnClass is not present");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.testing;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactoryOptions;
|
||||
import io.r2dbc.spi.ConnectionFactoryProvider;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ConnectionFactoryProvider} for Oracle's R2DBC driver. Allows for absence of the driver which is required when
|
||||
* using Java 8 as the ServiceLoader requires presence of classes listed in the service loader manifest.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class OracleConnectionFactoryProviderWrapper implements ConnectionFactoryProvider {
|
||||
|
||||
private final ConnectionFactoryProvider delegate;
|
||||
|
||||
public OracleConnectionFactoryProviderWrapper() {
|
||||
|
||||
if (ClassUtils.isPresent("oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl", getClass().getClassLoader())) {
|
||||
|
||||
delegate = createProvider();
|
||||
} else {
|
||||
delegate = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static ConnectionFactoryProvider createProvider() {
|
||||
|
||||
try {
|
||||
return (ConnectionFactoryProvider) Class.forName("oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl")
|
||||
.newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
ReflectionUtils.handleReflectionException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOptions) {
|
||||
if (delegate != null) {
|
||||
return delegate.create(connectionFactoryOptions);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Oracle R2DBC (oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl) is not on the class path");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConnectionFactoryOptions connectionFactoryOptions) {
|
||||
|
||||
if (delegate != null) {
|
||||
return delegate.supports(connectionFactoryOptions);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDriver() {
|
||||
|
||||
if (delegate != null) {
|
||||
return delegate.getDriver();
|
||||
}
|
||||
|
||||
return "oracle-r2dbc (proxy)";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.data.r2dbc.testing;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactories;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactoryOptions;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase.ProvidedDatabase;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import org.testcontainers.containers.OracleContainer;
|
||||
|
||||
/**
|
||||
* Utility class for testing against Oracle.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class OracleTestSupport {
|
||||
|
||||
private static ExternalDatabase testContainerDatabase;
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id INTEGER PRIMARY KEY,\n" //
|
||||
+ " version INTEGER NULL,\n" //
|
||||
+ " name VARCHAR2(255) NOT NULL,\n" //
|
||||
+ " manual INTEGER NULL,\n" //
|
||||
+ " cert RAW(255) NULL\n" //
|
||||
+ ")";
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
|
||||
+ " id INTEGER GENERATED by default on null as IDENTITY PRIMARY KEY,\n" //
|
||||
+ " version INTEGER NULL,\n" //
|
||||
+ " name VARCHAR2(255) NOT NULL,\n" //
|
||||
+ " manual INTEGER NULL\n" //
|
||||
+ ")";
|
||||
|
||||
/**
|
||||
* Returns a database either hosted locally or running inside Docker.
|
||||
*
|
||||
* @return information about the database. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
public static ExternalDatabase database() {
|
||||
|
||||
if (!ClassUtils.isPresent("oracle.r2dbc.impl.OracleConnectionFactoryProviderImpl",
|
||||
OracleTestSupport.class.getClassLoader())) {
|
||||
return ExternalDatabase.unavailable();
|
||||
}
|
||||
|
||||
if (Boolean.getBoolean("spring.data.r2dbc.test.preferLocalDatabase")) {
|
||||
|
||||
return getFirstWorkingDatabase( //
|
||||
OracleTestSupport::local, //
|
||||
OracleTestSupport::testContainer //
|
||||
);
|
||||
} else {
|
||||
|
||||
return getFirstWorkingDatabase( //
|
||||
OracleTestSupport::testContainer, //
|
||||
OracleTestSupport::local //
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static ExternalDatabase getFirstWorkingDatabase(Supplier<ExternalDatabase>... suppliers) {
|
||||
|
||||
return Stream.of(suppliers).map(Supplier::get) //
|
||||
.filter(ExternalDatabase::checkValidity) //
|
||||
.findFirst() //
|
||||
.orElse(ExternalDatabase.unavailable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locally provided database.
|
||||
*/
|
||||
private static ExternalDatabase local() {
|
||||
|
||||
return ProvidedDatabase.builder() //
|
||||
.hostname("localhost") //
|
||||
.port(1521) //
|
||||
.database("XEPDB1") //
|
||||
.username("system") //
|
||||
.password("oracle") //
|
||||
.jdbcUrl("jdbc:oracle:thin:system/oracle@localhost:1521:XEPDB1") //
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a database provided via Testcontainers.
|
||||
*/
|
||||
private static ExternalDatabase testContainer() {
|
||||
|
||||
if (testContainerDatabase == null) {
|
||||
|
||||
try {
|
||||
OracleContainer container = new OracleContainer("springci/spring-data-oracle-xe-prebuild:18.4.0")
|
||||
.withReuse(true);
|
||||
container.start();
|
||||
|
||||
testContainerDatabase = ProvidedDatabase.builder(container) //
|
||||
.database("XEPDB1").build();
|
||||
} catch (IllegalStateException ise) {
|
||||
// docker not available.
|
||||
testContainerDatabase = ExternalDatabase.unavailable();
|
||||
}
|
||||
}
|
||||
|
||||
return testContainerDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Oracle {@link ConnectionFactory} configured from the {@link ExternalDatabase}.
|
||||
*/
|
||||
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
|
||||
|
||||
ConnectionFactoryOptions options = ConnectionUtils.createOptions("oracle", database);
|
||||
return ConnectionFactories.get(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DataSource} configured from the {@link ExternalDatabase}.
|
||||
*/
|
||||
public static DataSource createDataSource(ExternalDatabase database) {
|
||||
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
|
||||
dataSource.setUsername(database.getUsername());
|
||||
dataSource.setPassword(database.getPassword());
|
||||
dataSource.setUrl(database.getJdbcUrl().replace(":xe", "/XEPDB1"));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://github.com/oracle/oracle-r2dbc/issues/10, otherwise the driver needs to be operated in module-path mode.
|
||||
org.springframework.data.r2dbc.testing.OracleConnectionFactoryProviderWrapper
|
||||
Reference in New Issue
Block a user