jdbctemplate动态多数据源配置-爱代码爱编程
一、前言
多数据源的配置,是一个相对比较常见的需求。
什么是数据源?数据源就是javax.sql.DataSource,所有实现了这个接口的DataSource就叫做数据源,现在比较常用阿里巴巴的DruidDataSource,支持监控多数据源下的sql运行状况,便于以此实现以sql为核心的应用系统,比如BI报表系统、BI工具、ETL工具等。而这些场景下的业务sql通常是属于动态数据源,它们的操作对象来自于不同的数据库类型,或不同的数据库实例,通常被存放在某个业务表中,还可能需要被新增、删除和修改,因而我们不能像使用Mybatis那样,预先定义好要执行的所有sql,然后放在Mybatis的mapper配置文件中,这种Jdbc操作方式就显得不恰当,换用spring-jdbc的JdbcTemplate刚好可以完美地解决这个问题。
二、实现思路
2.1 配置数据源属性
这里选用DruidDataSource作为我们的业务数据源,一般在application.properties中为其配置各项Druid连接池属性,如下:
2.2 自定义业务数据源Bean
实现自己的DruidDataSource,加载上面的Druid数据源配置,初始化DruidDataSource实例,以线程安全的方式缓存在一Map<String, DruidDataSource>中,并提供获取数据源和关闭数据源的方法。如下:
package com.cjia.common.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.StringUtils;
import com.cjia.common.exception.SourceException;
import com.cjia.model.SqlDBConfig;
import com.cjia.utils.MD5Util;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class JdbcDataSource extends DruidDataSource {
@Value("${spring.datasource.type}")
private String type;
@Value("${source.max-active:10}")
private int maxActive;
@Value("${source.initial-size:5}")
private int initialSize;
@Value("${source.min-idle:3}")
private int minIdle;
@Value("${source.max-wait:30000}")
private int maxWait;
@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.test-while-idle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.druid.filters}")
private String filters;
@Value("${source.break-after-acquire-failure:true}")
private boolean breakAfterAcquireFailure;
@Value("${source.connection-error-retry-attempts:0}")
private int connectionErrorRetryAttempts;
private static volatile Map<String, DruidDataSource> map = new HashMap<>();
public synchronized void removeDatasource(String jdbcUrl, String username, String password) {
String key = getKey(jdbcUrl, username, password);
if (map.containsKey(key)) {
map.remove(key);
}
}
public synchronized DruidDataSource getDataSource(SqlDBConfig sqlDBConfig) throws SourceException {
String jdbcUrl = sqlDBConfig.getUrl();
String username = sqlDBConfig.getUsername();
String password = sqlDBConfig.getPassword();
String key = getKey(jdbcUrl, username, password);
if (!map.containsKey(key) || null == map.get(key)) {
DruidDataSource instance = new JdbcDataSource();
String className = null;
try {
className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName();
} catch (SQLException e) {
}
if (StringUtils.isEmpty(className)) {
throw new SourceException("Driver Class Not null: DbId=" + sqlDBConfig.getDbId());
} else {
instance.setDriverClassName(className);
}
instance.setUrl(jdbcUrl.trim());
instance.setUsername(username);
instance.setPassword(password);
instance.setInitialSize(initialSize);
instance.setMinIdle(minIdle);
instance.setMaxActive(maxActive);
instance.setMaxWait(maxWait);
instance.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
instance.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
instance.setTestWhileIdle(false);
instance.setTestOnBorrow(testOnBorrow);
instance.setTestOnReturn(testOnReturn);
try {
instance.setFilters(filters);
} catch (SQLException ex) {
log.error("druid configuration initialization filter", ex);
}
instance.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
instance.setBreakAfterAcquireFailure(breakAfterAcquireFailure);
try {
instance.init();
} catch (Exception e) {
log.error("Exception during pool initialization", e);
throw new SourceException(e.getMessage());
}
map.put(key, instance);
}
return map.get(key);
}
private String getKey(String jdbcUrl, String username, String password) {
StringBuilder sb = new StringBuilder();
if (!StringUtils.isEmpty(username)) {
sb.append(username);
}
if (!StringUtils.isEmpty(password)) {
sb.append(":").append(password);
}
sb.append("@").append(jdbcUrl.trim());
return MD5Util.md5(sb.toString(), MD5Util.SEC_KEY);
}
}
2.3 动态构造JdbcTemplate
首先,注入上一步自定义的业务数据源JdbcDataSource,接着,JdbcTemplate提供了传入DataSource的构造方式,获取到JdbcTemplate对象后,调用它丰富的API执行业务sql,如下:
// 获取初始化后的druid数据源,SqlDBConfig存放了jdbcUrl、username、password
DruidDataSource dataSource = jdbcDataSource.getDataSource(sqlDBConfig);
List<Map<String, Object>> result = new JdbcTemplate(dataSource).queryForList(sql, paramArgs);
以上。