当前位置: 代码迷 >> 综合 >> 多数据源切换-Druid
  详细解决方案

多数据源切换-Druid

热度:32   发布时间:2023-11-03 08:11:55.0

这是实际应用场景中的多数据源切换案例

逻辑思路如下:

1.系统初始化,加载所有数据库中配置的数据源,加载进去spring容器

2.通过两种方法切换数据源:

2.1MultipleDataSource.setDataSourceKey(dataSourceKey);//切换
MultipleDataSource.clearDataSourceKey();//清除当前数据源并且还原到默认数据库

2.2使用@SwitchDataSource(name="dateSourceKey")注解

 

 
<beans><-- 配置默认数据源 -->
<context:property-placeholder location="classpath*:jdbc.properties"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/><property name="minIdle" value="${rapid.jdbcPool.minPoolSize}"/><property name="maxActive" value="${rapid.jdbcPool.maxPoolSize}"/><property name="initialSize"value="${rapid.jdbcPool.initialPoolSize}"/><!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时,default=0 --><property name="maxWait"value="${rapid.jdbcPool.maxWait}"/><!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁 --><property name="timeBetweenEvictionRunsMillis"value="${rapid.jdbcPool.timeBetweenEvictionRunsMillise}"/><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,毫秒 --><property name="minEvictableIdleTimeMillis"value="${rapid.jdbcPool.minEvictableIdleTimeMillis}"/><!-- 配置一个连接在池中最小生存的时间,毫秒 --><!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 --><property name="testWhileIdle" value="${rapid.jdbcPool.testWhileIdle}"/><property name="testOnBorrow" value="false"/><property name="testOnReturn" value="false"/><!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都无效 --><property name="validationQuery" value="${rapid.jdbcPool.validationQuery}"/><!-- #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,default=false --><property name="poolPreparedStatements" value="${rapid.jdbcPool.poolPreparedStatements}"/><!-- #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。#在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 --><property name="maxOpenPreparedStatements" value="${rapid.jdbcPool.maxOpenPreparedStatements}"/><!-- #属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall --><!--<property name="filters" value="stat"/>--><property name="timeBetweenLogStatsMillis"value="${rapid.jdbcPool.timeBetweenLogStatsMillis}"/><!-- 定时把监控数据输出到日志中,毫秒 --><property name="proxyFilters"><list><ref bean="stat-filter"/><ref bean="log-filter"/></list></property>
</bean><!-- 多数据源切换 -->
<bean id="multipleDataSource" class="cn.core.persistent.hibernate.datasource.MultipleDataSource"><property name="defaultTargetDataSource" ref="dataSource"/><property name="targetDataSources"><map><entry key="dataSource" value-ref="dataSource"/></map></property>
</bean><!-- 多数据源注入 -->
<bean id="loadDataSource" class="cn.core.persistent.hibernate.datasource.LoadDataSource"><property name="baseExtendDao" ref="baseExtendDao"/>
</bean>
<!-- 声明事务 --><bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"><property name="dataSource" ref="multipleDataSource"/><property name="sessionFactory" ref="sessionFactory"/></bean><aop:config><aop:pointcut id="myMethod" expression="execution(* cn.*.service..*.*(..))"/><aop:advisor advice-ref="multipleDataSource" order="1"pointcut="execution(* cn.*.service..*.*(..)) and @annotation(cn.core.persistent.hibernate.datasource.SwitchDataSource)"/><aop:advisor advice-ref="txAdvice" pointcut-ref="myMethod" order="2"/></aop:config><tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes><tx:method name="add*" propagation="REQUIRED"/><tx:method name="append*" propagation="REQUIRED"/><tx:method name="insert*" propagation="REQUIRED"/><tx:method name="save*" propagation="REQUIRED"/><tx:method name="delete*" propagation="REQUIRED"/><tx:method name="remove*" propagation="REQUIRED"/><tx:method name="repair*" propagation="REQUIRED"/><tx:method name="delAndRepair*" propagation="REQUIRED"/><tx:method name="update*" propagation="REQUIRED"/><tx:method name="modify*" propagation="REQUIRED"/><tx:method name="edit*" propagation="REQUIRED"/><tx:method name="init*" propagation="REQUIRED"/><tx:method name="test*" propagation="REQUIRED"rollback-for="Exception"/><!-- 如果不写 rollback-for它弄死都不给你回滚,这个东西弄了半天 --><tx:method name="query*" propagation="SUPPORTS" read-only="true"/><tx:method name="select*" propagation="SUPPORTS" read-only="true"/><tx:method name="find*" propagation="SUPPORTS" read-only="true"/><tx:method name="load*" propagation="SUPPORTS" read-only="true"/><tx:method name="search*" propagation="SUPPORTS" read-only="true"/><!--指定当前方法以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,等我以非事务的状态运行完,再继续原来的事务。查询定义即可 read-only="true"  表示只读--><tx:method name="*" propagation="NOT_SUPPORTED" read-only="true"/><!--<tx:method name="logical*" propagation="REQUIRED" />&lt;!&ndash; 一般指逻辑删除 &ndash;&gt;--><!--<tx:method name="batch*" propagation="REQUIRED" />&lt;!&ndash; 批量操作 &ndash;&gt;--></tx:attributes></tx:advice>
</beans>
import cn.commons.string.StringUtils;
import cn.core.model.datasource.DataSource;
import cn.core.model.logger.DruidSQLLoggerMonitorAppender;
import cn.core.persistent.hibernate.AbstractHibernateDao;
import cn.core.persistent.hibernate.extend.BaseExtendDao;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;/*** 多数据源加载* @bug [nothing]* @see [nothing]* @备注:*/
public class LoadDataSource extends AbstractHibernateDao {protected final Log logger = LogFactory.getLog(getClass());private BaseExtendDao baseExtendDao;/*** 加载数据库内配置的数据源* */public List<DataSource> selectDataSourceForDateBase() {Session session = null;try {session = super.getSessionFactory().openSession();//查询数据库中所有数据源数据Query query = session.getNamedQuery("DataSource.findAll");return query.list();} catch (Exception e) {} finally {if (session != null) {session.flush();session.close();}}return new ArrayList();}/*** 创建数据源并注入进 spring 容器* */public void buiderDataSourceToSpring(ApplicationContext applicationContext) {//加载数据源//根据数据源,创建 DruidDataSource.class//重新创建多数据源List<DataSource> listDataSource = this.selectDataSourceForDateBase();if (listDataSource.size() > 0) {//默认数据源bean的idDruidDataSource druidDataSourceDefault = applicationContext.getBean(SwitchDataSource.master, DruidDataSource.class);Map<Object, Object> targetDataSources = new HashedMap();targetDataSources.put(SwitchDataSource.master, druidDataSourceDefault);//支撑库//Bean的实例工厂DefaultListableBeanFactory dbf = (DefaultListableBeanFactory) ((ConfigurableApplicationContext) applicationContext).getBeanFactory();for (Iterator<DataSource> iterator = listDataSource.iterator(); iterator.hasNext(); ) {DataSource ds = iterator.next();//Bean构建  BeanService.class 要创建的Bean的Class对象BeanDefinitionBuilder dataSourceBuider = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);//向里面的属性注入值,提供get set方法toolBuiderDataSourceToSpring(dataSourceBuider, ds);dataSourceBuider.setInitMethodName("init");dataSourceBuider.setDestroyMethodName("close");//将实例注册spring容器中 key 等同于 id配置dbf.registerBeanDefinition(ds.getDataSourceKey(), dataSourceBuider.getBeanDefinition());DruidDataSource druidDataSource = applicationContext.getBean(ds.getDataSourceKey(), DruidDataSource.class);targetDataSources.put(ds.getDataSourceKey(), druidDataSource);logger.info("加载数据源:名称->[" + ds.getDataSourceKey() + "],url->[" + druidDataSource.getUrl() + "],driver->[" + druidDataSource.getDriverClassName() + "],username->[" + druidDataSource.getUsername() + "]");}//重构多数据源列表MultipleDataSource multipleDataSource = applicationContext.getBean("multipleDataSource", MultipleDataSource.class);multipleDataSource.setTargetDataSources(targetDataSources);multipleDataSource.afterPropertiesSet();}}/*** 数据库配置的数据源注入容器** @title toolBuiderDataSourceToSpring* @param dataSourceBuider spring 容器注入实例*        ds 数据库配置的数据源* @return* @throws*/private void toolBuiderDataSourceToSpring(BeanDefinitionBuilder dataSourceBuider, DataSource ds) {Assert.notNull(ds, "多数据源注入异常 -> 配置的数据源不能为 null!!");Assert.notNull(dataSourceBuider, "多数据源注入异常 -> Spring 容器为 null!!");Assert.hasText(ds.getDriverClassName(), "多数据源加载异常 -> [" + ds.getDataSourceKey() + "] -> [driverClassName is null]");Assert.hasText(ds.getUrl(), "多数据源加载异常 -> [" + ds.getDataSourceKey() + "] -> [url is null]");Assert.hasText(ds.getUsername(), "多数据源加载异常 -> [" + ds.getDataSourceKey() + "] -> [username is null]");Assert.hasText(ds.getPassword(), "多数据源加载异常 -> [" + ds.getDataSourceKey() + "] -> [password is null]");dataSourceBuider.addPropertyValue("driverClassName", ds.getDriverClassName());dataSourceBuider.addPropertyValue("url", ds.getUrl());dataSourceBuider.addPropertyValue("username", ds.getUsername());dataSourceBuider.addPropertyValue("password", ds.getPassword());if (StringUtils.hasText(ds.getConnectPoolProperties())) {Map map = StringUtils.parseStringToMap(ds.getConnectPoolProperties(), ",", "=");if (map.containsKey("minPoolSize")) {//最小连接dataSourceBuider.addPropertyValue("minIdle", map.get("minPoolSize"));}if (map.containsKey("maxPoolSize")) {//最大连接dataSourceBuider.addPropertyValue("maxActive", map.get("maxPoolSize"));}if (map.containsKey("initialPoolSize")) {//初始连接数dataSourceBuider.addPropertyValue("initialSize", map.get("initialPoolSize"));}if (map.containsKey("maxWait")) {//获取连接时最大等待时间dataSourceBuider.addPropertyValue("maxWait", map.get("maxWait"));}if (map.containsKey("timeBetweenEvictionRunsMillis")) {//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,毫秒dataSourceBuider.addPropertyValue("timeBetweenEvictionRunsMillis", map.get("timeBetweenEvictionRunsMillis"));}if (map.containsKey("minEvictableIdleTimeMillis")) {//配置一个连接在池中最小生存的时间,毫秒dataSourceBuider.addPropertyValue("minEvictableIdleTimeMillis", map.get("minEvictableIdleTimeMillis"));}//是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,default=falseif (map.containsKey("poolPreparedStatements")) {dataSourceBuider.addPropertyValue("poolPreparedStatements", map.get("poolPreparedStatements"));}//#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。//#在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100if (map.containsKey("maxOpenPreparedStatements")) {dataSourceBuider.addPropertyValue("maxOpenPreparedStatements", map.get("maxOpenPreparedStatements"));}//用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都无效if (map.containsKey("validationQuery")) {String vq = (String) map.get("validationQuery");if (StringUtils.hasText(vq)) {dataSourceBuider.addPropertyValue("validationQuery", map.get("validationQuery"));//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效if (map.containsKey("testWhileIdle")) {dataSourceBuider.addPropertyValue("testWhileIdle", map.get("testWhileIdle"));}if (map.containsKey("testOnBorrow")) {dataSourceBuider.addPropertyValue("testOnBorrow", map.get("testOnBorrow"));}if (map.containsKey("testOnReturn")) {dataSourceBuider.addPropertyValue("testOnReturn", map.get("testOnReturn"));}}}//是否日志打印if (map.containsKey("statementExecutableSqlLogEnable")) {boolean isStatementExecutableSqlLogEnable = (Boolean) map.get("statementExecutableSqlLogEnable");if (isStatementExecutableSqlLogEnable) {DruidSQLLoggerMonitorAppender log4jFilter = new DruidSQLLoggerMonitorAppender();log4jFilter.setStatementExecutableSqlLogEnable(true);if (map.containsKey("serializationCurrentThreadExecutableSqlLogEnable")) {log4jFilter.setSerializationCurrentThreadExecutableSqlLogEnable((Boolean) map.get("serializationCurrentThreadExecutableSqlLogEnable"));}List list = new ArrayList();list.add(log4jFilter);dataSourceBuider.addPropertyValue("proxyFilters", list);}}}}public BaseExtendDao getBaseExtendDao() {return baseExtendDao;}public void setBaseExtendDao(BaseExtendDao baseExtendDao) {this.baseExtendDao = baseExtendDao;}}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchDataSource {String master = "dataSource";String dataSourceKey() default SwitchDataSource.master;}
import cn.commons.string.StringUtils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;/*** 多数据源切换* @bug [nothing]* @see [nothing]* @备注:*/
public class MultipleDataSource extends AbstractRoutingDataSource implements MethodInterceptor {//保存当前线程使用的数据源private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal();//设置数据源public static void setDataSourceKey(String dataSourceName) {if (StringUtils.hasText(dataSourceName)) {MultipleDataSource.dataSourceKey.set(dataSourceName);}}//清除数据源,清除后使用默认的支撑库数据源public static void clearDataSourceKey() {MultipleDataSource.dataSourceKey.remove();}/**** sessionFactory 加载数据源扩展方法* 详情请看 AbstractRoutingDataSource.determineTargetDataSource() 方法;* 这是一个扩展,返回的是数据源的名称,也就是 spring 配置的 baenName,通过返回的 beanName未找到数据源会直接使用支撑库做为数据源* */@Overrideprotected Object determineCurrentLookupKey() {//这里返回 null或""  没有关系,在 bean-core.xml 我们注入了默认数据源return MultipleDataSource.dataSourceKey.get();}/*** 结合 spring aop 与 annotation 动态切换数据源.* 框架通过 Aop 实现了事务管理,那么数据源的切换与事务控制作用于相同的时候方法上,并且优先于事务控制切换数据源.详细配置请参考 beans-core.xml** 如何使用:* 在需要切换数据源的service层的方法上,配置注解标签,并指定数据源名称,代码如下:* <code>*  @SwitchDataSource(name="dateSourceKey")*  public List getProducts(){*      ...*  }* </code>*/@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Object result = null;SwitchDataSource switchDataSource = null;try {switchDataSource = invocation.getMethod().getAnnotation(SwitchDataSource.class);//接口方法定义的注解if (switchDataSource == null) {
//				Class[] argsClass = new Class[invocation.getArguments().length];
//				for (int i = 0, j = invocation.getArguments().length; i < j; i++) {
//					argsClass[i] = invocation.getArguments()[i].getClass();
//				}//上面的获取方式有可能来至于接口,这里尝试在实现类获取注解
//				switchDataSource = invocation.getThis().getClass().getMethod(invocation.getMethod().getName(), argsClass).getAnnotation(SwitchDataSource.class);// 20170526 修改// 在接口中定义方法时,一般的形参都会实现父类或者接口.而实现类传入的实参又会是具体的实现类.所以导致直接使用 getMethod(String name, Class<?>... parameterTypes) 是无法获取方法的.//尝试在实现类获取注解String methodName = invocation.getMethod().getName();Object[] args = invocation.getArguments();//实际参数;如 arraylistMethod[] methods = invocation.getThis().getClass().getMethods();for (int i = 0; i < methods.length; i++) {if (methods[i].getName().equals(methodName)) {//方法相同Class[] typeClasss = methods[i].getParameterTypes();//定义的参数类型;如 listif (typeClasss.length == args.length) {//参数数量相等if (typeClasss.length == 0) {//无参情况switchDataSource = methods[i].getAnnotation(SwitchDataSource.class);break;} else {boolean isTrue = false;//判断所有的参数的类型是否相同for (int j = 0; j < args.length; j++) {if (typeClasss[j].isAssignableFrom(args[j].getClass())) {isTrue = true;} else {isTrue = false;break;}}if (isTrue) {switchDataSource = methods[i].getAnnotation(SwitchDataSource.class);break;}}}}}}if (switchDataSource != null) {MultipleDataSource.setDataSourceKey(switchDataSource.dataSourceKey());}result = invocation.proceed();} catch (Exception ex) {ex.printStackTrace();} finally {if (switchDataSource != null) {MultipleDataSource.clearDataSourceKey();//还原数据源;}}return result;}public static void main(String[] args) {System.out.println(ArrayList.class.isAssignableFrom(List.class));}
}
//系统初始化加载数据库中的数据源import javax.servlet.ServletException;import cn.core.persistent.hibernate.datasource.LoadDataSource;
import cn.core.persistent.hibernate.datasource.MultipleDataSource;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;import java.util.ArrayList;
import java.util.List;
import java.util.Map;/**** 提供框架启动初始化扩展功能,继承 DispatcherServlet 并实现 initFrameworkServlet() 方法,该方法原生提供 spring 扩展** 目前提供扩展如下:* 1.加载多数据源* @类名称:RapidFrameWorkServlet*/
public class RapidFrameWorkServlet extends DispatcherServlet {private static final long serialVersionUID = 1L;/*** 覆写spring的一个方法用来初始化进而得到初始化参数*/@Overridepublic void initFrameworkServlet() throws ServletException {try {//加载多数据源super.getWebApplicationContext().getBean("loadDataSource", LoadDataSource.class).buiderDataSourceToSpring(super.getWebApplicationContext());} catch (Exception e) {e.printStackTrace();}}
}
<-- 数据库中数据源对象 -->
CREATE TABLE DATASOURCE
(DATASOURCE_GUID          VARCHAR2(32 BYTE),DATASOURCE_NAME          VARCHAR2(128 BYTE),DATASOURCE_KEY           VARCHAR2(128 BYTE)   NOT NULL,DRIVER_CLASS_NAME        VARCHAR2(256 BYTE)   NOT NULL,URL                      VARCHAR2(512 BYTE)   NOT NULL,USERNAME                 VARCHAR2(128 BYTE)   NOT NULL,PASSWORD                 VARCHAR2(128 BYTE)   NOT NULL,CONNECT_POOL_PROPERTIES  VARCHAR2(1024 BYTE),OPERATOR_ID              VARCHAR2(64 BYTE),CREATE_TIME              DATE,MODIFY_TIME              DATE,RES_ACT_MAP_ID           VARCHAR2(64 BYTE),VALID_SIGN               NUMBER
)
//数据源控制层类,在新增数据源后重新加载容器的数据源
public class DataSourceResource implements ApplicationContextAware {@Resourceprivate IDataSourceService dataSourceService;@Resource(name="loadDataSource")private LoadDataSource loadDataSource;private ApplicationContext applicationContext;public void setApplicationContext(ApplicationContext paramApplicationContext) throws BeansException {applicationContext=paramApplicationContext;}/*** 添加数据* @param dataSourceType* @return*/@RequestMapping("/put")@ResponseBody@RepeatSubmitTokenpublic boolean addDataSourceTypeAction(DataSource dataSource){boolean flag= dataSourceService.selectDataSourceName(dataSourceType.getDataSourceName());if(flag){return false;}boolean isSuccess = dataSourceService.addDataSourceType(dataSource);//数据源添加成功后重新加载数据源loadDataSource.buiderDataSourceToSpring(applicationContext);return isSuccess;}
}

 

  相关解决方案