我写的和网上的略有不同,主要是通过动态注入数据源的方式来动态确定数据源。首先介绍网上主流的一主多从:Spring配置主从数据库,也就是读写分离,网上的大多数代码都是通过配置多个数据库连接池来实现,继承AbstractRoutingDataSource,将多个数据源以Map的key-value注入到targetDataSources。AbstractRoutingDataSource实现了InitializingBean,所以会调用afterPropertiesSet()完成一系列的初始化动作,我们需要实现的只有determineCurrentLookupKey()方法,此方法返回的相当于是一个key值,去取targetDataSources中所对应的数据源。而因为AbstractRoutingDataSource继承了AbstractDataSource所以会在连接数据库的时候,调用getConnetion,而getConnection会调用determineTargetDataSource()方法,determineTargetDataSource()方法调用determineCurrentLookupKey(),从而得到datasource。网上的代码就介绍到这里。
下面讲我的方式:
1.配置数据源,因为这里因为主从的配置内容,中,只有数据库地址不同,所以,我将url剥离出去,相当于生成一个模板datasource,只需要动态确定jdbcUrl而不需要配置多个数据源。以下是bean.xml
<bean id="datasourceDemo" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass">
<value>${jdbc.driverClassName}</value>
</property>
<property name="properties">
<props>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
</props>
</property>
<property name="minPoolSize">
<value>${c3p0.minPoolSize}</value>
</property>
<property name="maxPoolSize">
<value>${c3p0.maxPoolSize}</value>
</property>
<property name="maxStatements">
<value>${c3p0.maxStatements}</value>
</property>
<property name="testConnectionOnCheckin">
<value>${c3p0.testConnectionOnCheckin}</value>
</property>
<property name="automaticTestTable">
<value>${c3p0.automaticTestTable}</value>
</property>
<property name="idleConnectionTestPeriod">
<value>${c3p0.idleConnectionTestPeriod}</value>
</property>
<property name="maxIdleTime">
<value>${c3p0.maxIdleTime}</value>
</property>
<property name="testConnectionOnCheckout">
<value>${c3p0.testConnectionOnCheckout}</value>
</property>
</bean>
<bean id="databaseSources" class="com.kf.database.DatabaseSources">
</bean>
<bean id="dataSource" class="com.kf.database.DynamicDataSource">
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>sql-map-config.xml</value>
</property>
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient">
<ref bean="sqlMapClient" />
</property>
</bean>
<bean id="messageDao" class="com.kf.dao.MessageDaoImpl">
<property name="sqlMapClientTemplate">
<ref bean="sqlMapClientTemplate" />
</property>
</bean>
2.DynamicDataSource为了简单方便我还是实现了AbstractRoutingDataSource方法,这里也可以实现DataSource,没有区别
package com.kf.database;
/**
* @author yan
*
*/
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.util.Assert;
import com.kf.util.DbContextHolder;
public class DynamicDataSource extends AbstractRoutingDataSource {
// private Map targetDataSources;
@SuppressWarnings("rawtypes")
private Map resolvedDataSources;
private DataSource resolvedDefaultDataSource;
@SuppressWarnings("unused")
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Lock lock = new ReentrantLock();
private volatile int count = 0;
@SuppressWarnings("rawtypes")
public DynamicDataSource() {
resolvedDataSources = new HashMap();
}
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource) this.resolvedDataSources.get(lookupKey);
if (dataSource == null) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
protected Object determineCurrentLookupKey() {
String status = DbContextHolder.getDbType();
if ("master".equals(status) || (DbContextHolder.getSlaveCount() == 0)) {
return "master"; //这里是主数据库
} else {
lock.lock();
try { //从的数据库,进行动态分配
count++;
int lookupKey = count % DbContextHolder.getSlaveCount();
return String.valueOf(lookupKey + 1);
} finally {
lock.unlock();
}
}
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@SuppressWarnings("rawtypes")
public Map getResolvedDataSources() {
return resolvedDataSources;
}
@Override
public void afterPropertiesSet() { //此函数我主要是是为了屏蔽AbstractRoutingDataSource中的一系列的初始化动作
}
}
3,动态注入数据源类,具体的我将属性文件中的第一个作为主数据源只写,其他的作为从数据源只读,master作为主数据源的key,而从数据源用数字来代替,主要是为了负载均衡
package com.kf.database;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
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 com.kf.util.DBUtils;
import com.kf.util.DbContextHolder;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DatabaseSources implements ApplicationContextAware { //这个接口是为了,当bean接口初始化的时候,获取context
private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseSources.class);
private static ApplicationContext context;
public static DatabaseSources getInstance() {
if (context != null) {
return (DatabaseSources) context.getBean("databaseSources");
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
DatabaseSources.context = applicationContext;
}
@SuppressWarnings("unchecked")
public void init() { //这个函数是重点,读取属性文件中的url,然后动态生成多个数据库连接池,动态注入到DynamicDataSource类
List<String> urlList = DBUtils.getUrls(DBUtils.initUrlMap());
Iterator<String> iter = urlList.iterator();
int count = 0;
DynamicDataSource dynamicDataSource = (DynamicDataSource) context.getBean("dataSource");
while (iter.hasNext()) {
String url = (String) iter.next();
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition;
beanDefinition = new ChildBeanDefinition("datasourceDemo"); //获取数据源模板
if (count == 0) {
acf.registerBeanDefinition("master", beanDefinition); //注册bean
ComboPooledDataSource cpds = (ComboPooledDataSource) context.getBean("master");
cpds.setJdbcUrl(url); //设置bean的地址
dynamicDataSource.getResolvedDataSources().put("master", cpds); //注入DynamicDataSource
} else {
acf.registerBeanDefinition("" + count, beanDefinition);
ComboPooledDataSource cpds = (ComboPooledDataSource) context.getBean("" + count);
cpds.setJdbcUrl(url);
dynamicDataSource.getResolvedDataSources().put("" + count, cpds); //以数字作为key值,注入DynamicDataSource
}
count++;
}
}
@SuppressWarnings("unchecked")
public void addSlaveDatasource(List<String> urlList) { 此函数为了添加从数据源
Iterator<String> iter = urlList.iterator();
int count = 1;
DynamicDataSource dynamicDataSource = (DynamicDataSource) context.getBean("dataSource");
while (iter.hasNext()) {
int tempID = DbContextHolder.getSlaveCount() + count;
String url = (String) iter.next();
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition;
beanDefinition = new ChildBeanDefinition("datasourceDemo");
acf.registerBeanDefinition("" + count, beanDefinition);
ComboPooledDataSource cpds = (ComboPooledDataSource) context.getBean("" + tempID);
cpds.setJdbcUrl(url);
dynamicDataSource.getResolvedDataSources().put("" + tempID, cpds);
count++;
}
DbContextHolder.setSlaveCount(DbContextHolder.getSlaveCount() + urlList.size());
}
@SuppressWarnings("unchecked")
public void setMasterDatasource(String url){ //此函数是为了更改主数据源做的
DynamicDataSource dynamicDataSource = (DynamicDataSource) context.getBean("dataSource");
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition;
beanDefinition = new ChildBeanDefinition("datasourceDemo");
acf.registerBeanDefinition("master", beanDefinition);
ComboPooledDataSource cpds = (ComboPooledDataSource) context.getBean("master");
cpds.setJdbcUrl(url);
dynamicDataSource.getResolvedDataSources().put("master", cpds);
}
}
大体就是以上的完成了,就可以实现数据库的负载均衡,以及动态加载数据源