diff --git a/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDao.java b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDao.java new file mode 100644 index 0000000000..180f54326e --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDao.java @@ -0,0 +1,29 @@ +package org.baeldung.dsrouting; + +import javax.sql.DataSource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +/** + * Database access code for datasource routing example. + */ +public class ClientDao { + + private static final String SQL_GET_CLIENT_NAME = "select name from client"; + + private final JdbcTemplate jdbcTemplate; + + public ClientDao(DataSource datasource) { + this.jdbcTemplate = new JdbcTemplate(datasource); + } + + public String getClientName() { + return this.jdbcTemplate.query(SQL_GET_CLIENT_NAME, rowMapper) + .get(0); + } + + private static RowMapper rowMapper = (rs, rowNum) -> { + return rs.getString("name"); + }; +} diff --git a/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDataSourceRouter.java b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDataSourceRouter.java new file mode 100644 index 0000000000..997e461cde --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDataSourceRouter.java @@ -0,0 +1,14 @@ +package org.baeldung.dsrouting; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * Returns thread bound client lookup key for current context. + */ +public class ClientDataSourceRouter extends AbstractRoutingDataSource { + + @Override + protected Object determineCurrentLookupKey() { + return ClientDatabaseContextHolder.getClientDatabase(); + } +} diff --git a/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabase.java b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabase.java new file mode 100644 index 0000000000..6c87cb3dde --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabase.java @@ -0,0 +1,7 @@ +package org.baeldung.dsrouting; + +public enum ClientDatabase { + + ACME_WIDGETS, WIDGETS_ARE_US, WIDGET_DEPOT + +} diff --git a/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabaseContextHolder.java b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabaseContextHolder.java new file mode 100644 index 0000000000..c08559e877 --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientDatabaseContextHolder.java @@ -0,0 +1,26 @@ +package org.baeldung.dsrouting; + +import org.springframework.util.Assert; + +/** + * Thread shared context to point to the datasource which should be used. This + * enables context switches between different clients. + */ +public class ClientDatabaseContextHolder { + + private static final ThreadLocal CONTEXT = new ThreadLocal<>(); + + public static void set(ClientDatabase clientDatabase) { + Assert.notNull(clientDatabase, "clientDatabase cannot be null"); + CONTEXT.set(clientDatabase); + } + + public static ClientDatabase getClientDatabase() { + return CONTEXT.get(); + } + + public static void clear() { + CONTEXT.remove(); + } + +} diff --git a/spring-boot/src/main/java/org/baeldung/dsrouting/ClientService.java b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientService.java new file mode 100644 index 0000000000..4b63c6333c --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/dsrouting/ClientService.java @@ -0,0 +1,21 @@ +package org.baeldung.dsrouting; + +/** + * Service layer code for datasource routing example. Here, the service methods are responsible + * for setting and clearing the context. + */ +public class ClientService { + + private final ClientDao clientDao; + + public ClientService(ClientDao clientDao) { + this.clientDao = clientDao; + } + + public String getClientName(ClientDatabase clientDb) { + ClientDatabaseContextHolder.set(clientDb); + String clientName = this.clientDao.getClientName(); + ClientDatabaseContextHolder.clear(); + return clientName; + } +} diff --git a/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTestConfiguration.java b/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTestConfiguration.java new file mode 100644 index 0000000000..09382c4ca0 --- /dev/null +++ b/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTestConfiguration.java @@ -0,0 +1,64 @@ +package org.baeldung.dsrouting; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +@Configuration +public class DataSourceRoutingTestConfiguration { + + @Bean + public ClientService clientService() { + return new ClientService(new ClientDao(clientDatasource())); + } + + @Bean + public DataSource clientDatasource() { + Map targetDataSources = new HashMap<>(); + DataSource acmeWidgetsDatasource = acmeWidgetsDatasource(); + DataSource widgetsAreUsDatasource = widgetsAreUsDatasource(); + DataSource widgetsDepotDatasource = widgetsDepotDatasource(); + targetDataSources.put(ClientDatabase.ACME_WIDGETS, acmeWidgetsDatasource); + targetDataSources.put(ClientDatabase.WIDGETS_ARE_US, widgetsAreUsDatasource); + targetDataSources.put(ClientDatabase.WIDGET_DEPOT, widgetsDepotDatasource); + + ClientDataSourceRouter clientRoutingDatasource = new ClientDataSourceRouter(); + clientRoutingDatasource.setTargetDataSources(targetDataSources); + clientRoutingDatasource.setDefaultTargetDataSource(acmeWidgetsDatasource); + return clientRoutingDatasource; + } + + private DataSource acmeWidgetsDatasource() { + EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder(); + EmbeddedDatabase embeddedDb = dbBuilder.setType(EmbeddedDatabaseType.H2) + .setName("ACMEWIDGETS") + .addScript("classpath:dsrouting-db.sql") + .build(); + return embeddedDb; + } + + private DataSource widgetsAreUsDatasource() { + EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder(); + EmbeddedDatabase embeddedDb = dbBuilder.setType(EmbeddedDatabaseType.H2) + .setName("WIDGETSAREUS") + .addScript("classpath:dsrouting-db.sql") + .build(); + return embeddedDb; + } + + private DataSource widgetsDepotDatasource() { + EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder(); + EmbeddedDatabase embeddedDb = dbBuilder.setType(EmbeddedDatabaseType.H2) + .setName("WIDGETDEPOT") + .addScript("classpath:dsrouting-db.sql") + .build(); + return embeddedDb; + } +} diff --git a/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTests.java b/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTests.java new file mode 100644 index 0000000000..f19871702e --- /dev/null +++ b/spring-boot/src/test/java/org/baeldung/dsrouting/DataSourceRoutingTests.java @@ -0,0 +1,62 @@ +package org.baeldung.dsrouting; + +import static org.junit.Assert.assertEquals; + +import javax.sql.DataSource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DataSourceRoutingTestConfiguration.class) +public class DataSourceRoutingTests { + + @Autowired + DataSource routingDatasource; + + @Autowired + ClientService clientService; + + @Before + public void setup() { + final String SQL_ACME_WIDGETS = "insert into client (id, name) values (1, 'ACME WIDGETS')"; + final String SQL_WIDGETS_ARE_US = "insert into client (id, name) values (2, 'WIDGETS ARE US')"; + final String SQL_WIDGET_DEPOT = "insert into client (id, name) values (3, 'WIDGET DEPOT')"; + + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(routingDatasource); + + ClientDatabaseContextHolder.set(ClientDatabase.ACME_WIDGETS); + jdbcTemplate.execute(SQL_ACME_WIDGETS); + ClientDatabaseContextHolder.clear(); + + ClientDatabaseContextHolder.set(ClientDatabase.WIDGETS_ARE_US); + jdbcTemplate.execute(SQL_WIDGETS_ARE_US); + ClientDatabaseContextHolder.clear(); + + ClientDatabaseContextHolder.set(ClientDatabase.WIDGET_DEPOT); + jdbcTemplate.execute(SQL_WIDGET_DEPOT); + ClientDatabaseContextHolder.clear(); + } + + @Test + public void givenClientDbs_whenContextsSwitch_thenRouteToCorrectDatabase() throws Exception { + + // test ACME WIDGETS + String clientName = clientService.getClientName(ClientDatabase.ACME_WIDGETS); + assertEquals(clientName, "ACME WIDGETS"); + + // test WIDGETS_ARE_US + clientName = clientService.getClientName(ClientDatabase.WIDGETS_ARE_US); + assertEquals(clientName, "WIDGETS ARE US"); + + // test WIDGET_DEPOT + clientName = clientService.getClientName(ClientDatabase.WIDGET_DEPOT); + assertEquals(clientName, "WIDGET DEPOT"); + } +} diff --git a/spring-boot/src/test/resources/dsrouting-db.sql b/spring-boot/src/test/resources/dsrouting-db.sql new file mode 100644 index 0000000000..c9ca52907a --- /dev/null +++ b/spring-boot/src/test/resources/dsrouting-db.sql @@ -0,0 +1,5 @@ +create table client ( + id numeric, + name varchar(50), + constraint pk_client primary key (id) +); \ No newline at end of file