Build against Borca snapshots.

Remove MySQL support as the MySQL driver is not yet published. Update groupId for Postgres driver to org.postgresql.

See #710
This commit is contained in:
Mark Paluch
2022-01-24 15:50:54 +01:00
parent 8893a4650b
commit 36a1400e55
12 changed files with 55 additions and 670 deletions

10
pom.xml
View File

@@ -33,7 +33,7 @@
<r2dbc-spi-test.version>0.8.5.RELEASE</r2dbc-spi-test.version>
<mssql-jdbc.version>7.1.2.jre8-preview</mssql-jdbc.version>
<mariadb-jdbc.version>2.5.4</mariadb-jdbc.version>
<r2dbc-releasetrain.version>Arabba-SR12</r2dbc-releasetrain.version>
<r2dbc-releasetrain.version>Borca-BUILD-SNAPSHOT</r2dbc-releasetrain.version>
<reactive-streams.version>1.0.3</reactive-streams.version>
<netty>4.1.63.Final</netty>
</properties>
@@ -224,7 +224,7 @@
<!-- R2DBC Drivers -->
<dependency>
<groupId>io.r2dbc</groupId>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<optional>true</optional>
</dependency>
@@ -241,12 +241,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb</groupId>
<artifactId>r2dbc-mariadb</artifactId>

View File

@@ -23,6 +23,8 @@ import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
/**
* Integration tests for {@link DatabasePopulator} using H2.
*
@@ -40,6 +42,10 @@ class H2DatabasePopulatorIntegrationTests extends AbstractDatabaseInitialization
return this.connectionFactory;
}
Resource usersSchema() {
return resource("users-schema-h2.sql");
}
@Test
void shouldRunScript() {

View File

@@ -27,6 +27,7 @@ import java.util.UUID;
import javax.sql.DataSource;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -146,6 +147,11 @@ public class MariaDbDatabaseClientIntegrationTests extends AbstractDatabaseClien
.verifyComplete();
}
@Override
@Test
@Disabled
public void insertTypedObjectWithBinary() {}
@Table("boolean_mapping")
@Data
static class BooleanMapping {

View File

@@ -1,186 +0,0 @@
/*
* 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.core;
import static org.assertj.core.api.Assertions.*;
import io.r2dbc.spi.ConnectionFactory;
import lombok.Data;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.dialect.MySqlDialect;
import org.springframework.data.r2dbc.testing.ExternalDatabase;
import org.springframework.data.r2dbc.testing.MySqlTestSupport;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Integration tests for {@link DatabaseClient} against MySQL.
*
* @author Mark Paluch
* @author Mingyuan Wu
*/
public class MySqlDatabaseClientIntegrationTests extends AbstractDatabaseClientIntegrationTests {
@RegisterExtension public static final ExternalDatabase database = MySqlTestSupport.database();
@Override
protected DataSource createDataSource() {
return MySqlTestSupport.createDataSource(database);
}
@Override
protected ConnectionFactory createConnectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
@Override
protected String getCreateTableStatement() {
return MySqlTestSupport.CREATE_TABLE_LEGOSET;
}
@Test // gh-166
public void considersBuiltInConverters() {
ConnectionFactory connectionFactory = createConnectionFactory();
JdbcTemplate jdbc = createJdbcTemplate(createDataSource());
try {
jdbc.execute("DROP TABLE boolean_mapping");
} catch (DataAccessException e) {}
jdbc.execute("CREATE TABLE boolean_mapping (id int, flag1 TINYINT, flag2 TINYINT)");
BooleanMapping mapping = new BooleanMapping();
mapping.setId(42);
mapping.setFlag1(true);
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.insert().into(BooleanMapping.class).using(mapping).then() //
.as(StepVerifier::create) //
.verifyComplete();
databaseClient.select().from(BooleanMapping.class).fetch().first() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual.isFlag1()).isTrue()) //
.verifyComplete();
}
@Test // gh-305
public void shouldApplyCustomConverters() {
ConnectionFactory connectionFactory = createConnectionFactory();
JdbcTemplate jdbc = createJdbcTemplate(createDataSource());
ReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(MySqlDialect.INSTANCE,
Arrays.asList(UuidToStringConverter.INSTANCE, StringToUuidConverter.INSTANCE));
try {
jdbc.execute("DROP TABLE uuid_type");
} catch (DataAccessException e) {}
jdbc.execute("CREATE TABLE uuid_type (id varchar(255), uuid_value varchar(255))");
UuidType uuidType = new UuidType();
uuidType.setId(UUID.randomUUID());
uuidType.setUuidValue(UUID.randomUUID());
DatabaseClient databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory)
.dataAccessStrategy(strategy).build();
databaseClient.insert().into(UuidType.class).using(uuidType).then() //
.as(StepVerifier::create) //
.verifyComplete();
databaseClient.select().from(UuidType.class).matching(Criteria.where("id").is(uuidType.getId())) //
.fetch().first() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual.getUuidValue()).isEqualTo(uuidType.getUuidValue())) //
.verifyComplete();
uuidType.setUuidValue(null);
databaseClient.update().table(UuidType.class).using(uuidType).then() //
.as(StepVerifier::create) //
.verifyComplete();
databaseClient.execute("SELECT * FROM uuid_type WHERE id = ?") //
.bind(0, uuidType.getId()) //
.as(UuidType.class) //
.fetch().first() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual.getUuidValue()).isNull()) //
.verifyComplete();
databaseClient.execute("SELECT * FROM uuid_type WHERE id in (:ids)") //
.bind("ids", Collections.singleton(uuidType.getId())) //
.as(UuidType.class) //
.fetch().first() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual.getUuidValue()).isNull()) //
.verifyComplete();
}
@Table("boolean_mapping")
@Data
static class BooleanMapping {
int id;
boolean flag1;
boolean flag2;
}
@Table("uuid_type")
@Data
static class UuidType {
@Id UUID id;
UUID uuidValue;
}
@WritingConverter
enum UuidToStringConverter implements Converter<UUID, String> {
INSTANCE;
@Override
public String convert(UUID uuid) {
return uuid.toString();
}
}
@ReadingConverter
enum StringToUuidConverter implements Converter<String, UUID> {
INSTANCE;
@Override
public UUID convert(String value) {
return UUID.fromString(value);
}
}
}

View File

@@ -1,79 +0,0 @@
/*
* 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.core;
import io.r2dbc.spi.ConnectionFactory;
import reactor.core.publisher.Mono;
import java.time.Duration;
import javax.sql.DataSource;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.data.r2dbc.testing.ExternalDatabase;
import org.springframework.data.r2dbc.testing.MySqlTestSupport;
import org.springframework.r2dbc.core.DatabaseClient;
/**
* Transactional integration tests for {@link DatabaseClient} against MySQL.
*
* @author Mark Paluch
*/
public class MySqlTransactionalDatabaseClientIntegrationTests
extends AbstractTransactionalDatabaseClientIntegrationTests {
@RegisterExtension public static final ExternalDatabase database = MySqlTestSupport.database();
@Override
protected DataSource createDataSource() {
return MySqlTestSupport.createDataSource(database);
}
@Override
protected ConnectionFactory createConnectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
@Override
protected String getCreateTableStatement() {
return MySqlTestSupport.CREATE_TABLE_LEGOSET;
}
@Override
protected Mono<Void> prepareForTransaction(DatabaseClient client) {
/*
* We have to execute a sql statement first.
* Otherwise MySql don't have a transaction id.
* And we need to delay emitting the result so that MySql has time to write the transaction id, which is done in
* batches every now and then.
* @see: https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-internal-data.html
*/
return client.sql(getInsertIntoLegosetStatement()) //
.bind(0, 42055) //
.bind(1, "SCHAUFELRADBAGGER") //
.bindNull(2, Integer.class) //
.fetch().rowsUpdated() //
.delayElement(Duration.ofMillis(50)) //
.then();
}
@Override
protected String getCurrentTransactionIdStatement() {
return "SELECT tx.trx_id FROM information_schema.innodb_trx tx WHERE tx.trx_mysql_thread_id = connection_id()";
}
}

View File

@@ -2,8 +2,6 @@ package org.springframework.data.r2dbc.dialect;
import static org.assertj.core.api.Assertions.*;
import dev.miku.r2dbc.mysql.MySqlConnectionConfiguration;
import dev.miku.r2dbc.mysql.MySqlConnectionFactory;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.mssql.MssqlConnectionConfiguration;
@@ -40,13 +38,10 @@ public class DialectResolverUnitTests {
MssqlConnectionFactory mssql = new MssqlConnectionFactory(MssqlConnectionConfiguration.builder().host("localhost")
.database("foo").username("bar").password("password").build());
H2ConnectionFactory h2 = new H2ConnectionFactory(H2ConnectionConfiguration.builder().inMemory("mem").build());
MySqlConnectionFactory mysql = MySqlConnectionFactory
.from(MySqlConnectionConfiguration.builder().host("localhost").username("mysql").build());
assertThat(DialectResolver.getDialect(postgres)).isEqualTo(PostgresDialect.INSTANCE);
assertThat(DialectResolver.getDialect(mssql)).isEqualTo(SqlServerDialect.INSTANCE);
assertThat(DialectResolver.getDialect(h2)).isEqualTo(H2Dialect.INSTANCE);
assertThat(DialectResolver.getDialect(mysql)).isEqualTo(MySqlDialect.INSTANCE);
}
@Test // gh-20, gh-104

View File

@@ -24,6 +24,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Value;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -60,6 +61,10 @@ import org.springframework.transaction.reactive.TransactionalOperator;
*/
public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestSupport {
static {
Hooks.onOperatorDebug();
}
@Autowired private LegoSetRepository repository;
@Autowired private ConnectionFactory connectionFactory;
protected JdbcTemplate jdbc;

View File

@@ -1,137 +0,0 @@
/*
* 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 lombok.AllArgsConstructor;
import lombok.Data;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
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.dao.DataAccessException;
import org.springframework.data.annotation.Id;
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.MySqlTestSupport;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory} against MySQL.
*
* @author Mark Paluch
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class MySqlR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIntegrationTests {
@RegisterExtension public static final ExternalDatabase database = MySqlTestSupport.database();
@Autowired DateTestsRepository dateTestsRepository;
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
includeFilters = { @Filter(classes = MySqlLegoSetRepository.class, type = FilterType.ASSIGNABLE_TYPE),
@Filter(classes = DateTestsRepository.class, type = FilterType.ASSIGNABLE_TYPE) })
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
@Bean
@Override
public ConnectionFactory connectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
}
@Override
protected DataSource createDataSource() {
return MySqlTestSupport.createDataSource(database);
}
@Override
protected ConnectionFactory createConnectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
@Override
protected String getCreateTableStatement() {
return MySqlTestSupport.CREATE_TABLE_LEGOSET_WITH_ID_GENERATION;
}
@Override
protected Class<? extends LegoSetRepository> getRepositoryInterfaceType() {
return MySqlLegoSetRepository.class;
}
@Test
public void shouldUserJsr310Types() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(createDataSource());
try {
jdbcTemplate.execute("DROP TABLE date_tests");
} catch (DataAccessException e) {}
jdbcTemplate.execute("CREATE TABLE date_tests (id int, created_timestamp TIMESTAMP, created_date datetime);");
dateTestsRepository.save(new DateTests(null, LocalDateTime.now(), LocalDateTime.now())).as(StepVerifier::create)
.expectNextCount(1).verifyComplete();
}
@Data
@AllArgsConstructor
static class DateTests {
@Id Integer id;
LocalDateTime createdTimestamp;
LocalDateTime createdDate;
}
interface MySqlLegoSetRepository 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();
}
interface DateTestsRepository extends ReactiveCrudRepository<DateTests, Integer> {
}
}

View File

@@ -1,95 +0,0 @@
/*
* 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.repository;
import io.r2dbc.spi.ConnectionFactory;
import java.util.Optional;
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.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.testing.ExternalDatabase;
import org.springframework.data.r2dbc.testing.MySqlTestSupport;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* Integration tests for {@link LegoSetRepository} with table and column names that contain upper and lower case
* characters against MySql.
*
* @author Jens Schauder
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class MySqlR2dbcRepositoryWithMixedCaseNamesIntegrationTests
extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests {
@RegisterExtension public static final ExternalDatabase database = MySqlTestSupport.database();
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE))
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
@Bean
@Override
public ConnectionFactory connectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
@Override
public R2dbcMappingContext r2dbcMappingContext(Optional<NamingStrategy> namingStrategy,
R2dbcCustomConversions r2dbcCustomConversions) {
R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions);
r2dbcMappingContext.setForceQuote(true);
return r2dbcMappingContext;
}
}
@Override
protected DataSource createDataSource() {
return MySqlTestSupport.createDataSource(database);
}
@Override
protected ConnectionFactory createConnectionFactory() {
return MySqlTestSupport.createConnectionFactory(database);
}
@Override
protected String getCreateTableStatement() {
return MySqlTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES;
}
@Override
protected String getDropTableStatement() {
return MySqlTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES;
}
}

View File

@@ -1,160 +0,0 @@
/*
* 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.testing;
import dev.miku.r2dbc.mysql.MySqlConnectionFactoryProvider;
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.testcontainers.containers.MySQLContainer;
import com.mysql.cj.jdbc.MysqlDataSource;
/**
* Utility class for testing against MySQL.
*
* @author Mark Paluch
* @author Bogdan Ilchyshyn
* @author Jens Schauder
*/
public class MySqlTestSupport {
private static ExternalDatabase testContainerDatabase;
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
+ " id integer PRIMARY KEY,\n" //
+ " version integer NULL,\n" //
+ " name varchar(255) NOT NULL,\n" //
+ " manual integer NULL\n," //
+ " cert varbinary(255) NULL\n" //
+ ") ENGINE=InnoDB;";
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
+ " id integer AUTO_INCREMENT PRIMARY KEY,\n" //
+ " version integer NULL,\n" //
+ " name varchar(255) NOT NULL,\n" //
+ " flag boolean 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 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( //
MySqlTestSupport::local, //
MySqlTestSupport::testContainer //
);
} else {
return getFirstWorkingDatabase( //
MySqlTestSupport::testContainer, //
MySqlTestSupport::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(3306) //
.database("mysql") //
.username("root") //
.password("my-secret-pw") //
.jdbcUrl("jdbc:mysql://localhost:3306/mysql?allowPublicKeyRetrieval=true") //
.build();
}
/**
* Returns a database provided via Testcontainers.
*/
private static ExternalDatabase testContainer() {
if (testContainerDatabase == null) {
try {
MySQLContainer container = new MySQLContainer("mysql:8.0.24");
container.start();
testContainerDatabase = ProvidedDatabase.builder(container) //
.database(container.getDatabaseName()) //
.username("root") //
.build();
} catch (IllegalStateException ise) {
// docker not available.
testContainerDatabase = ExternalDatabase.unavailable();
}
}
return testContainerDatabase;
}
/**
* Creates a new R2DBC MySQL {@link ConnectionFactory} configured from the {@link ExternalDatabase}.
*/
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
ConnectionFactoryOptions options = ConnectionUtils.createOptions("mysql", database);
return new MySqlConnectionFactoryProvider().create(options);
}
/**
* Creates a new {@link DataSource} configured from the {@link ExternalDatabase}.
*/
public static DataSource createDataSource(ExternalDatabase database) {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUser(database.getUsername());
dataSource.setPassword(database.getPassword());
dataSource.setURL(database.getJdbcUrl() + "?useSSL=false&allowPublicKeyRetrieval=true");
return dataSource;
}
}

View File

@@ -23,10 +23,12 @@ import io.r2dbc.spi.ConnectionMetadata;
import io.r2dbc.spi.IsolationLevel;
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Statement;
import io.r2dbc.spi.TransactionDefinition;
import io.r2dbc.spi.ValidationDepth;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -156,11 +158,17 @@ public class StatementRecorder implements ConnectionFactory {
}
class RecorderConnection implements Connection {
@Override
public Publisher<Void> beginTransaction() {
return createStatement("BEGIN").execute().then();
}
@Override
public Publisher<Void> beginTransaction(TransactionDefinition definition) {
return createStatement("BEGIN " + definition).execute().then();
}
@Override
public Publisher<Void> close() {
return createStatement("CLOSE").execute().then();
@@ -238,6 +246,16 @@ public class StatementRecorder implements ConnectionFactory {
return createStatement("SET AUTOCOMMIT " + autoCommit).execute().then();
}
@Override
public Publisher<Void> setLockWaitTimeout(Duration timeout) {
return createStatement("SET LOCK WAIT TIMEOUT " + timeout).execute().then();
}
@Override
public Publisher<Void> setStatementTimeout(Duration timeout) {
return createStatement("SET STATEMENT TIMEOUT " + timeout).execute().then();
}
@Override
public Publisher<Void> setTransactionIsolationLevel(IsolationLevel isolationLevel) {
return createStatement("SET TRANSACTION ISOLATION LEVEL " + isolationLevel.asSql()).execute().then();
@@ -307,6 +325,16 @@ public class StatementRecorder implements ConnectionFactory {
public Flux<Result> execute() {
return Flux.fromIterable(results).doOnSubscribe(subscription -> executedStatements.add(this));
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [sql='").append(sql).append('\'');
sb.append(", bindings=").append(bindings);
sb.append(']');
return sb.toString();
}
}
}

View File

@@ -0,0 +1,8 @@
DROP TABLE users IF EXISTS;
CREATE TABLE users
(
id INTEGER NOT NULL AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL
);