Sử dụng nhiều DataSource với Spring Boot và RoutingDataSource
1. Mục tiêu của tài liệu
Đôi khi bạn cần tạo một ứng dụng Web kết nối vào nhiều cơ sở dữ liệu khác nhau. Và thật tốt là bạn có thể làm điều đó dễ dàng trong Spring Boot.
Spring cung cấp cho bạn lớp AbstractRoutingDataSource, bạn cần viết một class mở rộng từ class này. Routing Datasource chứa một bản đồ (Map) các Datasource thực sự.
Spring cung cấp cho bạn lớp AbstractRoutingDataSource, bạn cần viết một class mở rộng từ class này. Routing Datasource chứa một bản đồ (Map) các Datasource thực sự.
Chú ý: Routing Datasource cũng là một Datasource, nó là một Datasource đặc biệt.
Và DataSource Interceptor sẽ quyết định DataSource nào sẽ được sử dụng tương ứng với mỗi request từ phía người dùng.
Trong tài liệu này tôi mô phỏng một Website sử dụng 2 datasource. Mỗi datasource kết nối vào một Database khác nhau. Một database lưu trữ thông tin cho hệ thống Publisher (Nhà xuất bản) và một Database lưu trữ thông tin hệ thống Advertiser (Nhà quảng cáo).
Trong một số tình huống bạn cũng có thể xây dựng một website tin tức đa ngôn ngữ, bạn có thể tạo nhiều Database, mỗi database lưu trữ nội dung các bài viết của một ngôn ngữ cụ thể.
Trong một trường hợp khác, nếu ứng dụng của bạn cần nhiều DataSource, và mỗi chức năng (mỗi trang) có thể làm việc cùng lúc với nhiều DataSource bạn có thể tham khảo các hướng dẫn dưới đây:
2. Chuẩn bị Database
Tôi sẽ tạo ra 2 cơ sở dữ liệu, một database dành cho hệ thống PUBLISHER (Nhà xuất bản) và một database dành cho hệ thống ADVERTISER (Nhà quảng cáo). Hai Database này có thể không cùng một loại. Bạn có thể sử dụng Oracle, MySQL hoặc SQL Server, ...
Database1:
Hệ thống cở sở dữ liệu của PUBLISHER có một bảng Publishers.
Database1 - (MySQL, SQL Server, PostGres)
-- ===========================================
-- DATABASE FOR PUBLISHER SYSTEM
-- ===========================================
create table PUBLISHERS
(
ID Bigint,
NAME VARCHAR(255),
Primary key (ID)
);
insert into publishers (ID, NAME)
values (1, 'publisher 1');
insert into publishers (ID, NAME)
values (2, 'publisher 2');
Database1 - (ORACLE)
-- ===========================================
-- DATABASE FOR PUBLISHER SYSTEM
-- ===========================================
create table PUBLISHERS
(
ID NUMBER(19),
NAME VARCHAR(255),
Primary key (ID)
);
insert into publishers (ID, NAME)
values (1, 'publisher 1');
insert into publishers (ID, NAME)
values (2, 'publisher 2');
Commit;
Database 2:
Hệ thống cở sở dữ liệu của ADVERTISER có một bảng Advertisers.
Database2 (MySQL, SQL Server, PostGres)
-- ===========================================
-- DATABASE FOR ADVERTISER SYSTEM
-- ===========================================
create table ADVERTISERS
(
ID Bigint,
NAME VARCHAR(255),
Primary key (ID)
);
insert into advertisers (ID, NAME)
values (1, 'Advertiser 1');
insert into advertisers (ID, NAME)
values (2, 'Advertiser 2');
Database2 - (ORACLE)
-- ===========================================
-- DATABASE FOR ADVERTISER SYSTEM
-- ===========================================
create table ADVERTISERS
(
ID NUMBER(19),
NAME VARCHAR(255),
Primary key (ID)
);
insert into advertisers (ID, NAME)
values (1, 'Advertiser 1');
insert into advertisers (ID, NAME)
values (2, 'Advertiser 2');
Commit;
3. Tạo dự án Spring Boot
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.o7planning</groupId>
<artifactId>SpringBootRoutingDS</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootRoutingDS</name>
<description>Spring Boot + Multi DataSource + Routing DataSource</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- For Thymeleaf VIEW. Remove it if using Jsp VIEW -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SQL Server - Mssql-Jdbc driver -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SQL Server - JTDS driver -->
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<!-- For JSP VIEW (Need REMOVE spring-boot-starter-thymeleaf) -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- For JSP VIEW (Need REMOVE spring-boot-starter-thymeleaf) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<!-- Repository for ORACLE JDBC Driver -->
<repository>
<id>codelds</id>
<url>https://code.lds.org/nexus/content/groups/main-repo</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4. Cấu hình DataSource & RoutingDataSource
Thông tin các Database được cấu hình tại file datasource-cfg.properties.
datasource-cfg.properties (MySQL + MySQL)
# DataSource (PUBLISHER System).
spring.datasource.driver-class-name.1=com.mysql.jdbc.Driver
spring.datasource.url.1=jdbc:mysql://localhost:3306/publisher
spring.datasource.username.1=root
spring.datasource.password.1=12345
# DataSource (ADVERTISER System).
spring.datasource.driver-class-name.2=com.mysql.jdbc.Driver
spring.datasource.url.2=jdbc:mysql://localhost:3306/advertiser
spring.datasource.username.2=root
spring.datasource.password.2=12345
datasource-cfg.properties (SQL Server + SQL Server) (JTDS Driver)
# DataSource (PUBLISHER System).
spring.datasource.driver-class-name.1=net.sourceforge.jtds.jdbc.Driver
spring.datasource.url.1=jdbc:jtds:sqlserver://localhost:1433/publisher;instance=SQLEXPRESS
spring.datasource.username.1=sa
spring.datasource.password.1=12345
# DataSource (ADVERTISER System).
spring.datasource.driver-class-name.2=net.sourceforge.jtds.jdbc.Driver
spring.datasource.url.2=jdbc:jtds:sqlserver://localhost:1433/advertiser;instance=SQLEXPRESS
spring.datasource.username.2=sa
spring.datasource.password.2=12345
datasource-cfg.properties (SQL Server + SQL Server) (Mssql-Jdbc Driver)
# DataSource (PUBLISHER System).
spring.datasource.driver-class-name.1=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url.1=jdbc:sqlserver://tran-vmware-pc\\SQLEXPRESS:1433;databaseName=publisher
spring.datasource.username.1=sa
spring.datasource.password.1=12345
# DataSource (ADVERTISER System).
spring.datasource.driver-class-name.2=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url.2=jdbc:sqlserver://tran-vmware-pc\\SQLEXPRESS:1433;databaseName=advertiser
spring.datasource.username.2=sa
spring.datasource.password.2=12345
datasource-cfg.properties (ORACLE + ORACLE)
# DataSource (PUBLISHER System).
spring.datasource.driver-class-name.1=oracle.jdbc.driver.OracleDriver
spring.datasource.url.1=jdbc:oracle:thin:@localhost:1521:db12c
spring.datasource.username.1=publisher
spring.datasource.password.1=12345
# DataSource (ADVERTISER System).
spring.datasource.driver-class-name.2=oracle.jdbc.driver.OracleDriver
spring.datasource.url.2=jdbc:oracle:thin:@localhost:1521:db12c
spring.datasource.username.2=advertiser
spring.datasource.password.2=12345
datasource-cfg.properties (PostGres + PostGres)
# DataSource (PUBLISHER System).
spring.datasource.driver-class-name.1=org.postgresql.Driver
spring.datasource.url.1=jdbc:postgresql://tran-vmware-pc:5432/publisher
spring.datasource.username.1=postgres
spring.datasource.password.1=12345
# DataSource (ADVERTISER System).
spring.datasource.driver-class-name.2=org.postgresql.Driver
spring.datasource.url.2=jdbc:postgresql://tran-vmware-pc:5432/advertiser
spring.datasource.username.2=postgres
spring.datasource.password.2=12345
Theo mặc định Spring Boot sẽ tự động cấu hình một DataSource, vì vậy bạn cần phải vô hiệu hóa nó để có thể tự cấu hình các DataSource của bạn. Các cấu hình tự động này bao gồm:
- DataSourceAutoConfiguration
- DataSourceTransactionManagerAutoConfiguration
SpringBootRoutingDsApplication.java
package org.o7planning.sbroutingds;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.o7planning.sbroutingds.routing.MyRoutingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
@SpringBootApplication
// Disable Auto Config DataSource & DataSourceTransactionManager
@EnableAutoConfiguration(exclude = { //
DataSourceAutoConfiguration.class, //
DataSourceTransactionManagerAutoConfiguration.class })
// Load to Environment
// (@see resources/datasource-cfg.properties).
@PropertySources({ @PropertySource("classpath:datasource-cfg.properties") })
public class SpringBootRoutingDsApplication {
// Lưu trữ các giá thuộc tính load bởi @PropertySource.
@Autowired
private Environment env;
public static void main(String[] args) {
SpringApplication.run(SpringBootRoutingDsApplication.class, args);
}
// Returns Routing DataSource (MyRoutingDataSource)
@Autowired
@Bean(name = "dataSource")
public DataSource getDataSource(DataSource dataSource1, DataSource dataSource2) {
System.out.println("## Create DataSource from dataSource1 & dataSource2");
MyRoutingDataSource dataSource = new MyRoutingDataSource();
dataSource.initDataSources(dataSource1, dataSource2);
return dataSource;
}
@Bean(name = "dataSource1")
public DataSource getDataSource1() throws SQLException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// See: datasouce-cfg.properties
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name.1"));
dataSource.setUrl(env.getProperty("spring.datasource.url.1"));
dataSource.setUsername(env.getProperty("spring.datasource.username.1"));
dataSource.setPassword(env.getProperty("spring.datasource.password.1"));
System.out.println("## DataSource1: " + dataSource);
return dataSource;
}
@Bean(name = "dataSource2")
public DataSource getDataSource2() throws SQLException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// See: datasouce-cfg.properties
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name.2"));
dataSource.setUrl(env.getProperty("spring.datasource.url.2"));
dataSource.setUsername(env.getProperty("spring.datasource.username.2"));
dataSource.setPassword(env.getProperty("spring.datasource.password.2"));
System.out.println("## DataSource2: " + dataSource);
return dataSource;
}
@Autowired
@Bean(name = "transactionManager")
public DataSourceTransactionManager getTransactionManager(DataSource dataSource) {
DataSourceTransactionManager txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource);
return txManager;
}
}
MyRoutingDataSource là một DataSource, nó chứa một bản đồ (Map) giữa các khóa và các Datasource thực sự.
MyRoutingDataSource.java
package org.o7planning.sbroutingds.routing;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
// Đây là một DataSource.
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
// Xem thêm: DataSourceInterceptor
String keyDS = (String) request.getAttribute("keyDS");
System.out.println("KeyDS=" + keyDS);
if (keyDS == null) {
keyDS = "PUBLISHER_DS";
}
return keyDS;
}
public void initDataSources(DataSource dataSource1, DataSource dataSource2) {
Map<Object, Object> dsMap = new HashMap<Object, Object>();
dsMap.put("PUBLISHER_DS", dataSource1);
dsMap.put("ADVERTISER_DS", dataSource2);
this.setTargetDataSources(dsMap);
}
}
5. DataSourceInterceptor & Cấu hình
DataSourceInterceptor sẽ phân tích URL của request, và quyết định Datasource nào sẽ được sử dụng.
DataSourceIntercetor.java
package org.o7planning.sbroutingds.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class DataSourceIntercetor extends HandlerInterceptorAdapter {
// Request:
// /publisher/list
// /advertiser/list
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String contextPath = request.getServletContext().getContextPath();
// /SomeContextPath/publisher
String prefixPublisher = contextPath + "/publisher";
// /SomeContextPath/advertiser
String prefixAdvertiser = contextPath + "/advertiser";
// /SomeContextPath/publisher/dashboard
// /SomeContextPath/advertiser/dashboard
String uri = request.getRequestURI();
System.out.println("URI:"+ uri);
if(uri.startsWith(prefixPublisher)) {
request.setAttribute("keyDS", "PUBLISHER_DS");
}
else if(uri.startsWith(prefixAdvertiser)) {
request.setAttribute("keyDS", "ADVERTISER_DS");
}
return true;
}
}
Trong WebMvcConfig bạn cần đăng ký DataSourceInterceptor.
WebMvcConfig.java
package org.o7planning.sbroutingds.config;
import org.o7planning.sbroutingds.interceptor.DataSourceIntercetor;
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 WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DataSourceIntercetor())//
.addPathPatterns("/publisher/*", "/advertiser/*");
}
}
6. DAO, Controller
DataDAO là lớp tiện ích, truy vấn dữ liệu từ Database.
DataDAO.java
package org.o7planning.sbroutingds.dao;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class DataDAO extends JdbcDaoSupport {
@Autowired
public DataDAO(DataSource dataSource) {
this.setDataSource(dataSource);
}
public List<String> queryPublishers() {
String sql = "Select name from Publishers";
List<String> list = this.getJdbcTemplate().queryForList(sql, String.class);
return list;
}
public List<String> queryAdvertisers() {
String sql = "Select name from Advertisers";
List<String> list = this.getJdbcTemplate().queryForList(sql, String.class);
return list;
}
}
MainController.java
package org.o7planning.sbroutingds.controller;
import java.sql.SQLException;
import java.util.List;
import org.o7planning.sbroutingds.dao.DataDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class MainController {
@Autowired
private DataDAO dataDAO;
@RequestMapping(value = { "/" }, method = RequestMethod.GET)
public String home(Model model) throws SQLException {
return "home";
}
@RequestMapping(value = { "/advertiser/list" }, method = RequestMethod.GET)
public String advertiser(Model model) throws SQLException {
List<String> list = dataDAO.queryAdvertisers();
model.addAttribute("advertisers", list);
return "advertiser";
}
@RequestMapping(value = { "/publisher/list" }, method = RequestMethod.GET)
public String publisher(Model model) throws SQLException {
List<String> list = dataDAO.queryPublishers();
model.addAttribute("publishers", list);
return "publisher";
}
}
7. Thymeleaf Template
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Home</title>
</head>
<body>
<h2>Multi DataSource with RoutingDataSource</h2>
<h3>/publisher/* ==> Using Publisher-DB</h3>
<ul>
<li><a th:href="@{/publisher/list}">Show Publisher List</a></li>
</ul>
<h3>/advertiser/* ==> Using Advertiser-DB</h3>
<ul>
<li> <a th:href="@{/advertiser/list}">Show Advertiser Lists</a> </li>
</ul>
</body>
</html>
publisher.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Publisher System</title>
</head>
<body>
<a th:href="@{/}">Home</a>
<h2>Using Publisher-DB</h2>
<ul>
<li th:each="publisher : ${publishers}" th:utext="${publisher}"></li>
</ul>
</body>
</html>
advertiser.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Advertiser System</title>
</head>
<body>
<a th:href="@{/}">Home</a>
<h2>Using Advertiser-DB</h2>
<ul>
<li th:each="advertiser : ${advertisers}" th:utext="${advertiser}"></li>
</ul>
</body>
</html>
Các hướng dẫn Spring Boot
- Cài đặt Spring Tool Suite cho Eclipse
- Hướng dẫn lập trình Spring cho người mới bắt đầu
- Hướng dẫn lập trình Spring Boot cho người mới bắt đầu
- Các thuộc tính thông dụng của Spring Boot
- Hướng dẫn sử dụng Spring Boot và Thymeleaf
- Hướng dẫn sử dụng Spring Boot và FreeMarker
- Hướng dẫn sử dụng Spring Boot và Groovy
- Hướng dẫn sử dụng Spring Boot và Mustache
- Hướng dẫn sử dụng Spring Boot và JSP
- Hướng dẫn sử dụng Spring Boot, Apache Tiles, JSP
- Sử dụng Logging trong Spring Boot
- Giám sát ứng dụng với Spring Boot Actuator
- Tạo ứng dụng web đa ngôn ngữ với Spring Boot
- Sử dụng nhiều ViewResolver trong Spring Boot
- Sử dụng Twitter Bootstrap trong Spring Boot
- Hướng dẫn và ví dụ Spring Boot Interceptor
- Hướng dẫn sử dụng Spring Boot, Spring JDBC và Spring Transaction
- Hướng dẫn và ví dụ Spring JDBC
- Hướng dẫn sử dụng Spring Boot, JPA và Spring Transaction
- Hướng dẫn sử dụng Spring Boot và Spring Data JPA
- Hướng dẫn sử dụng Spring Boot, Hibernate và Spring Transaction
- Tương tác Spring Boot, JPA và cơ sở dữ liệu H2
- Hướng dẫn sử dụng Spring Boot và MongoDB
- Sử dụng nhiều DataSource với Spring Boot và JPA
- Sử dụng nhiều DataSource với Spring Boot và RoutingDataSource
- Tạo ứng dụng Login với Spring Boot, Spring Security, Spring JDBC
- Tạo ứng dụng Login với Spring Boot, Spring Security, JPA
- Tạo ứng dụng đăng ký tài khoản với Spring Boot, Spring Form Validation
- Ví dụ OAuth2 Social Login trong Spring Boot
- Chạy các nhiệm vụ nền theo lịch trình trong Spring
- Ví dụ CRUD Restful Web Service với Spring Boot
- Ví dụ Spring Boot Restful Client với RestTemplate
- Ví dụ CRUD với Spring Boot, REST và AngularJS
- Bảo mật Spring Boot RESTful Service sử dụng Basic Authentication
- Bảo mật Spring Boot RESTful Service sử dụng Auth0 JWT
- Ví dụ Upload file với Spring Boot
- Ví dụ Download file với Spring Boot
- Ví dụ Upload file với Spring Boot và jQuery Ajax
- Ví dụ Upload file với Spring Boot và AngularJS
- Tạo ứng dụng Web bán hàng với Spring Boot, Hibernate
- Hướng dẫn và ví dụ Spring Email
- Tạo ứng dụng Chat đơn giản với Spring Boot và Websocket
- Triển khai ứng dụng Spring Boot trên Tomcat Server
- Triển khai ứng dụng Spring Boot trên Oracle WebLogic Server
- Cài đặt chứng chỉ SSL miễn phí Let's Encrypt cho Spring Boot
- Cấu hình Spring Boot chuyển hướng HTTP sang HTTPS
- Tìm nạp dữ liệu với Spring Data JPA DTO Projections
Show More