Spring Boot 动态数据源(多数据源自己主动切换)
本文实现案例场景:
某系统除了须要从自己的主要数据库上读取和管理数据外。另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库。
为了在开发中以最简单的方法使用,本文基于注解和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=123456custom.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 {@Autowiredprivate JdbcTemplate jdbcTemplate;// MyBatis的Mapper方法定义接口@Autowiredprivate 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>(){@Overridepublic 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>(){@Overridepublic 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>(){@Overridepublic 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 {@Overrideprotected 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 DynamicDataSourceRegisterimplements 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<>();@Overridepublic 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);}// 创建DynamicDataSourceGenericBeanDefinition 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;// 默认DataSourceClass<? 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;}/*** 载入多数据源配置*/@Overridepublic 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);//falsedataBinder.setIgnoreInvalidFields(false);//falsedataBinder.setIgnoreUnknownFields(true);//trueif(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 动态数据源(多数据源自己主动切换)相关推荐
- spring boot 动态切换数据源实现多租户开发
之前的文章有介绍过spring boot 动态切换数据源spring boot 动态切换数据源(数据源信息从数据库中读取)_lgq2016的博客-CSDN博客,今天简单介绍一下动态数据源切换实战,主要 ...
- 13、Spring Boot 2.x 多数据源配置
1.13 Spring Boot 2.x 多数据源配置 完整源码: Spring-Boot-Demos 转载于:https://www.cnblogs.com/Grand-Jon/p/9999779. ...
- Spring Boot 2.0 多数据源编程 jdbcUrl is required with driverClassName
转载:https://my.oschina.net/chinesedragon/blog/1647846 Spring Boot 2.0 多数据源编程 在Spring Boot 1.5.x之前,多数据 ...
- Spring Boot整合Jpa多数据源
Spring Boot整合Jpa多数据源 本文是Spring Boot整合数据持久化方案的最后一篇,主要和大伙来聊聊Spring Boot整合Jpa多数据源问题.在Spring Boot整合JbdcT ...
- Spring Boot 动态数据源(Spring 注解数据源)
本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基 ...
- spring boot 动态切换数据源(数据源信息从数据库中读取)
项目要求从多个源库(oracle,haha,pg)里面读取schema,table,字段等信息,spring提供了AbstractRoutingDataSource类实现动态数据源,下面就简单介绍一下 ...
- spring boot 动态数据源
由于项目中要用到spring boot结合mybatis做一个动态的数据源,所以自己做了一个,也踩了很多坑,这里把成果分享出来.如果是1.x的springboot版本可以看前面的,如果是2.x版本的可 ...
- Spring Boot骚操作-多数据源Service层封装
原文:https://www.pdai.tech/md/spring/springboot-data-multi.html mysql, es, mongodb 三个数据源用配置文件方式连接,JPA只 ...
- 【spring boot】 禁用/关闭数据源/DataSource
前言 spring boot 2.0.0.RELEASE maven 3.5 eclipse 4.9.0 用spring boot做程序,不需要连接数据库.该程序一直工作正常. 在某次修改程序后,出现 ...
最新文章
- Firefox beta 开始原生支持 Windows 10 ARM64
- 802.11概述及帧结构分析
- 数据结构2:中序线索化二叉树为什么要通过pre设置后继结点
- LeetCode 743. Network Delay Time
- easyui datagrid 每行数据添加 按钮
- 【深度探讨】阿里巴巴万级规模 K8s 集群全局高可用体系之美
- python线程创建对象_Python多线程编程基础:如何创建线程?
- Flutter - 生成二维码与识别二维码
- 直接拿来用!GitHub10个开源免费的后台管理面板
- datasnap——动态注册服务类
- 生鲜电商之毒,食行生鲜模式虽好、恐也难解
- 主管好当:一不指点工作,二不检查工作,三不改正错误
- swun 1766 我的悲剧不可能那么好数
- C++例4.11 求两个或三个正整数中的最大数,用带有默认参数的函数实现。
- html文章整体居中,html如何实现文本上下居中
- python编写一个函数把华氏温度转换成摄氏温度_编写一个函数把华氏温度转换成摄氏温度,温度转换公式为:c=(f-32)*5/9。在主函数中输入华氏温度值......
- js,JQ 图片转换base64 base64转换为file对象,blob对象
- H.266/VVC代码学习:普通量化和率失真优化量化(RDOQ)
- 第二章节 MongoDB的基本命令
- SQL Server numeric数据类型