Spring之动态切换数据库

        在做项目时,当数据量很大需要将数据存在不同的数据库或者希望将不同类型数据存入不同数据库时,就需要利用Spring进行动态的数据库切换。

1.1 原理示意图

        其原理如下图所示:

 

1.2 数据库配置

        要实现动态切换数据源,首先需要将各个数据源配置好,如下所示:

	<bean id="parentDataSource" abstract="true"
		  class="org.apache.tomcat.jdbc.pool.DataSource"
		  destroy-method="close"
		  p:maxWait="10000"
		  p:removeAbandoned="true"
		  p:removeAbandonedTimeout="180"
		  p:connectionProperties="clientEncoding=UTF-8"
		  p:validationQuery="SELECT 1"
		  p:validationInterval="30000"
		  p:testOnBorrow="false"
		  p:testOnReturn="false"
		  p:testWhileIdle="true"
		  p:timeBetweenEvictionRunsMillis="10000"
		  p:minEvictableIdleTimeMillis="60000"
		  p:logAbandoned="false"
		  p:defaultAutoCommit="true" />

	<bean id="dataSource" parent="parentDataSource"
		  p:driverClassName="com.mysql.jdbc.Driver"
		  p:username="${jdbc.user}"
		  p:password="${jdbc.password}"
		  p:initialSize="20"
		  p:maxActive="200"
		  p:maxIdle="200"
		  p:minIdle="5"/>

	<bean id="childDataSource1" parent="dataSource">
		<property name="url" value="${jdbc.url1}" />
	</bean>

	<bean id="childDataSource2" parent="dataSource">
		<property name="url" value="${jdbc.url2}" />
	</bean>
	

 

1.3 Java实现      

        之后,我们就需要利用spring中对动态选择数据源的支持,来实现将数据写入不同的数据库。

        先把定义的多个数据库bean放一放,先来看下spring中对动态选择数据源的支持。

        在spring中有一个抽象类AbstractRoutingDataSource类,通过这个类可以实现动态选择数据源。来看下这个类的成员变量。

    private Map<Object, Object> targetDataSources;
    private Object defaultTargetDataSource;
    private Map<Object, DataSource> resolvedDataSources;

       targetDataSources中保存了key和数据库连接的映射关系,defaultTargetDataSource表示默认的链接,resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储的结构也是数据库标识和数据源的映射关系。

        下面需要继承AbstractRoutingDataSource类,实现我们自己的数据库选择逻辑DataSourceSwitcher类,先上代码:

public class DataSourceSwitcher extends AbstractRoutingDataSource{

    private static final Logger LOGGER = LoggerFactory.getLogger("INTERACTIVE_LOGGER");

    private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();


    public static void clearDataSourceType() {
        LOGGER.debug("thread:{},remove,dataSource:{}",Thread.currentThread().getName());
        dataSourceKey.remove();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String s = dataSourceKey.get();
        LOGGER.debug("thread:{},determine,dataSource:{}",Thread.currentThread().getName(),s);
        return s;
    }

    public static void setDataSourceKey(String dataSource) {
        LOGGER.debug("thread:{},set,dataSource:{}",Thread.currentThread().getName(),dataSource);
        dataSourceKey.set(dataSource);
    }
}

        第5行,threadLocal的成员变量dataSource(由于不同的请求所需要的数据源可能不一样),用于存储数据源标识。

        第8行,清除数据源操作.

        第14行,该方法决定了需要使用哪个数据库,该方法是抽象方法,必须由我们实现,那么现在来看下这个方法是如何使用的。

	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();//这里获取数据库标识
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);//获得具体的数据源
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

        所以我们需要在determineCurrentLookupKey方法中返回数据库标识即可。

        第20行,设置数据源方法。

 

1.4 数据库配置和DataSourceSwitcher类结合

        现在把我们之前的数据库配置和DataSourceSwitcher进行合并,我在数据库的xml配置上添加如下配置:

	<bean id="dataSourceSwitcher" class="com.netease.mail.activity.service.switcher.DataSourceSwitcher">
		<property name="targetDataSources">
			<map>
				<entry key="ds1" value-ref="childDataSource1"/>
				<entry key="ds2" value-ref="childDataSource2"/>
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="childDataSource1"/>
	</bean>

	<bean id="transactionManager"
		  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSourceSwitcher" />
	</bean>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:mybatis/sql-map-config.xml" />
		<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
		<property name="dataSource" ref="dataSourceSwitcher" />
	</bean>

        可以看到,这里对targetDataSources进行了初始化,ds1对应了数据源childDataSource1;ds2对应了数据源childDataSource2。

        使用的话只要调用DataSourceSwitcher.setDataSourceKey(“ds1”),即将数据源切换到了childDataSource1。

1.5 增加切面处理

        如果每次执行方法都要设置一下数据源实在是件很麻烦的事情,另外我们需要对某个key进行hash后选择数据库,这块也没有实现。现在借助spring切面的功能,可以解决这两个问题。

 

1.6 扩展

上面我们只是实现了 master-slave 数据源的选择。如果有多台 master 或者有多台 slave。多台master组成一个HA,要实现当其中一台master挂了是,自动切换到另一台master,这个功能可以使用LVS/Keepalived来实现,也可以通过进一步扩展ThreadLocalRountingDataSource来实现,可以另外加一个线程专门来每个一秒来测试mysql是否正常来实现。同样对于多台slave之间要实现负载均衡,同时当一台slave挂了时,要实现将其从负载均衡中去除掉,这个功能既可以使用LVS/Keepalived来实现,同样也可以通过近一步扩展ThreadLocalRountingDataSource来实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值