数据库连接池druid:
com.alibaba
druid-spring-boot-starter
1.1.21
yml中druid配置:
druid:
initial-size: 5 # 初始化大小
min-idle: 10 # 最小连接数
max-active: 20 # 最大连接数
max-wait: 60000 # 获取连接时的最大等待时间
配置文件中配置默认的数据源datasource,默认数据源中需要建立一张各租户的数据源表:
上图表中有redis-database字段为切换redis库时使用的字段,如不需要可自行修改。
创建DataSourceContextHolder用于切换数据源,使用threadlocal保留当前线程数据源信息:
@Slf4j
public class DataSourceContextHolder {
private static final ThreadLocal DATA_SOURCE = new ThreadLocal<>();
/**
* 切换数据源
*/
public static void setDataSource(String datasourceId) {
DATA_SOURCE.set(datasourceId);
log.info("已切换到数据源:{}",datasourceId);
}
public static String getDataSource() {
return DATA_SOURCE.get();
}
/**
* 删除数据源
*/
public static void removeDataSource() {
DATA_SOURCE.remove();
log.info("已切换到默认主数据源");
}
}
继承AbstractRoutingDataSource实现根据不同请求切换数据源:
@Slf4j
@Data
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private boolean debug = true;
/**
* 存储注册的数据源
*/
private volatile Map
接下来配置默认数据源,还有数据库连接池的信息,以及将数据源配置到sql工厂当中:
@Configuration
@Slf4j
public class DruidDBConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.druid.max-wait}")
private int maxWait;
@Bean
@Primary
@Qualifier("mainDataSource")
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);
//是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
datasource.setPoolPreparedStatements(false);
datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
//申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
datasource.setTestOnBorrow(true);
//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
datasource.setTestWhileIdle(true);
//用来检测连接是否有效的sql
datasource.setValidationQuery("select 1 from dual");
//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
datasource.setTimeBetweenEvictionRunsMillis(60000);
//配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
datasource.setMinEvictableIdleTimeMillis(180000);
datasource.setKeepAlive(true);
return datasource;
}
@Bean(name = "dynamicDataSource")
@Qualifier("dynamicDataSource")
public DynamicRoutingDataSource dynamicDataSource() throws SQLException {
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDebug(false);
//配置缺省的数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource());
Map targetDataSources = new HashMap();
//额外数据源配置 TargetDataSources
targetDataSources.put("mainDataSource", dataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//对新的SqlSessionFactory配置 修改mybatis-plus Page自动分页失效问题 以及 找不到xml问题
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.addInterceptor(new MybatisPlusConfig().paginationInterceptor());
sqlSessionFactoryBean.setConfiguration(configuration);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
/**
* @Description: 将动态数据加载类添加到事务管理器
* @param dataSource
* @return org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicRoutingDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
现在就可以根据数据源表中唯一值切换数据源了,调用changeDB(数据源唯一值):
@Resource
DatabaseListService databaseListService;
@Resource
private DynamicRoutingDataSource dynamicRoutingDataSource;
@Override
public boolean changeDB(String datasourceId) {
//切到默认数据源
DataSourceContextHolder.removeDataSource();
//找到所有的配置
List databaseListList = databaseListService.list();
if(!CollectionUtils.isEmpty(databaseListList)){
for (DatabaseList d : databaseListList) {
if(d.getFactoryCode().equals(datasourceId)){
//判断连接是否存在,不存在就创建
dynamicRoutingDataSource.checkCreateDataSource(d);
//切换数据源
DataSourceContextHolder.setDataSource(d.getFactoryCode());
return true;
}
}
}
return false;
}
以上就完成了所有步骤,可以切换数据源了。
在我的项目里,我在登录的拦截器中校验完权限后判断属于哪个数据源来切换。
下面说一下redis库的动态切换:
同样使用threadlocal保留当前线程的redis库,在get或set时设置redis数据库索引:
@Slf4j
@Component
public class RedisUtil {
private static RedisTemplate redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisUtil.redisTemplate = redisTemplate;
}
private static ThreadLocal database = new ThreadLocal();
public static Integer getDatabase() {
return (Integer) database.get();
}
public static void setDatabase(Integer db) {
database.set(db);
}
/**
* 设置数据库索引
*
*/
public static void setDbIndex() {
//默认使用1
Integer dbIndex = 1;
if(database.get()!=null){
dbIndex = database.get();
}
LettuceConnectionFactory redisConnectionFactory = (LettuceConnectionFactory) redisTemplate
.getConnectionFactory();
if (redisConnectionFactory == null) {
return;
}
redisConnectionFactory.setDatabase(dbIndex);
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 属性设置后
redisConnectionFactory.afterPropertiesSet();
// 重置连接
redisConnectionFactory.resetConnection();
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
setDbIndex();
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public static boolean set(String key, Object value) {
setDbIndex();
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}