Весна — абстрактный источник данных маршрутизации
В этой статье мы рассмотрим Spring AbstractRoutingDatasource как абстрактную реализацию DataSource, которая динамически определяет фактический DataSource на основе текущего контекста. Теперь этот вопрос придет нам в голову, когда он нам может понадобиться, т.е. когда мы должны перейти к реализации AbstractRoutingDatasource. Иногда у нас есть требование динамически переключать базы данных на основе филиала/региона/локали или любого другого параметра и выполнять операцию на основе запроса. Итак, давайте разберемся с реализацией AbstractRoutingDatasource на примере. Предположим, у нас есть два филиала GeeksForGeeks, то есть Noida и Bangalore, и по одной базе данных для каждого филиала. Нам нужно получить детали из базы данных Noida, если запрос поступает из филиала Noida, а если запрос поступает из филиала Bangalore, то из базы данных Bangalore.
В этом руководстве мы предоставим конечную точку REST, которая подключается либо к noidadb, либо к bangaloredb, и будет извлекать информацию о стажерах из таблицы на основе запроса и возвращать объекты JSON. Здесь мы создадим конечную точку: http://localhost:8080/interns. и мы реализуем AbstractRoutingDatasource в приложении. Когда мы доберемся до конечной точки http://localhost:8080/interns из филиала Bangalore, мы получим приведенный ниже ответ, который представляет собой не что иное, как записи из базы данных bangaloredb.
[ { "id": 100, "name": "Bishal Dubey", "address": "Dibrugarh, Assam", "dob": "11-11-1994", "branch": "bangalore" }, { "id": 101, "name": "Bikash Dubey", "address": "Dibrugarh, Assam", "dob": "07-03-1997", "branch": "bangalore" }, { "id": 102, "name": "Nisha Ojha", "address": "Makum, Assam", "dob": "31-08-1997", "branch": "bangalore" } ]
Когда мы доберемся до конечной точки http://localhost:8080/interns из ветки Noida, мы получим приведенный ниже ответ, который представляет собой не что иное, как записи из базы данных noidadb.
[ { "id": 1, "name": "Neha Dubey", "address": "Doomdooma, Assam", "dob": "31-08-1992", "branch": "noida" }, { "id": 2, "name": "Roshan Ojha", "address": "Makum, Assam", "dob": "22-10-1998", "branch": "noida" }, { "id": 3, "name": "Muskan Pandey", "address": "Margherita, Assam", "dob": "12-05-2000", "branch": "noida" } ]
Note: Whenever we will hit the endpoint then we will pass one extra parameter in the request header named “branch” with the value Noida/Bangalore.
Теперь давайте перейдем к реализации AbstractRoutingDatasource, мы будем действовать шаг за шагом.
Пошаговая реализация
Структура проекта:
Создайте проект maven с необходимыми зависимостями в файле pom.xml .
XML
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.2.2.RELEASE</ version > < relativePath /> </ parent > < groupId >com.gfg.scripter</ groupId > < artifactId >scripter-datasource-routing</ artifactId > < version >0.0.1-SNAPSHOT</ version > < properties > < java.version >1.8</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-data-jpa</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-devtools</ artifactId > < scope >runtime</ scope > < optional >true</ optional > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < scope >runtime</ scope > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-configuration-processor</ artifactId > < optional >true</ optional > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
Настройте источники базы данных в application.properties как для noidadb, так и для bangaloredb с несколькими другими конфигурациями.
# database details for noida branch noida.datasource.url=jdbc:mysql://localhost:3306/noidadb?createDatabaseIfNotExist=true noida.datasource.username=root noida.datasource.password=root # database details for bangalore branch bangalore.datasource.url=jdbc:mysql://localhost:3306/bangaloredb?createDatabaseIfNotExist=true bangalore.datasource.username=root bangalore.datasource.password=root # JPA property settings spring.jpa.database=mysql spring.jpa.hibernate.ddl-auto=update spring.jpa.generate-ddl=true spring.jpa.show-sql=true
Создайте сущность стажера , которая будет использоваться для ссылки на стажера в обеих базах данных.
Java
package com.gfg.scripter.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table (name = "intern" ) public class Intern { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; private String name; private String address; private String dob; private String branch; // Generate Getters and Setters } |
Создайте контекст источника данных , т.е. это не что иное, как перечисление/константа, которая содержит имя обеих ветвей gfg. Здесь AbstractRoutingDataSource нужна часть информации, чтобы решить, к какой базе данных направить маршрут, и здесь этот контекст источника данных поможет.
Java
package com.gfg.scripter.constant; public enum BranchEnum { NOIDA, BANGALORE } |
Теперь мы создадим Context Holder , который будет содержать текущий контекст как ссылку ThreadLocal.
Java
package com.gfg.scripter.config; import com.gfg.scripter.constant.BranchEnum; public class BranchContextHolder { private static ThreadLocal<BranchEnum> threadLocal = new ThreadLocal<>(); public static void setBranchContext(BranchEnum branchEnum) { threadLocal.set(branchEnum); } public static BranchEnum getBranchContext() { return threadLocal.get(); } public static void clearBranchContext() { threadLocal.remove(); } } |
Создайте класс DataSource Routing , который расширит класс AbstractRoutingDatasource и будет содержать карту реальных источников данных.
Java
package com.gfg.scripter.config; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import com.gfg.scripter.constant.BranchEnum; public class DataSourceRouting extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return BranchContextHolder.getBranchContext(); } public void initDatasource(DataSource bangaloreDataSource, DataSource noidaDataSource) { Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(BranchEnum.NOIDA, noidaDataSource); dataSourceMap.put(BranchEnum.BANGALORE, bangaloreDataSource); this .setTargetDataSources(dataSourceMap); // Here we have to provide default datasource details. this .setDefaultTargetDataSource(noidaDataSource); } } |
Теперь мы создадим конфигурацию DataSource , т.е. объект/компонент баз данных noida/bangalore и другие необходимые конфигурации, т.е. диспетчер транзакций и диспетчер сущностей, которые будут указывать на сущность Intern и репозиторий Intern.
Java
package com.gfg.scripter.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.gfg.scripter.entity.Intern; @Configuration @EnableJpaRepositories (basePackages = "com.gfg.scripter.repository" , transactionManagerRef = "transcationManager" , entityManagerFactoryRef = "entityManager" ) @EnableTransactionManagement public class DataSourceConfig { @Bean @Primary @Autowired public DataSource dataSource() { DataSourceRouting routingDataSource = new DataSourceRouting(); routingDataSource.initDatasource(noidaDataSource(), bangaloreDataSource()); return routingDataSource; } @Bean @ConfigurationProperties (prefix = "noida.datasource" ) public DataSource noidaDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties (prefix = "bangalore.datasource" ) public DataSource bangaloreDataSource() { return DataSourceBuilder.create().build(); } @Bean (name = "entityManager" ) public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) { return builder.dataSource(dataSource()).packages(Intern. class ).build(); } @Bean (name = "transcationManager" ) public JpaTransactionManager transactionManager( @Autowired @Qualifier ( "entityManager" ) LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { return new JpaTransactionManager(entityManagerFactoryBean.getObject()); } } |
Теперь мы создадим еще один класс, т. е. DataSource Interceptor , который будет перехватывать каждый запрос и извлекать информацию о ветке из заголовка запроса и помещать ее в держатель контекста, который мы уже создали, т. е. BranchContextHolder, который будет переключать источник данных. После этого зарегистрируем этот перехватчик в WebMvcConfigurer.
Java
package com.gfg.scripter.config; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.gfg.scripter.constant.BranchEnum; @Component public class DataSourceInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String branch = request.getHeader( "branch" ); if (BranchEnum.BANGALORE.toString().equalsIgnoreCase(branch)) BranchContextHolder.setBranchContext(BranchEnum.BANGALORE); else BranchContextHolder.setBranchContext(BranchEnum.NOIDA); return super .preHandle(request, response, handler); } } |
Java
package com.gfg.scripter.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private DataSourceInterceptor dataSourceInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) {
РЕКОМЕНДУЕМЫЕ СТАТЬИ |