赞
踩
前言
最近公司有个需求需要后端访问多个数据库,在网上查了半天资料,结果发现大部分都是配置的主从库这种数据源固定的情况,不符合我的需求,假设我们有这样一种需求,后端需要从一个数据库的配置表里动态的读取其它mysql数据库的链接配置信息,并根据链接信息动态创建数据库链接,发起请求,而且还要能使用现在的一些链接池。
最后找到了这篇博客
https://blog.csdn.net/aiyo92/article/details/86518217
该篇博客主要基于以上链接博客,去掉了其中关于jpa设置,和其他类型数据链接,只用了mysql类型数据库,再加上了通过参数注解根据传递的数据源参数自动切换。
最后附上git 地址。
使用的版本信息
springboot2.1.4
mysql 8.0.13
druid 1.1.16
准备工作
1、创建测试数据库
test1,test2。test1作为我们的主数据库。
在test1中创建数据源配置信息表。
CREATE TABLE `test1`.`databasetype` (
`datasource_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pass_word` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`databasetype` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
这表表里设置好你的mysql数据库链接信息
datasource_id 是主键,在程序中也要靠它查找数据源。
url是数据库链接:如jdbc:mysql://127.0.0.1:3307/test2?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true
user_name、pass_word访问test2数据库的用户名和密码
code和databasetype都没有使用,不写。
在test2中创建一个用来测试的用户表
CREATE TABLE `test2`.`user` (
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(3) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
放入几条测试数据
INSERT INTO `user` VALUES ('张三', 20);
INSERT INTO `user` VALUES ('李四', 17);
2、搭建springboot项目
这块就不多说了,贴下pom和配置文件
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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zhou</groupId> <artifactId>datasource</artifactId> <version>0.0.1-SNAPSHOT</version> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--aop,通过注解切换数据源需要用到--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml
server: port: 80 #配置访问端口,不配置也行,默认不配置就是8080 spring: aop: proxy-target-class: true #true为使用CGLIB代理 datasource: #新版mysql驱动配置方法 driver-class-name: com.mysql.cj.jdbc.Driver #nullCatalogMeansCurrent=true& url: jdbc:mysql://127.0.0.1:3307/test1?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true # driver-class-name: com.mysql.jdbc.Driver # url: jdbc:mysql://192.168.1.111:3306/sfms username: root password: root driverClassName: com.mysql.cj.jdbc.Driver ###################以下为druid增加的配置########################### type: com.alibaba.druid.pool.DruidDataSource # 下面为连接池的补充设置,应用到上面所有数据源中 # 初始化大小,最小,最大 initialSize: 5 minIdle: 5 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,log4j # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合并多个DruidDataSource的监控数据 useGlobalDataSourceStat: true ###############以上为配置druid添加的配置######################################## # type: com.zaxxer.hikari.HikariDataSource # hikari: # minimum-idle: 5 # maximum-pool-size: 15 # auto-commit: true # idle-timeout: 30000 # pool-name: DatebookHikariCP # max-lifetime: 1800000 # connection-timeout: 30000 # connection-test-query: SELECT 1 mybatis: type-aliases-package: com.zhou.datasource.model #扫描包路径 configuration: map-underscore-to-camel-case: true #打开驼峰命名
正餐开始上代码
1、先创建DynamicDataSource ,继承于AbstractRoutingDataSource,主要重写determineCurrentLookupKey()方法,通过这个方法拿到数据源。AbstractRoutingDataSource具体作用请自行百度,或者原博里也有解释。
在这个类里的createDataSourceWithCheck方法,实现了根据链接信息创建数据源的方法。
package com.zhou.datasource.dbconfig; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.stat.DruidDataSourceStatManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.util.StringUtils; import java.sql.Connection; import java.sql.DriverManager; import java.util.Map; import java.util.Set; /** * @auther zhoupan * @date 2019/4/8 20:43 * @info */ public class DynamicDataSource extends AbstractRoutingDataSource{ private boolean debug = true; private final Logger log = LoggerFactory.getLogger(getClass()); private Map<Object, Object> dynamicTargetDataSources; private Object dynamicDefaultTargetDataSource; @Override protected Object determineCurrentLookupKey() { String datasource = DBContextHolder.getDataSource(); if (!StringUtils.isEmpty(datasource)) { Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; if (dynamicTargetDataSources2.containsKey(datasource)) { log.info("---当前数据源:" + datasource + "---"); } else { log.info("不存在的数据源:"); return null; // throw new ADIException("不存在的数据源:"+datasource,500); } } else { log.info("---当前数据源:默认数据源---"); } return datasource; } @Override public void setTargetDataSources(Map<Object, Object> targetDataSources) { super.setTargetDataSources(targetDataSources); this.dynamicTargetDataSources = targetDataSources; } // 创建数据源 public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) { try { try { // 排除连接不上的错误 Class.forName(driveClass); DriverManager.getConnection(url, username, password);// 相当于连接数据库 } catch (Exception e) { return false; } @SuppressWarnings("resource") // HikariDataSource druidDataSource = new HikariDataSource(); DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setName(key); druidDataSource.setDriverClassName(driveClass); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 druidDataSource.setMaxActive(200); //最大连接池数量 druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象 druidDataSource.setMinIdle(40); //最小连接池数量 String validationQuery = "select 1 from dual"; // if("mysql".equalsIgnoreCase(databasetype)) { // driveClass = DBUtil.mysqldriver; // validationQuery = "select 1"; // } else if("oracle".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.oracledriver; // druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 // druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50); // int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut(); // druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒 // } else if("sqlserver2000".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.sql2000driver; // validationQuery = "select 1"; // } else if("sqlserver".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.sql2005driver; // validationQuery = "select 1"; // } druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用 druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000 druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断 druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。 druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时 druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志 // DataSource createDataSource = druidDataSource; druidDataSource.init(); // Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; // dynamicTargetDataSources2.put(key, createDataSource);// 加入map this.dynamicTargetDataSources.put(key, druidDataSource); setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources // setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理 log.info(key+"数据源初始化成功"); //log.info(key+"数据源的概况:"+druidDataSource.dump()); return true; } catch (Exception e) { log.error(e + ""); return false; } } // 删除数据源 public boolean delDatasources(String datasourceid) { Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; if (dynamicTargetDataSources2.containsKey(datasourceid)) { Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances(); for (DruidDataSource l : druidDataSourceInstances) { if (datasourceid.equals(l.getName())) { dynamicTargetDataSources2.remove(datasourceid); DruidDataSourceStatManager.removeDataSource(l); setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理 return true; } } return false; } else { return false; } } // 测试数据源连接是否有效 public boolean testDatasource(String key, String driveClass, String url, String username, String password) { try { Class.forName(driveClass); DriverManager.getConnection(url, username, password); return true; } catch (Exception e) { return false; } } /** * Specify the default target DataSource, if any. * <p> * The mapped value can either be a corresponding * {@link javax.sql.DataSource} instance or a data source name String (to be * resolved via a {@link #setDataSourceLookup DataSourceLookup}). * <p> * This DataSource will be used as target if none of the keyed * {@link #setTargetDataSources targetDataSources} match the * {@link #determineCurrentLookupKey()} current lookup key. */ @Override public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); this.dynamicDefaultTargetDataSource = defaultTargetDataSource; } /** * @param debug * the debug to set */ public void setDebug(boolean debug) { this.debug = debug; } /** * @return the debug */ public boolean isDebug() { return debug; } /** * @return the dynamicTargetDataSources */ public Map<Object, Object> getDynamicTargetDataSources() { return dynamicTargetDataSources; } /** * @param dynamicTargetDataSources * the dynamicTargetDataSources to set */ public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) { this.dynamicTargetDataSources = dynamicTargetDataSources; } /** * @return the dynamicDefaultTargetDataSource */ public Object getDynamicDefaultTargetDataSource() { return dynamicDefaultTargetDataSource; } /** * @param dynamicDefaultTargetDataSource * the dynamicDefaultTargetDataSource to set */ public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) { this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource; } public void createDataSourceWithCheck(com.zhou.datasource.model.DataSource dataSource) throws Exception { String datasourceId = dataSource.getDatasourceId(); log.info("准备创建数据源"+datasourceId); Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; if (dynamicTargetDataSources2.containsKey(datasourceId)) { log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常..."); //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId); DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId); boolean rightFlag = true; Connection connection = null; try { log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount()); long activeCount = druidDataSource.getActiveCount(); log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount); if(activeCount > 0) { log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace()); } log.info("准备获取数据库连接..."); connection = druidDataSource.getConnection(); log.info("数据源"+datasourceId+"正常"); } catch (Exception e) { log.error(e.getMessage(),e); //把异常信息打印到日志文件 rightFlag = false; log.info("缓存数据源"+datasourceId+"已失效,准备删除..."); if(delDatasources(datasourceId)) { log.info("缓存数据源删除成功"); } else { log.info("缓存数据源删除失败"); } } finally { if(null != connection) { connection.close(); } } if(rightFlag) { log.info("不需要重新创建数据源"); return; } else { log.info("准备重新创建数据源..."); createDataSource(dataSource); log.info("重新创建数据源完成"); } } else { createDataSource(dataSource); } } private void createDataSource(com.zhou.datasource.model.DataSource dataSource) throws Exception { String datasourceId = dataSource.getDatasourceId(); log.info("准备创建数据源"+datasourceId); String databasetype = dataSource.getDatabasetype(); String username = dataSource.getUserName(); String password = dataSource.getPassWord(); // password = new String(SecurityTools.decrypt(Base64.decode(password))); String url = dataSource.getUrl(); String driveClass = "com.mysql.cj.jdbc.Driver"; // if("mysql".equalsIgnoreCase(databasetype)) { // driveClass = DBUtil.mysqldriver; // } else if("oracle".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.oracledriver; // } else if("sqlserver2000".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.sql2000driver; // } else if("sqlserver".equalsIgnoreCase(databasetype)){ // driveClass = DBUtil.sql2005driver; // } if(testDatasource(datasourceId,driveClass,url,username,password)) { boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype); if(!result) { log.error("数据源"+datasourceId+"配置正确,但是创建失败"); // throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500); } } else { log.error("数据源配置有错误"); // throw new ADIException("数据源配置有错误",500); } } }
2再创建链接池的配置类DruidDBConfig,在这个类里读取了配置文件中数据库链接信息和druid链接池的配置信息,手动装载了默认的数据源。
package com.zhou.datasource.dbconfig; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; /** * DruidDBConfig类被@Configuration标注,用作配置信息; DataSource对象被@Bean声明,为Spring容器所管理, * * @Primary表示这里定义的DataSource将覆盖其他来源的DataSource。 * @auther zhoupan * @date 2019/4/8 22:06 * @info */ @Configuration @EnableTransactionManagement public class DruidDBConfig { private final Logger log = LoggerFactory.getLogger(getClass()); // adi数据库连接信息 @Value("${spring.datasource.url}") private String dbUrl; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driverClassName}") private String driverClassName; // 连接池连接信息 @Value("${spring.datasource.initialSize}") private int initialSize; @Value("${spring.datasource.minIdle}") private int minIdle; @Value("${spring.datasource.maxActive}") private int maxActive; @Value("${spring.datasource.maxWait}") private int maxWait; // @Bean (name = "dynamicDataSourceAspect") // public DynamicDataSourceAspect dynamicDataSourceAspect() { // return org.aspectj.lang.Aspects.aspectOf(DynamicDataSourceAspect.class); // } @Bean // 声明其为Bean实例 @Primary // 在同样的DataSource中,首先使用被标注的DataSource @Qualifier("adiDataSource") public DataSource dataSource() throws SQLException { DruidDataSource datasource = new DruidDataSource(); // 基础连接信息 datasource.setUrl(this.dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setDriverClassName(driverClassName); // 连接池连接信息 datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 datasource.setMaxPoolPreparedStatementPerConnectionSize(50); datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒 datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用 datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 String validationQuery = "select 1 from dual"; datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000 datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断 datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。 datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时 datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志 return datasource; } @Bean(name = "dynamicDataSource") @Qualifier("dynamicDataSource") public DynamicDataSource dynamicDataSource() throws SQLException { DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setDebug(false); //配置缺省的数据源 dynamicDataSource.setDefaultTargetDataSource(dataSource()); Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); targetDataSources.put("adiDataSource", dataSource()); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); //解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题 sqlSessionFactoryBean.setConfiguration(configuration()); return sqlSessionFactoryBean.getObject(); } // @Bean // public SqlSessionTemplate sqlSessionTemplate() throws Exception { // return new SqlSessionTemplate(sqlSessionFactory()); // } /** * 读取驼峰命名设置 * @return */ @Bean @ConfigurationProperties(prefix = "mybatis.configuration") public org.apache.ibatis.session.Configuration configuration(){ return new org.apache.ibatis.session.Configuration(); } }
默认的数据源创建完了,然后创建DBContextHolder,确定当前使用哪个数据源
package com.zhou.datasource.dbconfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @auther zhoupan * @date 2019/4/8 21:57 * @info */ public class DBContextHolder { private final static Logger log = LoggerFactory.getLogger(DBContextHolder.class); // 对当前线程的操作-线程安全的 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); // 调用此方法,切换数据源 public static void setDataSource(String dataSource) { contextHolder.set(dataSource); log.info("已切换到数据源:{}",dataSource); } // 获取数据源 public static String getDataSource() { return contextHolder.get(); } // 删除数据源 public static void clearDataSource() { contextHolder.remove(); log.info("已切换到主数据源"); } }
到这里已经可以测试了,我们先手动切换测试下,mapper很简单,就查询全部
mapper
package com.zhou.datasource.mapper; import com.zhou.datasource.model.DataSource; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @auther zhoupan * @date 2019/4/8 21:12 * @info */ public interface DataSourceMapper { @Select("SELECT * FROM datasource") List<DataSource> get(); }
package com.zhou.datasource.mapper; import com.zhou.datasource.model.User; import org.apache.ibatis.annotations.Select; import java.util.List; /** * Created with IDEA * * @author : zhoupan * @date : 2019/4/9 11:44 * @info : */ public interface UserMapper { @Select("SELECT * FROM user") List<User> get(); }
然后是bean
public class DataSource {
String datasourceId;
String url;
String userName;
String passWord;
String code;
String databasetype;
....省略get set
}
public class User {
String userName;
String age;
....省略get set
}
在写个测试方法,我们取出查出的第一条DataSource 数据,然后传入参数手动创建数据源,在切换成当前创建的这个数据源。查询
@RunWith(SpringRunner.class) @SpringBootTest public class DatasourceApplicationTests { @Autowired private DataSourceMapper dataSourceMapper; @Autowired private UserMapper userMapper; @Autowired private DynamicDataSource dynamicDataSource; @Autowired private DBChangeService dbChangeService; /** * 手动改变数据源 */ @Test public void get(){ List<DataSource> list=dataSourceMapper.get(); DataSource d = list.get(0); //创建数据源 try { dynamicDataSource.createDataSourceWithCheck(d); } catch (Exception e) { e.printStackTrace(); } //切换数据源 DBContextHolder.setDataSource(d.getDatasourceId()); List<User> list1=userMapper.get(); list1.forEach(dataSource1 -> System.out.println(dataSource1.getUserName())); } }
好了,现在开始完成根据自动切换部分
创建一个注解DBChange 用来表示我们需要改变数据源的方法
package com.zhou.datasource.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created with IDEA * * @author : zhoupan * @date : 2019/4/9 10:00 * @info : */ /** * 注解生命周期作用范围 */ @Retention(RetentionPolicy.RUNTIME) /** *注解可以作用在参数或者方法上 */ @Target({ ElementType.METHOD,ElementType.PARAMETER }) public @interface DBChange { }
然后定义切面,在这里我定义了切点,将切入所有标注了@DBChange注解的方法
然后判断切入的方法里有没有带有@DBChange的数据源参数,有的话就切换。别忘了执行完后再把数据源切到主数据源
package com.zhou.datasource.dbconfig; import com.zhou.datasource.annotation.DBChange; import com.zhou.datasource.model.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.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; import javax.naming.Name; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * Created with IDEA * * @author : zhoupan * @date : 2019/4/9 10:53 * @info : */ @Aspect @Component public class DynamicDataSourceAspect { @Pointcut("@annotation(com.zhou.datasource.annotation.DBChange)") private void pointcut() { } @Autowired private DynamicDataSource dynamicDataSource; @Before("pointcut()") public void beforeSwitchDS(JoinPoint point){ //获得当前访问的class Class<?> className = point.getTarget().getClass(); //获得访问的方法名 String methodName = point.getSignature().getName(); Object[] objects=point.getArgs(); //得到方法的参数的类型 Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes(); String dataSource = null; try { // 得到访问的方法对象 Method method = className.getMethod(methodName, argClass); Parameter[] parameters = method.getParameters(); // 判断是否存在注解 for (int i = 0; i <parameters.length; i++) { Parameter parameter = parameters[i]; if (parameter.isAnnotationPresent(DBChange.class)) { DataSource dataSource1=(DataSource) objects[i]; dynamicDataSource.createDataSourceWithCheck(dataSource1); dataSource =dataSource1.getDatasourceId(); break; } } } catch (Exception e) { e.printStackTrace(); }finally { // 切换数据源 DBContextHolder.setDataSource(dataSource); } } @After("pointcut()") public void afterSwitchDS(JoinPoint point){ DBContextHolder.clearDataSource(); } }
好了 接下来就是测试,简单写了个service,写了个获取数据链接信息的方法,和一个使用了@DBChange 注解去查询用户信息的方法。
package com.zhou.datasource.service; import com.zhou.datasource.annotation.DBChange; import com.zhou.datasource.mapper.DataSourceMapper; import com.zhou.datasource.mapper.UserMapper; import com.zhou.datasource.model.DataSource; import com.zhou.datasource.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; import java.util.List; /** * Created with IDEA * * @author : zhoupan * @date : 2019/4/9 11:46 * @info : */ @Service public class DBChangeService { @Autowired private DataSourceMapper dataSourceMapper; @Autowired private UserMapper userMapper; /** * 使用默认数据源获取数据库中的其他数据库链接信息 * @return */ public List<DataSource> get(){ return dataSourceMapper.get(); } /** * 通过注解将数据源改为参数指定的。 * @param dataSource * @return */ @DBChange public List<User> getUser(@DBChange DataSource dataSource){ return userMapper.get(); } }
测试方法
/** * 测试自动切换 */ @Test public void contextLoads() { //取出数据库中的第一条数据源配置信息 List<DataSource> list=dataSourceMapper.get(); DataSource d = list.get(0); List<User> list1 = dbChangeService.getUser(d); list1.forEach(user -> System.out.println(user.getUserName())); /** * 第二次查询不用再创建数据源,直接使用 */ List<User> list2 = dbChangeService.getUser(d); list2.forEach(user -> System.out.println(user.getUserName())); }
https://github.com/z357904947/DataSource
需要源码的自行食用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。