Spring基于Bean对所有的组件进行管理,通过Bean解决各个模块之间的依赖关系,因此需要深入理解BeanFactory的机制。例如在本例中,如果要实现在查询的过程中,针对不同用户或者模块配置的不同的数据表,进行动态切换数据源(更改数据库连接、用户名和密码)进行查询的功能,需要理解Bean之间的依赖和调用关系。首先,配置数据库的链接,可以使用连接池或Spring自带的链接管理类,本例中使用DHCP的连接池实现链接的管理,通过读取配置文件中的链接,从而建立数据库的链接,配置文件如下所示:
<!--加载properties文件,供其他bean使用,用${...}来调用 -->
<context:property-placeholder location="classpath:inspur.properties" />
<bean id="sys_dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" autowire="byName">
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="maxActive">
<value>${jdbc.maxActive}</value>
</property>
<property name="maxIdle">
<value>${jdbc.maxIdle}</value>
</property>
<property name="testWhileIdle">
<value>true</value>
</property> <!-- 打开检查,用异步线程evict进行检查 -->
<property name="testOnBorrow">
<value>false</value>
</property>
<property name="testOnReturn">
<value>false</value>
</property>
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>30000</value>
</property>
<property name="numTestsPerEvictionRun">
<value>${jdbc.maxActive}</value>
</property>
</bean>
${jdbc.driver}、${jdbc.url}、${jdbc.username}、${jdbc.password}即为从配置文件中读取的数值,本链接使用的class为org.apache.commons.dbcp.BasicDataSource即DHCP连接池,id为sys_dataSource,可以供其他Bean使用ref=sys_dataSource进行调用,具体可见Spring中Bean配置。Mybatis和Spring整合之后,不需要自己声明SQLSessionFactory,是因为使用了依赖注入的方式,实现了SQLSessionFactory Bean,配置文件如下:
<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource" />
<property name="configLocation" value="mybatis_core.xml" />
<!-- 显式指定Mapper文件位置 -->
<property name="mapperLocations"
value="classpath:com/inspur/plugins/metaconf/conf/mybatis_*.xml" />
</bean>
<!-- 扫描basePackage下所有以@MyBatisRepository标识的 接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.inspur.plugins.metaconf.dao" />
<property name="annotationClass"
value="com.inspur.system.repository.mybatis.MyBatisRepository" />
</bean>
其中的ref便是指向数据源连接的Beanid,如果不需要动态数据源切换,应指向上文中配置的sys_dataSource。如果要进行动态数据源的切换,则需要根据Spring执行数据源链接的过程,重写其部分方法,并交于Spring管理。Spring在AbstractRoutingDataSource中进行数据库的链接,本类实现了InitializingBean接口,即Spring启动时会加载该类中的方法,如果要实现数据源动态切换,需要继承该类,并重写其中的方法,其中determineTargetDataSource()方法会在选择数据源时执行,determineCurrentLookupKey()决定选择数据源的名称,具体可以阅读Spring的源码,使用afterPropertiesSet()方法保存数据源,使用的方式为key-value的Map形式,Map的变量名为targetDataSource,即所有的数据源链接皆存于targetDataSource该Map中,通过key-value的方式进行选择,实现动态数据源切换,可以重写determineTargetDataSource()方法,在该方法中,根据每一个查询线程创建一个新的数据源,并保存到targetDataSource中,从而实现数据源的动态切换。具体方法可见代码如下:
package com.inspur.plugins.metaconf.comm;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import javax.transaction.SystemException;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import com.inspur.plugins.metaconf.model.DataSourceVO;
import com.inspur.plugins.metaconf.model.Dbs;
import com.inspur.plugins.metaconf.model.MetaConf;
import com.inspur.plugins.metaconf.model.MetaModelConf;
/**
* 数据源动态切换类
* <功能详细描述>
*
* @author Jeff.Wang
* @version [版本号, 2018年7月13日]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource
{
//@Autowired
//private CenterDatabaseManager centerDatabaseManager;
private Logger log = Logger.getLogger(this.getClass());
// 默认数据源,也就是主库
protected DataSource masterDataSource;
// 保存动态创建的数据源
private static final Map targetDataSource = new HashMap();
@Override
public DataSource determineTargetDataSource() {
// 根据数据库选择方案,拿到要访问的数据库
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>根据数据库选择方案,拿到要访问的数据库");
String dataSourceName = determineCurrentLookupKey();
if("dataSource".equals(dataSourceName)) {
// 访问默认主库
return masterDataSource;
}
// 根据数据库名字,从已创建的数据库中获取要访问的数据库
DataSource dataSource = (DataSource) targetDataSource.get(dataSourceName);
if(null == dataSource) {
// 从已创建的数据库中获取要访问的数据库,如果没有则创建一个
try
{
dataSource = this.selectDataSource(dataSourceName);
}
catch (SystemException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return dataSource;
}
@Override
protected String determineCurrentLookupKey() {
// TODO Auto-generated method stub
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>determineCurrentLookupKey()");
String dataSourceName = Dbs.getDbType();
if (dataSourceName == null || dataSourceName == "dataSource") {
// 默认的数据源名字
dataSourceName = "dataSource";
}
log.debug("use datasource : " + dataSourceName);
return dataSourceName;
}
public void addTargetDataSource(String key, BasicDataSource dataSource) {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>addTargetDataSource()");
this.targetDataSource.put(key, dataSource);
//setTargetDataSources(this.targetDataSource);
}
/**
* 该方法为同步方法,防止并发创建两个相同的数据库
* 使用双检锁的方式,防止并发
* @param dbType
* @return
* @throws SystemException
*/
private synchronized DataSource selectDataSource(String dbType) throws SystemException {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>selectDataSource()");
// 再次从数据库中获取,双检锁
DataSource obj = (DataSource)this.targetDataSource.get(dbType);
if (null != obj) {
return obj;
}
// 为空则创建数据库
BasicDataSource dataSource = this.getDataSource(dbType);
if (null != dataSource) {
// 将新创建的数据库保存到map中
this.setDataSource(dbType, dataSource);
return dataSource;
}else {
throw new SystemException("创建数据源失败!");
}
}
/**
* 查询对应数据库的信息
* @param dbtype
* @return
*/
private BasicDataSource getDataSource(String dbType) {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>getDataSource()");
String oriType = Dbs.getDbType();
// 先切换回主库
Dbs.setDbType("dataSource");
// 查询所需信息
MetaModelConf model = MetaConf.getInstance().getModelById(dbType);
DataSourceVO database = model.getDataSource();
// 切换回目标库
Dbs.setDbType(oriType);
/* String url = "jdbc:sqlserver://" + datebase.getIp() + ":1433"
+ ";DatabaseName=" + datebase.getDatabaseName();*/
BasicDataSource dataSource = createDataSource(database.getDriver(),database.getUrl(),database.getUsername(),database.getPassword());
return dataSource;
}
//创建数据源
private BasicDataSource createDataSource(String driverClassName, String url,
String username, String password) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setMaxActive(5);
dataSource.setMaxIdle(2);
dataSource.setTestWhileIdle(true);
dataSource.setValidationQuery("select sysdate from dual");
return dataSource;
}
public void setDataSource(String type, BasicDataSource dataSource) {
this.addTargetDataSource(type, dataSource);
Dbs.setDbType(type);
}
/**
* 设置数据库 id 和 DataSource
*/
public void setDataSource(String sourceId, DataSourceVO dataSourceVO){
BasicDataSource dataSource =
this.createDataSource(dataSourceVO.getDriver(), dataSourceVO.getUrl(),
dataSourceVO.getUsername(), dataSourceVO.getPassword());
this.addTargetDataSource(sourceId, dataSource);
Dbs.setDbType(sourceId);
}
/*@Override
public void setTargetDataSources(Map targetDataSources) {
// TODO Auto-generated method stub
super.setTargetDataSources(targetDataSources);
// 重点:通知container容器数据源发生了变化
afterPropertiesSet();
}*/
/**
* 该方法重写为空,因为AbstractRoutingDataSource类中会通过此方法将,targetDataSources变量中保存的数据源交给resolvedDefaultDataSource变量
* 在本方案中动态创建的数据源保存在了本类的targetDataSource变量中。如果不重写该方法为空,会因为targetDataSources变量为空报错
* 如果仍然想要使用AbstractRoutingDataSource类中的变量保存数据源,则需要在每次数据源变更时,调用此方法来为resolvedDefaultDataSource变量更新
*/
@Override
public void afterPropertiesSet() {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>afterPropertiesSet()");
}
public DataSource getMasterDataSource() {
return masterDataSource;
}
public void setMasterDataSource(DataSource masterDataSource) {
this.masterDataSource = masterDataSource;
}
}
Dbs的代码如下:
package com.inspur.plugins.metaconf.model;
/**
* 负责切换数据源
* <功能详细描述>
*
* @author Jeff.Wang
* @version [版本号, 2018年7月12日]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public class Dbs
{
private static final ThreadLocal<String> local = new ThreadLocal<String>();
public static String getDbType(){
return local.get();
}
public static void setDbType(String dbName){
local.set(dbName);
}
public static void clear(){
local.remove();
}
}
如果要实现动态数据源切换,则需要以后所有的需要数据源连接的地方,ref的值指向该数据源动态管理类,例如Mybatis中的数据源管理:
<!-- 数据源动态管理的Bean -->
<bean id="multipleDataSource" class="com.inspur.plugins.metaconf.comm.DynamicDataSource">
<property name="masterDataSource" ref="sys_dataSource"></property>
</bean>
<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource" />
…………………………………………………………………省略
</bean>
此后在进行查询时,只需要调用setDataSource()方法即可实现数据源的动态切换。
可参考: https://www.cnblogs.com/wsss/p/5475057.html 以及 http://blog.51cto.com/chrischen/1878517