Skip to content

Commit 7341a02

Browse files
committed
Reinstate support for mariadb-r2dbc.
Closes spring-projects#1364
1 parent 09189a4 commit 7341a02

File tree

6 files changed

+286
-15
lines changed

6 files changed

+286
-15
lines changed

spring-data-r2dbc/pom.xml

+15
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<degraph-check.version>0.1.4</degraph-check.version>
2929
<r2dbc-postgresql.version>1.0.1.RELEASE</r2dbc-postgresql.version>
3030
<r2dbc-h2.version>1.0.0.RELEASE</r2dbc-h2.version>
31+
<r2dbc-mariadb.version>1.1.3</r2dbc-mariadb.version>
3132
<r2dbc-mssql.version>1.0.0.RELEASE</r2dbc-mssql.version>
3233
<oracle-r2dbc.version>1.0.0</oracle-r2dbc.version>
3334
<r2dbc-spi.version>1.0.0.RELEASE</r2dbc-spi.version>
@@ -190,6 +191,13 @@
190191
<scope>test</scope>
191192
</dependency>
192193

194+
<dependency>
195+
<groupId>org.mariadb.jdbc</groupId>
196+
<artifactId>mariadb-java-client</artifactId>
197+
<version>${mariadb-java-client.version}</version>
198+
<scope>test</scope>
199+
</dependency>
200+
193201
<dependency>
194202
<groupId>com.oracle.database.jdbc</groupId>
195203
<artifactId>ojdbc11</artifactId>
@@ -213,6 +221,13 @@
213221
<scope>test</scope>
214222
</dependency>
215223

224+
<dependency>
225+
<groupId>org.mariadb</groupId>
226+
<artifactId>r2dbc-mariadb</artifactId>
227+
<version>${r2dbc-mariadb.version}</version>
228+
<scope>test</scope>
229+
</dependency>
230+
216231
<dependency>
217232
<groupId>io.r2dbc</groupId>
218233
<artifactId>r2dbc-mssql</artifactId>

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java

+4
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,10 @@ public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
598598

599599
return (row, metadata) -> {
600600

601+
if (metadata == null) {
602+
metadata = row.getMetadata();
603+
}
604+
601605
PersistentPropertyAccessor<?> propertyAccessor = entity.getPropertyAccessor(object);
602606
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
603607

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowMetadataUtils.java

-14
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@
1818
import io.r2dbc.spi.ColumnMetadata;
1919
import io.r2dbc.spi.RowMetadata;
2020

21-
import java.lang.reflect.Method;
22-
23-
import org.springframework.lang.Nullable;
24-
import org.springframework.util.ReflectionUtils;
25-
2621
/**
2722
* Utility methods for {@link io.r2dbc.spi.RowMetadata}
2823
*
@@ -31,9 +26,6 @@
3126
*/
3227
class RowMetadataUtils {
3328

34-
private static final @Nullable Method getColumnMetadatas = ReflectionUtils.findMethod(RowMetadata.class,
35-
"getColumnMetadatas");
36-
3729
/**
3830
* Check whether the column {@code name} is contained in {@link RowMetadata}. The check happens case-insensitive.
3931
*
@@ -63,12 +55,6 @@ public static boolean containsColumn(RowMetadata metadata, String name) {
6355
*/
6456
@SuppressWarnings("unchecked")
6557
public static Iterable<? extends ColumnMetadata> getColumnMetadata(RowMetadata metadata) {
66-
67-
if (getColumnMetadatas != null) {
68-
// Return type of RowMetadata.getColumnMetadatas was updated with R2DBC 0.9.
69-
return (Iterable<? extends ColumnMetadata>) ReflectionUtils.invokeMethod(getColumnMetadatas, metadata);
70-
}
71-
7258
return metadata.getColumnMetadatas();
7359
}
7460
}

spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/dialect/DialectResolverUnitTests.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
import java.util.Optional;
1515

1616
import org.junit.jupiter.api.Test;
17+
import org.mariadb.r2dbc.MariadbConnectionConfiguration;
18+
import org.mariadb.r2dbc.MariadbConnectionFactory;
1719
import org.reactivestreams.Publisher;
18-
1920
import org.springframework.data.relational.core.dialect.LimitClause;
2021
import org.springframework.data.relational.core.dialect.LockClause;
2122
import org.springframework.data.relational.core.sql.LockOptions;
@@ -35,9 +36,12 @@ void shouldResolveDatabaseType() {
3536
PostgresqlConnectionFactory postgres = new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
3637
.host("localhost").database("foo").username("bar").password("password").build());
3738
H2ConnectionFactory h2 = new H2ConnectionFactory(H2ConnectionConfiguration.builder().inMemory("mem").build());
39+
MariadbConnectionFactory mariadb = new MariadbConnectionFactory(
40+
MariadbConnectionConfiguration.builder().socket("/foo").username("bar").build());
3841

3942
assertThat(DialectResolver.getDialect(postgres)).isEqualTo(PostgresDialect.INSTANCE);
4043
assertThat(DialectResolver.getDialect(h2)).isEqualTo(H2Dialect.INSTANCE);
44+
assertThat(DialectResolver.getDialect(mariadb)).isEqualTo(MySqlDialect.INSTANCE);
4145
}
4246

4347
@Test // gh-20, gh-104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2019-2023 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+
* https://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+
package org.springframework.data.r2dbc.repository;
17+
18+
import io.r2dbc.spi.ConnectionFactory;
19+
import reactor.core.publisher.Flux;
20+
import reactor.core.publisher.Mono;
21+
22+
import javax.sql.DataSource;
23+
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.junit.jupiter.api.extension.RegisterExtension;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.ComponentScan.Filter;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.context.annotation.FilterType;
30+
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
31+
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
32+
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
33+
import org.springframework.data.r2dbc.testing.ExternalDatabase;
34+
import org.springframework.data.r2dbc.testing.MariaDbTestSupport;
35+
import org.springframework.test.context.ContextConfiguration;
36+
import org.springframework.test.context.junit.jupiter.SpringExtension;
37+
38+
/**
39+
* Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory} against MariaDB.
40+
*
41+
* @author Mark Paluch
42+
*/
43+
@ExtendWith(SpringExtension.class)
44+
@ContextConfiguration
45+
public class MariaDbR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIntegrationTests {
46+
47+
@RegisterExtension public static final ExternalDatabase database = MariaDbTestSupport.database();
48+
49+
@Configuration
50+
@EnableR2dbcRepositories(considerNestedRepositories = true,
51+
includeFilters = @Filter(classes = MySqlLegoSetRepository.class, type = FilterType.ASSIGNABLE_TYPE))
52+
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
53+
54+
@Bean
55+
@Override
56+
public ConnectionFactory connectionFactory() {
57+
return MariaDbTestSupport.createConnectionFactory(database);
58+
}
59+
}
60+
61+
@Override
62+
protected DataSource createDataSource() {
63+
return MariaDbTestSupport.createDataSource(database);
64+
}
65+
66+
@Override
67+
protected ConnectionFactory createConnectionFactory() {
68+
return MariaDbTestSupport.createConnectionFactory(database);
69+
}
70+
71+
@Override
72+
protected String getCreateTableStatement() {
73+
return MariaDbTestSupport.CREATE_TABLE_LEGOSET_WITH_ID_GENERATION;
74+
}
75+
76+
@Override
77+
protected Class<? extends LegoSetRepository> getRepositoryInterfaceType() {
78+
return MySqlLegoSetRepository.class;
79+
}
80+
81+
interface MySqlLegoSetRepository extends LegoSetRepository {
82+
83+
@Override
84+
@Query("SELECT name FROM legoset")
85+
Flux<Named> findAsProjection();
86+
87+
@Override
88+
@Query("SELECT * FROM legoset WHERE manual = :manual")
89+
Mono<LegoSet> findByManual(int manual);
90+
91+
@Override
92+
@Query("SELECT id FROM legoset")
93+
Flux<Integer> findAllIds();
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2019-2023 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+
* https://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+
package org.springframework.data.r2dbc.testing;
17+
18+
import io.r2dbc.spi.ConnectionFactory;
19+
import io.r2dbc.spi.ConnectionFactoryOptions;
20+
import lombok.SneakyThrows;
21+
22+
import java.util.function.Supplier;
23+
import java.util.stream.Stream;
24+
25+
import javax.sql.DataSource;
26+
27+
import org.mariadb.jdbc.MariaDbDataSource;
28+
import org.mariadb.r2dbc.MariadbConnectionFactoryProvider;
29+
import org.springframework.data.r2dbc.testing.ExternalDatabase.ProvidedDatabase;
30+
import org.testcontainers.containers.MariaDBContainer;
31+
import org.testcontainers.utility.DockerImageName;
32+
33+
/**
34+
* Utility class for testing against MariaDB.
35+
*
36+
* @author Mark Paluch
37+
* @author Jens Schauder
38+
*/
39+
public class MariaDbTestSupport {
40+
41+
private static ExternalDatabase testContainerDatabase;
42+
43+
public static final String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
44+
+ " id integer PRIMARY KEY,\n" //
45+
+ " name varchar(255) NOT NULL,\n" //
46+
+ " manual integer NULL\n," //
47+
+ " cert varbinary(255) NULL\n" //
48+
+ ") ENGINE=InnoDB;";
49+
50+
public static final String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
51+
+ " id integer AUTO_INCREMENT PRIMARY KEY,\n" //
52+
+ " name varchar(255) NOT NULL,\n" //
53+
+ " flag boolean NOT NULL,\n" //
54+
+ " manual integer NULL\n" //
55+
+ ") ENGINE=InnoDB;";
56+
57+
public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE `LegoSet` (\n" //
58+
+ " `Id` integer AUTO_INCREMENT PRIMARY KEY,\n" //
59+
+ " `Name` varchar(255) NOT NULL,\n" //
60+
+ " `Manual` integer NULL\n" //
61+
+ ") ENGINE=InnoDB;";
62+
63+
public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE `LegoSet`";
64+
65+
/**
66+
* Returns a database either hosted locally at {@code localhost:3306/mysql} or running inside Docker.
67+
*
68+
* @return information about the database. Guaranteed to be not {@literal null}.
69+
*/
70+
public static ExternalDatabase database() {
71+
72+
if (Boolean.getBoolean("spring.data.r2dbc.test.preferLocalDatabase")) {
73+
74+
return getFirstWorkingDatabase( //
75+
MariaDbTestSupport::local, //
76+
MariaDbTestSupport::testContainer //
77+
);
78+
} else {
79+
80+
return getFirstWorkingDatabase( //
81+
MariaDbTestSupport::testContainer, //
82+
MariaDbTestSupport::local //
83+
);
84+
}
85+
}
86+
87+
@SafeVarargs
88+
private static ExternalDatabase getFirstWorkingDatabase(Supplier<ExternalDatabase>... suppliers) {
89+
90+
return Stream.of(suppliers).map(Supplier::get) //
91+
.filter(ExternalDatabase::checkValidity) //
92+
.findFirst() //
93+
.orElse(ExternalDatabase.unavailable());
94+
}
95+
96+
/**
97+
* Returns a locally provided database .
98+
*/
99+
private static ExternalDatabase local() {
100+
101+
return ProvidedDatabase.builder() //
102+
.hostname("localhost") //
103+
.port(3306) //
104+
.database("mysql") //
105+
.username("root") //
106+
.password("my-secret-pw") //
107+
.jdbcUrl("jdbc:mariadb://localhost:3306/mysql") //
108+
.build();
109+
}
110+
111+
/**
112+
* Returns a database provided via Testcontainers.
113+
*/
114+
private static ExternalDatabase testContainer() {
115+
116+
if (testContainerDatabase == null) {
117+
118+
try {
119+
120+
String osArch = System.getProperty("os.arch");
121+
122+
DockerImageName armImageName = DockerImageName.parse("arm64v8/mariadb:10.3")
123+
.asCompatibleSubstituteFor("mariadb");
124+
125+
DockerImageName mariadb = DockerImageName.parse("mariadb").withTag("10.3.6");
126+
var container = new MariaDBContainer<>("aarch64".equals(osArch) ? armImageName : mariadb);
127+
128+
container.start();
129+
130+
testContainerDatabase = ProvidedDatabase.builder(container) //
131+
.username("root") //
132+
.database(container.getDatabaseName()) //
133+
.build();
134+
} catch (IllegalStateException ise) {
135+
// docker not available.
136+
testContainerDatabase = ExternalDatabase.unavailable();
137+
}
138+
}
139+
140+
return testContainerDatabase;
141+
}
142+
143+
/**
144+
* Creates a new R2DBC MariaDB {@link ConnectionFactory} configured from the {@link ExternalDatabase}.
145+
*/
146+
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
147+
148+
ConnectionFactoryOptions options = ConnectionUtils.createOptions("mariadb", database);
149+
return new MariadbConnectionFactoryProvider().create(options);
150+
}
151+
152+
/**
153+
* Creates a new {@link DataSource} configured from the {@link ExternalDatabase}.
154+
*/
155+
@SneakyThrows
156+
public static DataSource createDataSource(ExternalDatabase database) {
157+
158+
MariaDbDataSource dataSource = new MariaDbDataSource();
159+
160+
dataSource.setUser(database.getUsername());
161+
dataSource.setPassword(database.getPassword());
162+
dataSource.setUrl(
163+
String.format("jdbc:mariadb://%s:%d/%s?", database.getHostname(), database.getPort(), database.getDatabase()));
164+
165+
return dataSource;
166+
}
167+
}

0 commit comments

Comments
 (0)