diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml
index e0162559c..1b6e10540 100644
--- a/spring-data-r2dbc/pom.xml
+++ b/spring-data-r2dbc/pom.xml
@@ -28,6 +28,7 @@
0.1.4
1.0.1.RELEASE
1.0.0.RELEASE
+ 1.1.3
1.0.0.RELEASE
1.0.0
1.0.0.RELEASE
@@ -190,6 +191,13 @@
test
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ ${mariadb-java-client.version}
+ test
+
+
com.oracle.database.jdbc
ojdbc11
@@ -213,6 +221,13 @@
test
+
+ org.mariadb
+ r2dbc-mariadb
+ ${r2dbc-mariadb.version}
+ test
+
+
io.r2dbc
r2dbc-mssql
diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java
index cb092b037..afe50d096 100644
--- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java
+++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java
@@ -598,6 +598,10 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
return (row, metadata) -> {
+ if (metadata == null) {
+ metadata = row.getMetadata();
+ }
+
PersistentPropertyAccessor> propertyAccessor = entity.getPropertyAccessor(object);
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowMetadataUtils.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowMetadataUtils.java
index b632670e5..dfbb67b75 100644
--- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowMetadataUtils.java
+++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowMetadataUtils.java
@@ -18,11 +18,6 @@ package org.springframework.data.r2dbc.convert;
import io.r2dbc.spi.ColumnMetadata;
import io.r2dbc.spi.RowMetadata;
-import java.lang.reflect.Method;
-
-import org.springframework.lang.Nullable;
-import org.springframework.util.ReflectionUtils;
-
/**
* Utility methods for {@link io.r2dbc.spi.RowMetadata}
*
@@ -31,9 +26,6 @@ import org.springframework.util.ReflectionUtils;
*/
class RowMetadataUtils {
- private static final @Nullable Method getColumnMetadatas = ReflectionUtils.findMethod(RowMetadata.class,
- "getColumnMetadatas");
-
/**
* Check whether the column {@code name} is contained in {@link RowMetadata}. The check happens case-insensitive.
*
@@ -63,12 +55,6 @@ class RowMetadataUtils {
*/
@SuppressWarnings("unchecked")
public static Iterable extends ColumnMetadata> getColumnMetadata(RowMetadata metadata) {
-
- if (getColumnMetadatas != null) {
- // Return type of RowMetadata.getColumnMetadatas was updated with R2DBC 0.9.
- return (Iterable extends ColumnMetadata>) ReflectionUtils.invokeMethod(getColumnMetadatas, metadata);
- }
-
return metadata.getColumnMetadatas();
}
}
diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/dialect/DialectResolverUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/dialect/DialectResolverUnitTests.java
index a1e82b705..6efb1372d 100644
--- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/dialect/DialectResolverUnitTests.java
+++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/dialect/DialectResolverUnitTests.java
@@ -14,8 +14,9 @@ import lombok.RequiredArgsConstructor;
import java.util.Optional;
import org.junit.jupiter.api.Test;
+import org.mariadb.r2dbc.MariadbConnectionConfiguration;
+import org.mariadb.r2dbc.MariadbConnectionFactory;
import org.reactivestreams.Publisher;
-
import org.springframework.data.relational.core.dialect.LimitClause;
import org.springframework.data.relational.core.dialect.LockClause;
import org.springframework.data.relational.core.sql.LockOptions;
@@ -35,9 +36,12 @@ public class DialectResolverUnitTests {
PostgresqlConnectionFactory postgres = new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
.host("localhost").database("foo").username("bar").password("password").build());
H2ConnectionFactory h2 = new H2ConnectionFactory(H2ConnectionConfiguration.builder().inMemory("mem").build());
+ MariadbConnectionFactory mariadb = new MariadbConnectionFactory(
+ MariadbConnectionConfiguration.builder().socket("/foo").username("bar").build());
assertThat(DialectResolver.getDialect(postgres)).isEqualTo(PostgresDialect.INSTANCE);
assertThat(DialectResolver.getDialect(h2)).isEqualTo(H2Dialect.INSTANCE);
+ assertThat(DialectResolver.getDialect(mariadb)).isEqualTo(MySqlDialect.INSTANCE);
}
@Test // gh-20, gh-104
diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryIntegrationTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryIntegrationTests.java
new file mode 100644
index 000000000..0cbccf6e1
--- /dev/null
+++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryIntegrationTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2019-2023 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.ExternalDatabase;
+import org.springframework.data.r2dbc.testing.MariaDbTestSupport;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory} against MariaDB.
+ *
+ * @author Mark Paluch
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration
+public class MariaDbR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIntegrationTests {
+
+ @RegisterExtension public static final ExternalDatabase database = MariaDbTestSupport.database();
+
+ @Configuration
+ @EnableR2dbcRepositories(considerNestedRepositories = true,
+ includeFilters = @Filter(classes = MySqlLegoSetRepository.class, type = FilterType.ASSIGNABLE_TYPE))
+ static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
+
+ @Bean
+ @Override
+ public ConnectionFactory connectionFactory() {
+ return MariaDbTestSupport.createConnectionFactory(database);
+ }
+ }
+
+ @Override
+ protected DataSource createDataSource() {
+ return MariaDbTestSupport.createDataSource(database);
+ }
+
+ @Override
+ protected ConnectionFactory createConnectionFactory() {
+ return MariaDbTestSupport.createConnectionFactory(database);
+ }
+
+ @Override
+ protected String getCreateTableStatement() {
+ return MariaDbTestSupport.CREATE_TABLE_LEGOSET_WITH_ID_GENERATION;
+ }
+
+ @Override
+ protected Class extends LegoSetRepository> getRepositoryInterfaceType() {
+ return MySqlLegoSetRepository.class;
+ }
+
+ interface MySqlLegoSetRepository extends LegoSetRepository {
+
+ @Override
+ @Query("SELECT name FROM legoset")
+ Flux findAsProjection();
+
+ @Override
+ @Query("SELECT * FROM legoset WHERE manual = :manual")
+ Mono findByManual(int manual);
+
+ @Override
+ @Query("SELECT id FROM legoset")
+ Flux findAllIds();
+ }
+}
diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java
new file mode 100644
index 000000000..c2d1b8c19
--- /dev/null
+++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2019-2023 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 lombok.SneakyThrows;
+
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import javax.sql.DataSource;
+
+import org.mariadb.jdbc.MariaDbDataSource;
+import org.mariadb.r2dbc.MariadbConnectionFactoryProvider;
+import org.springframework.data.r2dbc.testing.ExternalDatabase.ProvidedDatabase;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Utility class for testing against MariaDB.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ */
+public class MariaDbTestSupport {
+
+ private static ExternalDatabase testContainerDatabase;
+
+ public static final String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
+ + " id integer PRIMARY KEY,\n" //
+ + " name varchar(255) NOT NULL,\n" //
+ + " manual integer NULL\n," //
+ + " cert varbinary(255) NULL\n" //
+ + ") ENGINE=InnoDB;";
+
+ public static final String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
+ + " id integer AUTO_INCREMENT PRIMARY KEY,\n" //
+ + " name varchar(255) NOT NULL,\n" //
+ + " flag boolean NOT NULL,\n" //
+ + " manual integer NULL\n" //
+ + ") ENGINE=InnoDB;";
+
+ public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE `LegoSet` (\n" //
+ + " `Id` integer AUTO_INCREMENT PRIMARY KEY,\n" //
+ + " `Name` varchar(255) NOT NULL,\n" //
+ + " `Manual` integer NULL\n" //
+ + ") ENGINE=InnoDB;";
+
+ public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE `LegoSet`";
+
+ /**
+ * Returns a database either hosted locally at {@code localhost:3306/mysql} or running inside Docker.
+ *
+ * @return information about the database. Guaranteed to be not {@literal null}.
+ */
+ public static ExternalDatabase database() {
+
+ if (Boolean.getBoolean("spring.data.r2dbc.test.preferLocalDatabase")) {
+
+ return getFirstWorkingDatabase( //
+ MariaDbTestSupport::local, //
+ MariaDbTestSupport::testContainer //
+ );
+ } else {
+
+ return getFirstWorkingDatabase( //
+ MariaDbTestSupport::testContainer, //
+ MariaDbTestSupport::local //
+ );
+ }
+ }
+
+ @SafeVarargs
+ private static ExternalDatabase getFirstWorkingDatabase(Supplier... 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(3306) //
+ .database("mysql") //
+ .username("root") //
+ .password("my-secret-pw") //
+ .jdbcUrl("jdbc:mariadb://localhost:3306/mysql") //
+ .build();
+ }
+
+ /**
+ * Returns a database provided via Testcontainers.
+ */
+ private static ExternalDatabase testContainer() {
+
+ if (testContainerDatabase == null) {
+
+ try {
+
+ String osArch = System.getProperty("os.arch");
+
+ DockerImageName armImageName = DockerImageName.parse("arm64v8/mariadb:10.3")
+ .asCompatibleSubstituteFor("mariadb");
+
+ DockerImageName mariadb = DockerImageName.parse("mariadb").withTag("10.3.6");
+ var container = new MariaDBContainer<>("aarch64".equals(osArch) ? armImageName : mariadb);
+
+ container.start();
+
+ testContainerDatabase = ProvidedDatabase.builder(container) //
+ .username("root") //
+ .database(container.getDatabaseName()) //
+ .build();
+ } catch (IllegalStateException ise) {
+ // docker not available.
+ testContainerDatabase = ExternalDatabase.unavailable();
+ }
+ }
+
+ return testContainerDatabase;
+ }
+
+ /**
+ * Creates a new R2DBC MariaDB {@link ConnectionFactory} configured from the {@link ExternalDatabase}.
+ */
+ public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
+
+ ConnectionFactoryOptions options = ConnectionUtils.createOptions("mariadb", database);
+ return new MariadbConnectionFactoryProvider().create(options);
+ }
+
+ /**
+ * Creates a new {@link DataSource} configured from the {@link ExternalDatabase}.
+ */
+ @SneakyThrows
+ public static DataSource createDataSource(ExternalDatabase database) {
+
+ MariaDbDataSource dataSource = new MariaDbDataSource();
+
+ dataSource.setUser(database.getUsername());
+ dataSource.setPassword(database.getPassword());
+ dataSource.setUrl(
+ String.format("jdbc:mariadb://%s:%d/%s?", database.getHostname(), database.getPort(), database.getDatabase()));
+
+ return dataSource;
+ }
+}