应用场景:查询业务为主,多个数据源,根据不同的请求,切换到不同的数据源;
1:创建DynamicDataSource
扩展一个Spring提供的AbstractRoutingDataSource,Override 其中的 determineCurrentLookupKey方法实现数据源的路由
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.Assert;
/**
* Dynamic DataSource
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
//@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
2:创建CustomerContextHolder
CustomerContextHolder这是开发人员自己实现的一个封装了ThreadLocal类型的ContextHolder。
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
3:bean.xml
xml 中有一个默认的数据源,多数据库的信息在默认数据源表中存放着;
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf-8</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>root</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>3600000</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>3600000</value>
</property>
</bean>
<!-- S -->
<bean id="dynamicDataSource" class="common.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 动态初始化map-->
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource" >
</property>
</bean>
<!-- E -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dynamicDataSource" ></property>
</bean>
启动时,需要用到默认数据源资源,从其表中得到数据,从而创建新的数据源bean
<bean id="applicationEventListener" class="common.DynamicCreateDataSourceBean">
<property name="dynamicDataSource" ref="dynamicDataSource" ></property>
<property name="jdbcTemplate" ref="jdbcTemplate" ></property>
</bean >
4:DynamicCreateDataSourceBean
动态创建datasourcebean
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* 实现一个实现ApplicationContextAware和ApplicationListener接口的类DynamicDataSourceC3p0,
* 实现ApplicationContextAware是为了得到ApplicationContext,
* 实现了ApplicationListener是为了配置spring的加载事件。
*
*/
public class DynamicCreateDataSourceBean implements ApplicationContextAware,ApplicationListener{
private ConfigurableApplicationContext app;
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate)
{
this.jdbcTemplate = jdbcTemplate;
}
private DynamicDataSource dynamicDataSource;
public void setDynamicDataSource(DynamicDataSource dynamicDataSource) {
this.dynamicDataSource = dynamicDataSource;
}
public void setApplicationContext(ApplicationContext app) throws BeansException {
this.app = (ConfigurableApplicationContext)app;
}
public void onApplicationEvent(ApplicationEvent event) {
// 如果是容器刷新事件OR Start Event
if (event instanceof ContextRefreshedEvent) {
try {
regDynamicBean();
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println(event.getClass().getSimpleName()+" 事件已发生!");
}
}
private void regDynamicBean() throws IOException {
// 解析属性文件,得到数据源Map
Map<String, DataSourceInfo> mapCustom = parsePropertiesFile();
// 把数据源bean注册到容器中
addSourceBeanToApp(mapCustom);
}
/**
* 功能说明:根据DataSource创建bean并注册到容器中
*
* @param acf
* @param mapCustom
*/
private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) {
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) app
.getAutowireCapableBeanFactory();
String DATASOURCE_BEAN_CLASS = "org.apache.commons.dbcp.BasicDataSource";
BeanDefinitionBuilder bdb;
Iterator<String> iter = mapCustom.keySet().iterator();
Map<String,Object> targetDataSources = new LinkedHashMap<String, Object>();
baseBeanComm = new ChildBeanDefinition("dataSource");
// 将默认数据源放入 targetDataSources map中
targetDataSources.put("dataSourceA", app.getBean("dataSource"));
// 根据数据源得到数据,动态创建数据源bean 并将bean注册到applicationContext中去
while (iter.hasNext()) {
// bean ID
String beanKey = iter.next();
// 创建bean
bdb = BeanDefinitionBuilder.rootBeanDefinition(DATASOURCE_BEAN_CLASS);
bdb.getBeanDefinition().setAttribute("id", beanKey);
bdb.addPropertyValue("driverClassName", "org.gjt.mm.mysql.Driver");
bdb.addPropertyValue("url", mapCustom.get(beanKey).connUrl);
bdb.addPropertyValue("username", mapCustom.get(beanKey).userName);
bdb.addPropertyValue("password", mapCustom.get(beanKey).password);
bdb.addPropertyValue("timeBetweenEvictionRunsMillis", 3600000);
bdb.addPropertyValue("minEvictableIdleTimeMillis", 3600000);
// 注册bean
acf.registerBeanDefinition(beanKey, bdb.getBeanDefinition());
// 放入map中,注意一定是刚才创建bean对象
targetDataSources.put(beanKey, app.getBean(beanKey));
}
// 将创建的map对象set到 targetDataSources;
dynamicDataSource.setTargetDataSources(targetDataSources);
// 必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动态切换才会起效
dynamicDataSource.afterPropertiesSet();
}
/**
* 功能说明:GET ALL SM_STATIONS FROM DB1
*
* @return
* @throws IOException
*/
private Map<String, DataSourceInfo> parsePropertiesFile(String fileName)
throws IOException {
String sql = "SELECT STATION_ID,CENTER_DB_NAME FROM SM_STATION ";
List list = jdbcTemplate.queryForList(sql);
Iterator iterator = list.iterator();
Map<String, DataSourceInfo> mds = new HashMap<String, DataSourceInfo>();
while (iterator.hasNext()) {
Map map4station = (Map) iterator.next();
DataSourceInfo dsi = new DataSourceInfo();
String username = "root";
String password = "root";
String url_1 = "jdbc:mysql://127.0.0.1:3306/";
String dbName = map4station.get("CENTER_DB_NAME")+"";
String url_2 = "?useUnicode=true&characterEncoding=utf-8";
String key = "dataSource_";
String sid = map4station.get("STATION_ID")+"";
key = key.concat(sid);
String url = url_1.concat(dbName).concat(url2);
dsi.connUrl = url;
dsi.userName = username;
dsi.password = password;
mds.put(key, dsi);
}
return mds;
}
// 自定义数据结构
private class DataSourceInfo{
public String connUrl;
public String userName;
public String password;
public String toString() {
return "(url:" + connUrl + ", username:" + userName + ", password:"
+ password + ")";
}
}
}
5:如何切换
创建过滤器,得到页面传过来的切换标示,直接根据切换标示切换数据源即可;而实际后台的业务不需要做任何修改;
CustomerContextHolder.setCustomerType(beanKey);
//beanKey 就是我们创建bean 的ID 值;
参考:http://lyunabc.iteye.com/blog/1544423 --动态切换写死在配置文件的数据源;
http://blog.csdn.net/littlechang/article/details/8071882--动态创建bean;