Весна — абстрактный источник данных маршрутизации
В этой статье мы рассмотрим 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")@EnableTransactionManagementpublic 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; @Componentpublic 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; @Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private DataSourceInterceptor dataSourceInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) {
РЕКОМЕНДУЕМЫЕ СТАТЬИ |