From 13b6e2de4d99b4406a65622e07f786cd10c4b528 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Fri, 25 Sep 2020 15:03:29 -0400 Subject: [PATCH] Add ability for user to utilize external DBs for Multi-DB sample Updated based on code review --- .../multiple-datasources/README.adoc | 25 +++- .../multiple-datasources/pom.xml | 4 + ...a => EmbeddedDataSourceConfiguration.java} | 7 +- .../ExternalDataSourceConfiguration.java | 71 ++++++++++ .../src/main/resources/application.properties | 2 + .../MultiDataSourcesApplicationTests.java | 2 +- ...tiDataSourcesExternalApplicationTests.java | 131 ++++++++++++++++++ 7 files changed, 237 insertions(+), 5 deletions(-) rename spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/{DataSourceConfiguration.java => EmbeddedDataSourceConfiguration.java} (86%) create mode 100644 spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/ExternalDataSourceConfiguration.java create mode 100644 spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesExternalApplicationTests.java diff --git a/spring-cloud-task-samples/multiple-datasources/README.adoc b/spring-cloud-task-samples/multiple-datasources/README.adoc index 63fa80fb..65d1b5f0 100644 --- a/spring-cloud-task-samples/multiple-datasources/README.adoc +++ b/spring-cloud-task-samples/multiple-datasources/README.adoc @@ -11,7 +11,9 @@ which one to be used for the Spring Cloud Task repository. * `MultipleDataSourcesApplication` - the Spring Boot Main Application. * `SampleCommandLineRunner` - the `CommandLineRunner` implementation for this task. It outputs the number of `DataSource` beans found in the context (should be 2). -* `DataSourceConfiguration` - Configures two `DataSource` beans. +* `EmbeddedDataSourceConfiguration` - Configures two `DataSource` beans using embedded databases. +* `ExternalDataSourceConfiguration` - Configures two `DataSource` beans using external databases. + * `CustomTaskConfigurer` - Uses a Spring `@Qualifier` to specify the correct `DataSource` to use. == Build: @@ -21,9 +23,26 @@ which one to be used for the Spring Cloud Task repository. $ mvn clean package ---- -== Run: +== Execute sample using 2 embedded databases (default): [source,shell,indent=2] ---- -$ java -jar target/multiple-datasources-2.2.2.RELEASE.jar +$ java -jar target/multiple-datasources-2.3.0-RELEASE.jar +---- + +== Execute sample using 2 external databases: + +Using the `external` profile, users will be able to establish both the default `spring.datasource` data source and a `second.datasource` data source. +For example: +[source,shell,indent=2] +---- +export spring_datasource_url= +export spring_datasource_username= +export spring_datasource_password= +export spring_datasource_driverClassName=org.mariadb.jdbc.Driver +export second_datasource_url=jdbc: +export second_datasource_username= +export second_datasource_password= +export second_datasource_driverClassName=org.mariadb.jdbc.Driver +java -jar target/multiple-datasources-2.3.0-RELEASE.jar --spring.profiles.active=external ---- diff --git a/spring-cloud-task-samples/multiple-datasources/pom.xml b/spring-cloud-task-samples/multiple-datasources/pom.xml index 2eae724b..bd2f3218 100644 --- a/spring-cloud-task-samples/multiple-datasources/pom.xml +++ b/spring-cloud-task-samples/multiple-datasources/pom.xml @@ -52,6 +52,10 @@ org.hsqldb hsqldb + + org.mariadb.jdbc + mariadb-java-client + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/DataSourceConfiguration.java b/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/EmbeddedDataSourceConfiguration.java similarity index 86% rename from spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/DataSourceConfiguration.java rename to spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/EmbeddedDataSourceConfiguration.java index 1194614e..b74f44a8 100644 --- a/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/DataSourceConfiguration.java +++ b/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/EmbeddedDataSourceConfiguration.java @@ -20,14 +20,19 @@ import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; /** + * Creates two data sources that use embedded databases. + * * @author Michael Minella + * @author Glenn Renfro */ @Configuration -public class DataSourceConfiguration { +@Profile("embedded") +public class EmbeddedDataSourceConfiguration { @Bean public DataSource dataSource() { diff --git a/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/ExternalDataSourceConfiguration.java b/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/ExternalDataSourceConfiguration.java new file mode 100644 index 00000000..d7e7891b --- /dev/null +++ b/spring-cloud-task-samples/multiple-datasources/src/main/java/io/spring/configuration/ExternalDataSourceConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018-2020 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 io.spring.configuration; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +/** + * Creates two data sources that use external databases. + * @author Glenn Renfro + */ +@Configuration +@Profile("external") +public class ExternalDataSourceConfiguration { + + @Bean(name = "springDataSourceProperties") + @ConfigurationProperties("spring.datasource") + @Primary + public DataSourceProperties springDataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean(name = "secondDataSourceProperties") + @ConfigurationProperties("second.datasource") + public DataSourceProperties myDataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean(name = "springDataSource") + @Primary + public DataSource dataSource(@Qualifier("springDataSourceProperties")DataSourceProperties springDataSourceProperties) { + return DataSourceBuilder.create().driverClassName(springDataSourceProperties.getDriverClassName()). + url(springDataSourceProperties.getUrl()). + password(springDataSourceProperties.getPassword()). + username(springDataSourceProperties.getUsername()). + build(); + } + + @Bean + public DataSource secondDataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) { + return DataSourceBuilder.create().driverClassName(secondDataSourceProperties.getDriverClassName()). + url(secondDataSourceProperties.getUrl()). + password(secondDataSourceProperties.getPassword()). + username(secondDataSourceProperties.getUsername()). + build(); + } +} diff --git a/spring-cloud-task-samples/multiple-datasources/src/main/resources/application.properties b/spring-cloud-task-samples/multiple-datasources/src/main/resources/application.properties index ad8e5130..c6d40075 100644 --- a/spring-cloud-task-samples/multiple-datasources/src/main/resources/application.properties +++ b/spring-cloud-task-samples/multiple-datasources/src/main/resources/application.properties @@ -1,2 +1,4 @@ spring.application.name=Demo Multiple DataSources Task logging.level.org.springframework.cloud.task=DEBUG + +spring.profiles.active=embedded diff --git a/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesApplicationTests.java b/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesApplicationTests.java index d71dfb07..ca73e232 100644 --- a/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesApplicationTests.java +++ b/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesApplicationTests.java @@ -35,7 +35,7 @@ public class MultiDataSourcesApplicationTests { @Test public void testTimeStampApp(CapturedOutput capturedOutput) throws Exception { - SpringApplication.run(MultipleDataSourcesApplication.class, new String[0]); + SpringApplication.run(MultipleDataSourcesApplication.class, "--spring.profiles.active=embedded"); String output = capturedOutput.toString(); diff --git a/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesExternalApplicationTests.java b/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesExternalApplicationTests.java new file mode 100644 index 00000000..d02bdde7 --- /dev/null +++ b/spring-cloud-task-samples/multiple-datasources/src/test/java/io/spring/MultiDataSourcesExternalApplicationTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2019 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 io.spring; + + +import java.sql.SQLException; + +import org.h2.tools.Server; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.util.SocketUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Glenn Renfro + */ +@ExtendWith({OutputCaptureExtension.class, SpringExtension.class}) +@SpringBootTest(classes = { MultiDataSourcesExternalApplicationTests.TaskLauncherConfiguration.class }) +public class MultiDataSourcesExternalApplicationTests { + private final static String DATASOURCE_URL; + + private final static String SECOND_DATASOURCE_URL; + + private final static String DATASOURCE_USER_NAME = "SA"; + + private final static String DATASOURCE_USER_PASSWORD = "''"; + + private final static String DATASOURCE_DRIVER_CLASS_NAME = "org.h2.Driver"; + + private static int randomPort; + + private static int secondRandomPort; + + static { + randomPort = SocketUtils.findAvailableTcpPort(); + DATASOURCE_URL = "jdbc:h2:tcp://localhost:" + randomPort + + "/mem:dataflow;DB_CLOSE_DELAY=-1;" + "DB_CLOSE_ON_EXIT=FALSE"; + secondRandomPort = SocketUtils.findAvailableTcpPort(); + SECOND_DATASOURCE_URL = "jdbc:h2:tcp://localhost:" + randomPort + + "/mem:dataflow;DB_CLOSE_DELAY=-1;" + "DB_CLOSE_ON_EXIT=FALSE"; + } + + + @Test + public void testTimeStampApp(CapturedOutput capturedOutput) throws Exception { + + SpringApplication.run(MultipleDataSourcesApplication.class, "--spring.profiles.active=external", + "--spring.datasource.url=" + DATASOURCE_URL, + "--spring.datasource.username=" + DATASOURCE_USER_NAME, + "--spring.datasource.password=" + DATASOURCE_USER_PASSWORD, + "--spring.datasource.driverClassName=" + DATASOURCE_DRIVER_CLASS_NAME, + "--second.datasource.url=" + SECOND_DATASOURCE_URL, + "--second.datasource.username=" + DATASOURCE_USER_NAME, + "--second.datasource.password=" + DATASOURCE_USER_PASSWORD, + "--second.datasource.driverClassName=" + DATASOURCE_DRIVER_CLASS_NAME); + + String output = capturedOutput.toString(); + + assertThat(output.contains("There are 2 DataSources within this application")) + .as("Unable to find CommandLineRunner output: " + output).isTrue(); + assertThat(output.contains("Creating: TaskExecution{")) + .as("Unable to find start task message: " + output).isTrue(); + assertThat(output.contains("Updating: TaskExecution")) + .as("Unable to find update task message: " + output).isTrue(); + } + + @Configuration + public static class TaskLauncherConfiguration { + + private static Server defaultServer; + + private static Server secondServer; + + @Bean + public Server initH2TCPServer() { + Server server = null; + try { + if (defaultServer == null) { + server = Server.createTcpServer("-ifNotExists", "-tcp", + "-tcpAllowOthers", "-tcpPort", String.valueOf(randomPort)) + .start(); + defaultServer = server; + } + } + catch (SQLException e) { + throw new IllegalStateException(e); + } + return defaultServer; + } + + @Bean + public Server initSecondH2TCPServer() { + Server server = null; + try { + if (secondServer == null) { + server = Server.createTcpServer("-ifNotExists", "-tcp", + "-tcpAllowOthers", "-tcpPort", String.valueOf(secondRandomPort)) + .start(); + secondServer = server; + } + } + catch (SQLException e) { + throw new IllegalStateException(e); + } + return secondServer; + } + } +}