业务场景:需要根据页面数据库参数入库并自动创建数据源连接以及对多数据源进行管理,在业务过程中自由切换数据源连接。
话不多说,直接上代码,基本每个代码功能块都有注释解释,应该可以看的明白。
根据配置参数创建数据源
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
/**
* @ClassName: DataSourceConfig
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:15:48
*/
@Configuration
@MapperScan(basePackages = { "com.*.zhsq.dao.zhsq.mysql","com.*.zhsq.dao.zhsq.opaq" }, sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfig {
/**
* 根据配置参数创建数据源。使用派生的子类。
*
* @return 数据源
*/
@Bean(name = "dataSource")
public DataSource testDataSource() {
DataSourceBuilder builder = DataSourceBuilder.create();
builder.type(DynamicDataSource.class);
return builder.build();
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mybatis/zhsq/mapper/collect/**/*.xml"));
return bean.getObject();
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
动态数据源创建类,用于创建连接
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.SQLException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import com.*.zhsq.base.bean.CacheMap;
import com.*.zhsq.base.enums.DbUrlTypeEnum;
import com.*.zhsq.utils.AesUtils;
/**
* @ClassName: DynamicDataSource
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:17:23
*/
public class DynamicDataSource extends DataSource {
private static Logger log = LogManager.getLogger(DynamicDataSource.class);
/**
* 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。
*/
@Override
public Connection getConnection(){
String projectCode = DBIdentifier.getProjectCode();
//1、获取数据源
DataSource dds = DDSHolder.instance().getDDS(projectCode);
//2、如果数据源不存在则创建
if (dds == null) {
try {
DataSource newDDS = initDDS(projectCode);
DDSHolder.instance().addDDS(projectCode, newDDS);
} catch (IllegalArgumentException | IllegalAccessException e) {
log.error("Init data source fail. projectCode:" + projectCode);
return null;
}
}
dds = DDSHolder.instance().getDDS(projectCode);
try {
return dds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
/**
* 以当前数据对象作为模板复制一份。
*
* @return dds
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
private DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {
DataSource dds = new DataSource();
// 2、复制PoolConfiguration的属性
PoolProperties property = new PoolProperties();
Field[] pfields = PoolProperties.class.getDeclaredFields();
for (Field f : pfields) {
f.setAccessible(true);
Object value = f.get(this.getPoolProperties());
try
{
f.set(property, value);
}
catch (Exception e)
{
//有一些static final的属性不能修改。忽略。
log.info("Set value fail. attr name:" + f.getName());
continue;
}
}
dds.setPoolProperties(property);
com.*.zhsq.modules.datasource.entity.DataSource bean = CacheMap.DATA_SOURCE_MAP.get(projectCode);
String urlFormat = DbUrlTypeEnum.getDbUrl(bean.getDatabaseType());
String url = String.format(urlFormat, bean.getIp(),bean.getPort(),bean.getDatabaseName());
dds.setUrl(url);
dds.setUsername(bean.getUserName());
dds.setPassword(decryptEncodePassword(bean.getPassword()));
dds.setDriverClassName(DbUrlTypeEnum.getDbDriver(bean.getDatabaseType()));
return dds;
}
/**
* 解密数据源密码
*/
private String decryptEncodePassword(String password){
String decrypt = "";
try {
decrypt = AesUtils.defaultAESDecryptEncode(password);
} catch (InvalidKeyException | NoSuchAlgorithmException
| NoSuchPaddingException | IllegalBlockSizeException
| BadPaddingException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return decrypt;
}
}
数据源定时监听
import org.apache.tomcat.jdbc.pool.DataSource;
/**
* @ClassName: DDSTimer
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:18:18
*/
public class DDSTimer {
/**
* 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
*/
private static long idlePeriodTime = 10 * 60 * 1000;
/**
* 动态数据源
*/
private DataSource dds;
/**
* 上一次访问的时间
*/
private long lastUseTime;
public DDSTimer(DataSource dds) {
this.dds = dds;
this.lastUseTime = System.currentTimeMillis();
}
/**
* 更新最近访问时间
*/
public void refreshTime() {
lastUseTime = System.currentTimeMillis();
}
/**
* 检测数据连接是否超时关闭。
*
* @return true-已超时关闭; false-未超时
*/
public boolean checkAndClose() {
if (System.currentTimeMillis() - lastUseTime > idlePeriodTime)
{
dds.close();
return true;
}
return false;
}
public DataSource getDds() {
return dds;
}
}
管理动态数据源
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import org.apache.tomcat.jdbc.pool.DataSource;
/**
* @ClassName: DDSHolder
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:19:07
*/
public class DDSHolder {
/**
* 管理动态数据源列表。<工程编码,数据源>
*/
private Map<String, DDSTimer> ddsMap = new HashMap<String, DDSTimer>();
/**
* 通过定时任务周期性清除不使用的数据源
*/
private static Timer clearIdleTask = new Timer();
static {
clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);
};
private DDSHolder() {
}
/*
* 获取单例对象
*/
public static DDSHolder instance() {
return DDSHolderBuilder.instance;
}
/**
* 添加动态数据源。
*
* @param projectCode 项目编码
* @param dds dds
*/
public synchronized void addDDS(String projectCode, DataSource dds) {
DDSTimer ddst = new DDSTimer(dds);
ddsMap.put(projectCode, ddst);
}
/**
* 查询动态数据源
*
* @param projectCode 项目编码
* @return dds
*/
public synchronized DataSource getDDS(String projectCode) {
if (ddsMap.containsKey(projectCode)) {
DDSTimer ddst = ddsMap.get(projectCode);
ddst.refreshTime();
return ddst.getDds();
}
return null;
}
/**
* 清除超时无人使用的数据源。
*/
public synchronized void clearIdleDDS() {
Iterator<Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();
for (; iter.hasNext(); ) {
Entry<String, DDSTimer> entry = iter.next();
if (entry.getValue().checkAndClose())
{
iter.remove();
}
}
}
/**
* 单例构件类
* @version 2018年2月26日
*/
private static class DDSHolderBuilder {
private static DDSHolder instance = new DDSHolder();
}
}
用不同的工程编码来区分数据库
/**
* @ClassName: DBIdentifier
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:16:45
*/
public class DBIdentifier {
private static ThreadLocal<String> projectCode = new ThreadLocal<String>();
public static String getProjectCode() {
return projectCode.get();
}
public static void setProjectCode(String code) {
projectCode.set(code);
}
}
定时任务
import java.util.TimerTask;
/**
* @ClassName: ClearIdleTimerTask
* @Description: TODO
* @author: shenying
* @date: 2018年7月26日 下午3:20:11
*/
public class ClearIdleTimerTask extends TimerTask {
@Override
public void run() {
DDSHolder.instance().clearIdleDDS();
}
}