在应对大量用户读取的系统中,对数据库的操作通常采用读写分离方式,往一个数据库写入,然后通过复制将数据同步到另外的多个数据库中,读操作都从这些数据库中操作,在采用spring来配置多数据库时,并不能直接支持从多个DataSource中获得数据库连接,为此需要开发一个DataSource的代理,代理实现javax.sql.DataSource接口。该代理根据一定的策略从已有的多个DataSource中选择一个,提供给SessionFactory,供数据访问层使用。原理如下图所示:
DataSource Proxy选择DataSource的方式可以根据实际进行设计,这里为简单,就采用随机方式选择一个。源代码如下:
package code;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Random;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class RandomDataSource implements DataSource {
private List<DataSource> dataSourcePool ;
protected Log log = LogFactory.getLog(getClass());
private static ThreadLocal<DataSource> dsHolder = new ThreadLocal<DataSource>();
/**
* 从已有DataSource中随机选择一个
*
*
*/
private DataSource randomDs(){
int size = dataSourcePool.size();
Random r= new Random();
int t = r.nextInt(size);
dsHolder.set(dataSourcePool.get(t));
return dsHolder.get();
}
@Override
public Connection getConnection() throws SQLException {
Connection conn = randomDs().getConnection();
log.info("conn URL---->"+conn.getMetaData().getURL());
return conn;
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
// TODO Auto-generated method stub
Connection conn = randomDs().getConnection(username, password);
return conn;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return dsHolder.get().getLogWriter();
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return dsHolder.get().getLoginTimeout();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
dsHolder.get().setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
dsHolder.get().setLoginTimeout(seconds);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return dsHolder.get().isWrapperFor(iface);
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return dsHolder.get().unwrap(iface);
}
public List<DataSource> getDataSourcePool() {
return dataSourcePool;
}
public void setDataSourcePool(List<DataSource> dataSourcePool) {
this.dataSourcePool = dataSourcePool;
}
}
为了可用性,实际生产环境中需要判断取到的DataSource对应的数据库是否可访问,若不能访问,则要取其他可用的DataSource。
在springframework中的配置如下:
<bean id="dataSource" class="code.RandomDataSource">
<property name="dataSourcePool">
<list>
<ref bean="dataSource1" />
<ref bean="dataSource2" />
</list>
</property>
</bean>
<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://192.168.2.192/ebook"/>
<property name="user" value="test"/>
<property name="password" value="test"/>
</bean>
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://192.168.2.105/ebook"/>
<property name="user" value="test"/>
<property name="password" value="test"/>
</bean>
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="dataSource" ref="dataSource"/>
</bean>
这里连接的是MySQL数据库,如果是其他数据库,需要将DataSource1和DataSource2中的参数以及hibernate.dialect做相应修改。