本文实现案例场景:

某系统除了须要从自己的主要数据库上读取和管理数据外。另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库。

为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中。加入本文实现的代码类后,仅仅须要配置好数据源就能够直接通过注解使用,简单方便。

一配置二使用

1. 启动类注冊动态数据源

2. 配置文件里配置多个数据源

3. 在须要的方法上使用注解指定数据源

1、在启动类加入 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})

@SpringBootApplication
@Import({DynamicDataSourceRegister.class}) // 注冊动态多数据源
public class SpringBootSampleApplication { // 省略其它代码
}

2、配置文件配置内容为:

(不包含项目中的其它配置,这里仅仅是数据源相关的)

# 主数据源,默认的
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456 # 很多其它数据源
custom.datasource.names=ds1,ds2
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456 custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456

3、用法

package org.springboot.sample.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List; import org.springboot.sample.datasource.TargetDataSource;
import org.springboot.sample.entity.Student;
import org.springboot.sample.mapper.StudentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service; /**
* Student Service
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月12日
*/
@Service
public class StudentService { @Autowired
private JdbcTemplate jdbcTemplate; // MyBatis的Mapper方法定义接口
@Autowired
private StudentMapper studentMapper; @TargetDataSource(name="ds2")
public List<Student> likeName(String name){
return studentMapper.likeName(name);
} public List<Student> likeNameByDefaultDataSource(String name){
return studentMapper.likeName(name);
} /**
* 不指定数据源使用默认数据源
*
* @return
* @author SHANHY
* @create 2016年1月24日
*/
public List<Student> getList(){
String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";
return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student();
stu.setId(rs.getInt("ID"));
stu.setAge(rs.getInt("AGE"));
stu.setName(rs.getString("NAME"));
stu.setSumScore(rs.getString("SCORE_SUM"));
stu.setAvgScore(rs.getString("SCORE_AVG"));
return stu;
} });
} /**
* 指定数据源
*
* @return
* @author SHANHY
* @create 2016年1月24日
*/
@TargetDataSource(name="ds1")
public List<Student> getListByDs1(){
String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";
return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student();
stu.setId(rs.getInt("ID"));
stu.setAge(rs.getInt("AGE"));
stu.setName(rs.getString("NAME"));
stu.setSumScore(rs.getString("SCORE_SUM"));
stu.setAvgScore(rs.getString("SCORE_AVG"));
return stu;
} });
} /**
* 指定数据源
*
* @return
* @author SHANHY
* @create 2016年1月24日
*/
@TargetDataSource(name="ds2")
public List<Student> getListByDs2(){
String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";
return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){ @Override
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student stu = new Student();
stu.setId(rs.getInt("ID"));
stu.setAge(rs.getInt("AGE"));
stu.setName(rs.getString("NAME"));
stu.setSumScore(rs.getString("SCORE_SUM"));
stu.setAvgScore(rs.getString("SCORE_AVG"));
return stu;
} });
}
}

要注意的是。在使用MyBatis时。注解@TargetDataSource 不能直接在接口类Mapper上使用。

按上面的代码中StudentMapper为接口,代码例如以下:

package org.springboot.sample.mapper;

import java.util.List;

import org.springboot.sample.entity.Student;

/**
* StudentMapper。映射SQL语句的接口,无逻辑实现
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月20日
*/
public interface StudentMapper { // 注解 @TargetDataSource 不能够在这里使用
List<Student> likeName(String name); Student getById(int id); String getNameById(int id); }


请将以下几个类放到Spring Boot项目中。

DynamicDataSource.java

DynamicDataSourceAspect.java

DynamicDataSourceContextHolder.java

DynamicDataSourceRegister.java

TargetDataSource.java

package org.springboot.sample.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* 动态数据源
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月22日
*/
public class DynamicDataSource extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
} }
package org.springboot.sample.datasource;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; /**
* 切换数据源Advice
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月23日
*/
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前运行
@Component
public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
String dsId = ds.name();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
} else {
logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
} @After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
} }
package org.springboot.sample.datasource;

import java.util.ArrayList;
import java.util.List; public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<>(); 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);
}
}
package org.springboot.sample.datasource;

import java.util.HashMap;
import java.util.Map; import javax.sql.DataSource; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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; /**
* 动态数据源注冊<br/>
* 启动动态数据源请在启动类中(如SpringBootSampleApplication)
* 加入 @Import(DynamicDataSourceRegister.class)
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月24日
*/
public class DynamicDataSourceRegister
implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues; // 如配置文件里未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// private static final Object DATASOURCE_TYPE_DEFAULT =
// "com.zaxxer.hikari.HikariDataSource"; // 数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<>(); @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
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();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry");
} /**
* 创建DataSource
*
* @param type
* @param driverClassName
* @param url
* @param username
* @param password
* @return
* @author SHANHY
* @create 2016年1月24日
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
try {
Object type = dsMap.get("type");
if (type == null)
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource Class<? extends DataSource> dataSourceType;
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); String driverClassName = dsMap.get("driver-class-name").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);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
} /**
* 载入多数据源配置
*/
@Override
public void setEnvironment(Environment env) {
initDefaultDataSource(env);
initCustomDataSources(env);
} /**
* 初始化主数据源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initDefaultDataSource(Environment env) {
// 读取主数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password")); defaultDataSource = buildDataSource(dsMap); dataBinder(defaultDataSource, env);
} /**
* 为DataSource绑定很多其它数据
*
* @param dataSource
* @param env
* @author SHANHY
* @create 2016年1月25日
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
//dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
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("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
} /**
* 初始化很多其它数据源
*
* @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);
}
} }
package org.springboot.sample.datasource;

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 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年1月23日
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}

本文代码博主是经过測试后没有问题才发出来共享给大家的。对于连接池參数配置会应用到全部数据源上。

比方配置一个:

spring.datasource.maximum-pool-size=80

那么我们全部的数据源都会自己主动应用上。


补充:

假设你使用的是SpringMVC,并集成了Shiro。一般按网上的配置你可能是:

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

那么你请不要这样做,请按以下方法配置:

    <!-- AOP式方法级权限检查  -->
<!-- 不要使用 DefaultAdvisorAutoProxyCreator 会出现二次代理的问题。这里不详述。 mark by shanhy 2016-05-15 -->
<aop:config proxy-target-class="true"/>
<!-- 或者你使用了 <aop:aspectj-autoproxy proxy-target-class="true" /> 也能够。 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

Spring Boot 动态数据源(多数据源自己主动切换)的更多相关文章

  1. Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多数据源

    本文是 Spring Boot 整合数据持久化方案的最后一篇,主要和大伙来聊聊 Spring Boot 整合 Jpa 多数据源问题.在 Spring Boot 整合JbdcTemplate 多数据源. ...

  2. Spring Boot + Mybatis 配置多数据源

    Spring Boot + Mybatis 配置多数据源 Mybatis拦截器,字段名大写转小写 package com.sgcc.tysj.s.common.mybatis; import java ...

  3. 13、Spring Boot 2.x 多数据源配置

    1.13 Spring Boot 2.x 多数据源配置 完整源码: Spring-Boot-Demos

  4. Spring Boot 2.x 多数据源配置之 MyBatis 篇

    场景假设:现有电商业务,商品和库存分别放在不同的库 配置数据库连接 app: datasource: first: driver-class-name: com.mysql.cj.jdbc.Drive ...

  5. Spring Boot动态注入删除bean

    Spring Boot动态注入删除bean 概述 因为如果采用配置文件或者注解,我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就得采用动态处理bean,包括:动态注入,动态删除. 动态注入 ...

  6. 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    [视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...

  7. (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  8. Spring Boot 动态数据源(Spring 注解数据源)

    本文实现案例场景:某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于 ...

  9. Spring Boot 动态数据源(多数据源自动切换)

    本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基 ...

随机推荐

  1. pytorch保存模型等相关参数,利用torch.save(),以及读取保存之后的文件

    本文分为两部分,第一部分讲如何保存模型参数,优化器参数等等,第二部分则讲如何读取. 假设网络为model = Net(), optimizer = optim.Adam(model.parameter ...

  2. [adb 命令学习篇] adb 命令总结

    https://testerhome.com/topics/2565 Android 常用 adb 命令总结 针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb ...

  3. jenkins执行自动化用例(详细、有用、mark 优先级高高高)

    http://blog.sina.com.cn/s/blog_68f262210102vx8o.html 第七章 测试用例接入jenkins自动运行 ------Web自动化测试之Webdriver+ ...

  4. 【BZOJ1208】宠物收养所(splay)

    题意:见题面 思路:因为每个时刻要么全是人要么全是宠物,所以可以一棵splay解决 维护单点插入,单点删除,求前驱,求后继即可 ..,..]of longint; num,fa:..]of longi ...

  5. android之总结(一)——原

    1,TextView 中实现跑马灯,需求:文字左边留置一段空白,不需要紧靠在左边:设置android:padding android:padding和android:layout_margin这个地方 ...

  6. python--输出自己需要的字符串连接的的方式

    python中有很多字符串连接方式,今天在写代码,顺便总结一下,从最原始的字符串连接方式到字符串列表连接,大家感受下: 最原始的字符串连接方式:str1 + str2 python 新字符串连接语法: ...

  7. (2)git本地生成SSH关联github

    1.安装git 2.打开 Git Bash 输入ssh ,查看是否安装了ssh 这个界面是安装了的意思 3.生成ssh 输入ssh-keygen -t rsa 指令, 再连续按三次回车 会生成两个文件 ...

  8. [Bzoj3206][Apio2013]道路费用(kruscal)(缩点)

    3206: [Apio2013]道路费用 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 536  Solved: 252[Submit][Status ...

  9. DDCTF2019逆向分析前俩题WriteUP

    DDCTF2019 笔者做了前俩道题.冷不丁过去一个月了.现在在此做一下WriteUp:题目链接: 1:题目1 2:题目2 reverse1:writeup: 1.程序打开后如下所示 2.查壳结果为U ...

  10. Java中Class.this和this的区别(转)

    当inner class(内部类)必顺使用到outer class(外部类)的this instance(实例)时,或者匿名内部类要使用外部类的实例. 例: class Outer{ String d ...