需求背景:项目中需要根据用户身份访问不同数据源,涉及到动态切换数据源,先写个小demo
经过网上搜索发现Spring中提供了一个叫AbstractRoutingDataSource的抽象类,该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上,先看下AbstractRoutingDataSource 源码
package org.springframework.jdbc.datasource.lookup;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;
/**
* Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
* calls to one of various target DataSources based on a lookup key. The latter is usually
* (but not necessarily) determined through some thread-bound transaction context.
*
* @author Juergen Hoeller
* @since 2.0.1
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map
上面这段源码的重点在于determineCurrentLookupKey
方法,这是AbstractRoutingDataSource
类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource
(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
本次主要用到的setTargetDataSources
、setDefaultTargetDataSource
、determineCurrentLookupKey
这三个方法,下面来开始撸代码了
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring.version>4.3.10.RELEASEspring.version>
<mybatis.version>3.4.4mybatis.version>
<jdk.version>1.8jdk.version>
<aspectj.version>1.8.10aspectj.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>${aspectj.version}version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>${mybatis.version}version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.40version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.25version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.7version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.2.2version>
dependency>
<dependency>
<groupId>commons-beanutilsgroupId>
<artifactId>commons-beanutilsartifactId>
<version>1.9.3version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.1.2version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.1.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.10version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.24version>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.9.9version>
dependency>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>3.6.0version>
dependency>
dependencies>
package com.os.common.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 获取数据源(依赖于spring)
*
* @author Peng
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceUtils.getDataSource();
}
}
保证线程与线程之间互不影响
package com.os.common.config;
/**
* 线程使用数据源工具类
* 保证线程使用数据源互不影响
*
* @author Peng
*/
public class DataSourceUtils {
/**
* 线程本地环境
*/
private static final ThreadLocal DATA_SOURCES = new ThreadLocal<>();
/**
* 设置数据源
*/
public static void setDataSource(String customerType) {
DATA_SOURCES.set(customerType);
}
/**
* 获取数据源
*/
public static String getDataSource() {
return DATA_SOURCES.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
DATA_SOURCES.remove();
}
}
package com.os.common.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* spring 配置
*
* @author Peng
*/
@Configuration
@ComponentScan(basePackages = {"com.os"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
@EnableTransactionManagement
public class RootConfig {
/**
* 这里写死的数据库url 实际项目应配置在配置文件中
*/
private static final String DB_URL1 = "jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=true";
private static final String DB_URL2 = "jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=true";
/**
* 配置数据源1
*/
@Bean(name = "db1", initMethod = "init", destroyMethod = "close")
public DataSource dataSource1() throws SQLException {
return this.createDataSource(DB_URL1, "root", "");
}
/**
* 配置数据源2
*/
@Bean(name = "db2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() throws SQLException {
return this.createDataSource(DB_URL2, "root", "");
}
/**
* 创建DataSource
*
* @param url 数据库地址
* @param username 数据库用户名
* @param password 数据库密码
*/
private DruidDataSource createDataSource(String url, String username, String password) throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl(url);// 数据库地址
druidDataSource.setUsername(username);// 用户名
druidDataSource.setPassword(password);// 密码
// 初始化大小,最小,最大
druidDataSource.setInitialSize(5);
druidDataSource.setMinIdle(3);
druidDataSource.setMaxActive(20);
druidDataSource.setMaxWait(60000);//配置获取连接等待超时的时间
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
druidDataSource.setMinEvictableIdleTimeMillis(300000);//配置一个连接在池中最小生存的时间,单位是毫秒
druidDataSource.setValidationQuery("SELECT 'x'");
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTestOnBorrow(false);
druidDataSource.setTestOnReturn(false);
//打开PSCache,并且指定每个连接上PSCache的大小
druidDataSource.setPoolPreparedStatements(true);// 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。 5.5及以上版本有PSCache,建议开启。
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
druidDataSource.setFilters("stat,wall,log4j");//配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
//通过connectProperties属性来打开mergeSql功能;慢SQL记录
Properties properties = new Properties();
properties.put("slowSqlMillis", "5000");
druidDataSource.setConnectProperties(properties);
druidDataSource.setUseGlobalDataSourceStat(true);//合并多个DruidDataSource的监控数据
return druidDataSource;
}
/**
* 创建动态DataSource
*
* @param dataSource1 数据源1
* @param dataSource2 数据源2
*/
@Bean
public DynamicDataSource source(@Qualifier("db1") DataSource dataSource1, @Qualifier("db2") DataSource dataSource2) throws SQLException {
DynamicDataSource source = new DynamicDataSource();
Map map = new HashMap<>();
map.put("db1", dataSource1);
map.put("db2", dataSource2);
source.setTargetDataSources(map);
source.setDefaultTargetDataSource(dataSource1);
return source;
}
/**
* sqlSessionFactory
*
* @param dataSource 数据源
*/
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactoryBean(DynamicDataSource dataSource) throws SQLException, IOException {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factory.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
return factory;
}
/**
* dao映射配置
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
configurer.setBasePackage("com.**.dao");
return configurer;
}
/**
* 事务配置
*
* @param dataSource 数据源
*/
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) throws SQLException {
return new DataSourceTransactionManager(dataSource);
}
}
至此就算是配置成功了,下面开始测试
@RequestMapping("/")
@ResponseBody
public Map<String, Object> list(@RequestParam(name = "type", defaultValue = "0") int type) {
Map<String, Object> json = new HashMap<>(4);
// 根据type参数切换 如果type==2就查询demo2数据库
if (type == 2) {
DataSourceUtils.setDataSource("db2");
}
// 访问db 代码就不贴了
List<User> userList = userService.listUser();
json.put("list", userList);
System.out.println(Thread.currentThread().getName());
DataSourceUtils.clearDataSource();
return json;
}
访问127.0.0.1
{"list":[{"createTime":"2017-09-18 19:58:44","id":1,"password":"123456","username":"demo1数据库"}]}
访问127.0.0.1?type=2
{"list":[{"createTime":"2017-09-18 19:58:44","id":1,"password":"123456","username":"demo2数据库"}]}
ok,总结完毕