问题
最近公司项目中遇到了一个问题,那就是Spring整合MyBatis需要动态切换数据源来访问不同的数据库,因为是游戏的后台,而公司的数据库都部署在不同的机器上,所以需要配置不同的数据源来达到访问数据的目的。一般的思路就是使用Spring配置数据源,然后动态的切换达到访问不同的数据库。
解决方法
自定义一个类继承自Spring的抽象类AbstractRoutingDataSource,然后实现其方法determineCurrentLookupKey()方法就可以了。上代码:
DynamicDataSource.java
package com.think.dao;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* 动态切换数据库
* Created by veion on 2016/10/23.
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
private static DynamicDataSource instance;
private static byte[] lock=new byte[0];
private static Map<Object,Object> dataSourceMap=new HashMap<Object, Object>();
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
dataSourceMap.putAll(targetDataSources);
//重要的方法,一定要加上不然会出现动态添加数据源的时候无法生效的情况
super.afterPropertiesSet();
}
public Map<Object, Object> getDataSourceMap() {
return dataSourceMap;
}
public static synchronized DynamicDataSource getInstance(){
if(instance==null){
synchronized (lock){
if(instance==null){
instance=new DynamicDataSource();
}
}
}
return instance;
}
//必须实现其方法
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDBType();
}
}
DataSourceContextHolder.java
package com.think.dao;
/**
* 数据库切换类
* Created by veion on 2016/10/23.
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static synchronized void setDBType(String dbType){
contextHolder.set(dbType);
}
public static String getDBType(){
return contextHolder.get();
}
public static void clearDBType(){
contextHolder.remove();
}
}
通过上面的方法之后,我们就可以在自己的项目中不管是动态创建数据源还是静态配置数据源都没有问题了。上一段我的数据源配置。
spring-config-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描注解Bean -->
<context:component-scan base-package="com.think">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 开启AOP监听 只对当前配置文件有效 -->
<aop:aspectj-autoproxy expose-proxy="true"/>
<!-- 数据源 -->
<bean id="thinkDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${think_public.url}"/>
<property name="username" value="${think.username}"/>
<property name="password" value="${think.password}"/>
<property name="initialSize" value="${druid.initialSize}"/>
<property name="minIdle" value="${druid.minIdle}"/>
<property name="maxActive" value="${druid.maxActive}"/>
<property name="maxWait" value="${druid.maxWait}"/>
<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${druid.validationQuery}"/>
<property name="testWhileIdle" value="${druid.testWhileIdle}"/>
<property name="testOnBorrow" value="${druid.testOnBorrow}"/>
<property name="testOnReturn" value="${druid.testOnReturn}"/>
<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}"/>
<property name="filters" value="${druid.filters}"/>
</bean>
<bean id="publicDataSource" parent="thinkDataSource">
<property name="url" value="${think_publicdata.url}"/>
</bean>
<bean id="ironStatsDB" parent="thinkDataSource">
<property name="url" value="${think_stats_url}"/>
</bean>
<!-- 动态数据源 -->
<bean id="dataSource" class="com.think.dao.DynamicDataSource" factory-method="getInstance">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="thinkDataSource" key="thinkDataSource"/>
<entry value-ref="publicDataSource" key="publicDataSource"/>
<entry value-ref="ironStatsDB" key="ironStatsDB"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="thinkDataSource"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="com.think.model"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.think.**.dao"/>
<property name="markerInterface" value="com.think.dao.MyMapper"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--事务管理器配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true" proxy-target-class="true">
<aop:advisor pointcut="execution(* com.think.service.*Service.insert*(..)) "
advice-ref="txAdvice"/>
<aop:advisor pointcut="execution(* com.think.service.*Service.delete*(..)) "
advice-ref="txAdvice"/>
<aop:advisor pointcut="execution(* com.think.service.*Service.update*(..)) "
advice-ref="txAdvice"/>
</aop:config>
</beans>
动态创建数据源添加到动态数据源中去。
Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();
Map<String, String> dbsMap = new HashMap<String, String>();
for (int i = 0; i < serverConfigModelList.size(); i++) {
ServerConfigModel model = serverConfigModelList.get(i);
if (serverIdList.contains(model.getServer_id())) {
continue;
} else {
serverIdList.add(model.getServer_id());
}
String localurl = "jdbc:mysql://" + model.getRemote_host() + ":"+ model.getData_port() + "/" +model.getDb_name()+ "?useUnicode=true&characterEncoding=UTF-8";
DynamicDataSourcePool dataSourcePool = new DynamicDataSourcePool(
model.getDataSourceName(), model.getDataSourcePassWord(),
localurl, "com.mysql.jdbc.Driver");
if (dataSourcePool != null && dataSourcePool.getPool() != null) {
/*
* dataSourcePool.getConnection(); factory.bind("" +
* model.getServer_id(), dataSourcePool);
*/
dataSourceMap.put(String.valueOf("game-" + model.getServer_id()),dataSourcePool.getPool());
dbsMap.put(String.valueOf("game-" + model.getServer_id()),String.valueOf("game-" + model.getServer_id()));
}
}
DBType.putDbMap(dbsMap);
DynamicDataSource.getInstance().setTargetDataSources(dataSourceMap);
The end.