在上一篇咱們介紹了多數據源,可是咱們會發如今實際中咱們不多直接獲取數據源對象進行操做,咱們經常使用的是jdbcTemplate或者是jpa進行操做數據庫。那麼這一節咱們將要介紹怎麼進行多數據源動態切換。添加本文實現的代碼以後,只須要配置要數據源就能夠直接經過註解使用,在實際使用的時候特別的簡單。那麼本章主要分如下幾個步驟進行實戰。java
接下來咱們看看每一步具體的實現吧:mysql
(1)新建maven java project;web
新建一個maven project,取名爲:spring-boot-multi-dsspring
(2)在pom.xml添加依賴包;sql
在pom.xml文件中加入依賴的庫文件,主要是spring boot基本的,數據庫驅動,spring-jpa支持便可,具體pom.xml文件以下:數據庫
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"apache
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">tomcat
<modelVersion>4.0.0</modelVersion>mvc
<groupId>com.kfit</groupId>app
<artifactId>spring-boot-multids</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-multids</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- jdk版本號,這裏須要你本地進行的jdk進行修改,這裏angel使用的是1.8的版本. -->
<java.version>1.8</java.version>
</properties>
<!--
spring boot 父節點依賴,
引入這個以後相關的引入就不須要添加version配置,
spring boot會自動選擇最合適的版本進行添加。
在這裏使用的1.3.3版本,可能目前官方有最新的版本了,你們能夠
使用最新的版本。
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<dependencies>
<!-- 單元測試包,在這裏沒有使用到. -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- spring boot web支持:mvc,aop...
這個是最基本的,基本每個基本的demo都是須要引入的。
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驅動.
咱們的demo是多數據源,在這裏使用Mysql數據庫.
-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- spring jpa
spring jpa中帶有自帶的tomcat數據鏈接池;
在代碼中咱們也須要用到.
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>
在上面的配置文件中都有相應的解釋,你們能夠本身解讀下。
(3)編寫啓動類App.java
編寫spring boot的啓動類:
package com.kfit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
* @author Angel(QQ:412887952)
* @version v.0.1
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
(4)編寫配置文件application.properties;
在這裏主要是多數據源和jpa的配置:
src/main/resources/application.properties:
########################################################
###配置文件包括1個主數據源和多個數據源,
###其中主數據源在Spring中的beanName默認爲dataSource,
###另外幾個數據源的beanName分包爲:ds一、ds二、ds3
###其中datasource的type屬性能夠具體指定到咱們須要的數據源上面,
###不指定狀況下默認爲:org.apache.tomcat.jdbc.pool.DataSource
###固然你也能夠把這些數據源配置到主dataSource數據庫中,而後讀取數據庫生成多數據源。固然這樣作的必要性並不大,難不成數據源還會常常變嗎。
########################################################
# 主數據源,默認的
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
# 更多數據源
custom.datasource.names=ds1,ds2,ds3
#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds1.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=root
#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds2.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds2.username=root
custom.datasource.ds2.password=root
#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds3.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds3.username=root
custom.datasource.ds3.password=root
# 下面爲鏈接池的補充設置,應用到上面全部數據源中
spring.datasource.maximum-pool-size=100
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy #org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
(5)動態數據源路由類;
動態數據源能進行自動切換的核心就是spring底層提供了AbstractRoutingDataSource類進行數據源的路由的,咱們主要繼承這個類,實現裏面的方法便可實現咱們想要的,這裏主要是實現方法:determineCurrentLookupKey(),而此方法只須要返回一個數據庫的名稱便可,因此咱們核心的是有一個類來管理數據源的線程池,這個類纔是動態數據源的核心處理類。還有另外就是咱們使用aop技術在執行事務方法前進行數據源的切換。因此這裏有幾個須要編碼的類,具體以下:
動態數據源上下文>com.kfit.config.datasource.DynamicDataSourceContextHolder:
package com.kfit.config.datasource.dynamic;
import java.util.ArrayList;
import java.util.List;
/**
* 動態數據源上下文.
*
* @author Angel(QQ:412887952)
* @version v.0.1
*/
public class DynamicDataSourceContextHolder {
/*
* 當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,
* 因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/*
* 管理全部的數據源id;
* 主要是爲了判斷數據源是否存在;
*/
public static List<String> dataSourceIds = new ArrayList<String>();
/**
* 使用setDataSourceType設置當前的
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
/**
* 判斷指定DataSrouce當前是否存在
*
* @param dataSourceId
* @return
* @author SHANHY
* @create 2016年1月24日
*/
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
數據源路由類>com.kfit.config.datasource.dynamic.DynamicDataSource:
package com.kfit.config.datasource.dynamic;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 動態數據源.
* @author Angel(QQ:412887952)
* @version v.0.1
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/*
* 代碼中的determineCurrentLookupKey方法取得一個字符串,
* 該字符串將與配置文件中的相應字符串進行匹配以定位數據源,配置文件,即applicationContext.xml文件中須要要以下代碼:(non-Javadoc)
* @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
*/
@Override
protected Object determineCurrentLookupKey() {
/*
* DynamicDataSourceContextHolder代碼中使用setDataSourceType
* 設置當前的數據源,在路由類中使用getDataSourceType進行獲取,
* 交給AbstractRoutingDataSource進行注入使用。
*/
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
指定數據源註解類>com.kfit.config.datasource.dynamic.TargetDataSource:
package com.kfit.config.datasource.dynamic;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 在方法上使用,用於指定使用哪一個數據源
* @author Angel(QQ:412887952)
* @version v.0.1
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
切換數據源Advice>com.kfit.config.datasource.dynamic.DynamicDataSourceAspect:
package com.kfit.config.datasource.dynamic;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 切換數據源Advice
* @author Angel(QQ:412887952)
* @version v.0.1
*/
@Aspect
@Order(-10)//保證該AOP在@Transactional以前執行
@Component
public class DynamicDataSourceAspect {
/*
* @Before("@annotation(ds)")
* 的意思是:
*
* @Before:在方法執行以前進行執行:
* @annotation(targetDataSource):
* 會攔截註解targetDataSource的方法,不然不攔截;
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSourcetargetDataSource) throws Throwable {
//獲取當前的指定的數據源;
String dsId = targetDataSource.value();
//若是不在咱們注入的全部的數據源範圍以內,那麼輸出警告信息,系統自動使用默認的數據源。
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
System.err.println("數據源[{}]不存在,使用默認數據源 > {}"+targetDataSource.value()+point.getSignature());
} else {
System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+point.getSignature());
//找到的話,那麼設置到動態數據源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSourcetargetDataSource) {
System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+point.getSignature());
//方法執行完畢以後,銷燬當前數據源信息,進行垃圾回收。
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
(6)註冊多數據源;
以上都是動態數據源在注入的時候使用的代碼,其實很重要的一部分代碼就是註冊咱們在application.properties配置的多數據源,這纔是重點,這裏咱們使用
ImportBeanDefinitionRegistrar進行註冊,具體的代碼以下:
package com.kfit.config.datasource.dynamic;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
/**
* 動態數據源註冊;
* @author Angel(QQ:412887952)
* @version v.0.1
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
//如配置文件中未指定數據源類型,使用該默認值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 默認數據源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
/**
* 加載多數據源配置
*/
@Override
public void setEnvironment(Environment environment) {
System.out.println("DynamicDataSourceRegister.setEnvironment()");
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
/**
* 加載主數據源配置.
* @param env
*/
private void initDefaultDataSource(Environment env){
// 讀取主數據源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
//建立數據源;
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
/**
* 初始化更多數據源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initCustomDataSources(Environment env) {
// 讀取配置文件獲取更多數據源,也能夠經過defaultDataSource讀取數據庫獲取更多數據源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多個數據源
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
/**
* 建立datasource.
* @param dsMap
* @return
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null){
type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
returnfactory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
returnnull;
}
/**
* 爲DataSource綁定更多數據
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已經設置的屬性
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 將主數據源添加到更多數據源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多數據源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
// 建立DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//添加屬性:AbstractRoutingDataSource.defaultTargetDataSource
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
這裏還有一個步驟很重要,因爲咱們是使用的ImportBeanDefinitionRegistrar的方式進行註冊的,因此咱們須要在App.java類中使用@Import進行註冊,具體改造以後的App.java代碼以下:
package com.kfit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import com.kfit.config.datasource.dynamic.DynamicDataSourceRegister;
/**
*
* @author Angel(QQ:412887952)
* @version v.0.1
*/
@SpringBootApplication
//註冊動態多數據源
@Import({DynamicDataSourceRegister.class})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
(7)測試類測試;
核心的代碼都編寫完畢了,固然咱們確定是須要編寫代碼進行測試下,咱們才放心了。
package com.kfit.demo.bean;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* 測試demo類.
* @author Angel(QQ:412887952)
* @version v.0.1
*/
@Entity
public class Demo {
@Id @GeneratedValue
private longid;
private String name;
public long getId() {
return id;
}
public void setId(longid) {
this.id = id;
}
public String getName() {
return name;
}
publicvoid setName(String name) {
this.name = name;
}
@Override
public String toString() {
return"Demo [id=" + id + ", name=" + name + "]";
}
}
package com.kfit.demo.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import com.kfit.demo.bean.Demo;
@Service
public class TestDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 不指定數據源使用默認數據源
* @return
*/
public List<Demo> getList(){
String sql = "select *from Demo";
return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){
@Override
public Demo mapRow(ResultSet rs, introwNum) throws SQLException {
Demo demo = new Demo();
demo.setId(rs.getLong("id"));
demo.setName(rs.getString("name"));;
returndemo;
}
});
}
/**
* 指定數據源
* 在對應的service進行指定;
* @return
* @author SHANHY
* @create 2016年1月24日
*/
public List<Demo> getListByDs1(){
/*
* 這張表示複製的主庫到ds1的,在ds中並無此表.
* 須要本身本身進行復制,否則會報錯:Table 'test1.demo1' doesn't exist
*/
String sql = "select *from Demo1";
return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){
@Override
public Demo mapRow(ResultSet rs, introwNum) throws SQLException {
Demo demo = new Demo();
demo.setId(rs.getLong("id"));
demo.setName(rs.getString("name"));;
returndemo;
}
});
}
}
com.kfit.demo.service.TestService :
package com.kfit.demo.service;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.kfit.config.datasource.dynamic.TargetDataSource;
import com.kfit.demo.bean.Demo;
import com.kfit.demo.dao.TestDao;
@Service
public class TestService {
@Resource
private TestDao testDao;
/**
* 不指定數據源使用默認數據源
* @return
*/
public List<Demo> getList(){
returntestDao.getList();
}
/**
* 指定數據源
* @return
*/
@TargetDataSource("ds1")
public List<Demo> getListByDs1(){
returntestDao.getListByDs1();
}
}
com.kfit.demo.controller.TestController:
package com.kfit.demo.controller;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kfit.demo.bean.Demo;
import com.kfit.demo.service.TestService;
@RestController
public class TestController {
@Resource
private TestService testService;
@RequestMapping("/test1")
public String test(){
// for(Demo d:testService.getList()){
// System.out.println(d);
// }
for(Demo d:testService.getListByDs1()){
System.out.println(d);
}
return"ok";
}
}
好了,測試代碼就這麼多了,運行App.java進行測試把,訪問:
http://127.0.0.1:8080/test1 查看控制檯的打印。
這裏須要提醒下,這種方式spring-jpa的方式好像不能自動路由,博主打算在以後的一篇文章介紹spring-jpa多數據源的問題。
https://blog.csdn.net/QQQQQQ654/article/details/78025559