应用场景:查询业务为主,多个数据源,根据不同的请求,切换到不同的数据源;
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;