当时想写没有动笔是因为,动态数据源切换解决了,但是无法做到动态添加,添加新的数据源需要重启web服务器。后来这个问题解决了,但因为业务忙,今日动笔,为了写这篇文章一周没关电脑了。
公司的业务是这样的,有目前有将近150多个db,每个的db的数据库库结构一致。位于同一台机器的不同端口下。现在需要做个管理系统,能管理这些db。并且这些db的数目在不断的增长中。不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring框架中总是配置一个数据源,如何在DAO在访问sessionFactory的时候在多个数据源中不断切换。
面对这个的需求的要解决的技术问题有:
1.这些db信息要如何存储,如何用spring的数据源进行管理。
2.如何做到并发,不同人登录处理不同的db数据,也就是数据源的动态切换。
3.当添加新的数据源时,如何动态的托管给spring。
方案:
1.首先这些db的数据,一定是要存储到db中的,当有新的db信息时,可以让运维的同学添加。如果是在xml中,容易出错不说,动态load那些db信息的xml生效也是可以的应该做的。
2.如何做的动态切换方案大概是这样,首先用ThreadLocal记录下不同人选择的服务器,然后实现spring的AbstractDataSource类,在这个类中加入根据前台选择服务器来动态返回数据源的逻辑。
3.动态更改数据源的方法是:首先实现AbstractDataSource实现类的已注册数据源,将最新的数据源信息从db中查出,set到AbstractDataSource中注册的数据源列表中。
践行:
起初考虑用 Spring 的 AbstractRoutingDataSource 实现数据源切换,能切换但不能动态改变数据库,初始化完成后就不能动了。
设想,如果Spring的事务不关心AbstractRoutingDataSource里数据源的变动,那可以直接实现一个和AbstractRoutingDataSource功能相仿,并且能动态改动数据源的 AbstractRoutingDataSource。
自己构建一个 AbstractRoutingDataSource (名字和Spring提供的一样)
Spring 继承了 AbstractDataSource并实现接口InitializingBean.
实现接口InitializingBean其实是为了把用户设置进去的 datasource 的Map转换成自己的Map。所以才导致 后面再设置 DataSource Map 不能生效.
那我们现在就写一个 AbstractRoutingDataSource ,也继承 AbstractDataSource,但是不用实现InitializingBean了,而后再自己实现的AbstractRoutingDataSource的子类里去实现InitializingBean.
MyAbstractRoutingDataSource.java
public abstract class MyAbstractRoutingDataSource extends AbstractDataSource{
private boolean lenientFallback = true;
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
private org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(MyAbstractRoutingDataSource.class);
public Connection getConnection() throws SQLException {
// TODO Auto-generated method stub
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password)
throws SQLException {
// TODO Auto-generated method stub
return determineTargetDataSource().getConnection(username, password);
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
public void putNewDataSource(Object key, DataSource dataSource){
if(this.resolvedDataSources == null){
this.resolvedDataSources = new HashMap<Object, DataSource>();
}
if(this.resolvedDataSources.containsKey(key)){
this.resolvedDataSources.remove(key);
logger.info("remove old key:" + key);
}
logger.info("add key:" + key + ", value=" + dataSource);
this.resolvedDataSources.put(key, dataSource);
}
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
int index = 0;
for (Entry<Object, DataSource> element : resolvedDataSources.entrySet()) {
logger.debug("myAbstractDS, index:" + index + ", key:" + element.getKey() + ", value:" + element.getValue().toString());
index++;
}
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 + "]");
}
logger.info("myAbstractDS, hit DS is " + dataSource.toString());
return dataSource;
}
protected abstract Object determineCurrentLookupKey();
public boolean isLenientFallback() {
return lenientFallback;
}
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}
public Map<Object, DataSource> getResolvedDataSources() {
return resolvedDataSources;
}
public void setResolvedDataSources(Map<Object, DataSource> resolvedDataSources) {
this.resolvedDataSources = resolvedDataSources;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)){
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
}
public DataSource getResolvedDefaultDataSource() {
return resolvedDefaultDataSource;
}
public void setResolvedDefaultDataSource(DataSource resolvedDefaultDataSource) {
this.resolvedDefaultDataSource = resolvedDefaultDataSource;
}
}
MyRoutingDataSource.java
public class MyRoutingDataSource extends MyAbstractRoutingDataSource implements InitializingBean, ApplicationContextAware{
private static final Logger logger = Logger.getLogger(MyRoutingDataSource.class);
@Resource(name = "playerServerInfoConstant")
private PlayerServerInfoConstant playerServerInfoConstant;
private ApplicationContext applicationContext = null;
@Override
protected Object determineCurrentLookupKey() {
String dataSourceName = SpObserver.getSp();
logger.info("dynamic data source name:" + dataSourceName);
if(dataSourceName == null){
dataSourceName = "dataSource2";
}
return dataSourceName;
}
public DataSource getDataSource() throws PropertyVetoException{
String sp = SpObserver.getSp();
return getDataSource(sp);
}
private DataSource getDataSource(String datasourceName) throws PropertyVetoException {
Map<Object, DataSource> resolvedDataSources = this.getResolvedDataSources();
if(resolvedDataSources == null){
resolvedDataSources = playerServerInfoConstant.getServerMap(false);
}
if (datasourceName == null || "".equals(datasourceName)) {
return getResolvedDefaultDataSource();
}
DataSource source = resolvedDataSources.get(datasourceName);
if (source == null) {
return getResolvedDefaultDataSource();
}
return source;
}
public void afterPropertiesSet() throws Exception {
Map<Object, DataSource> resolvedDataSources = playerServerInfoConstant.getServerMap(false);;
int i =0;
for (Entry<Object, DataSource> element : resolvedDataSources.entrySet()) {
logger.debug("myAbstractDS, index:" + i + ", key:" + element.getKey() + ", value:" + element.getValue().toString());
i++;
}
this.setResolvedDataSources(resolvedDataSources);
this.setResolvedDefaultDataSource((DataSource)this.applicationContext.getBean("dataSource"));
}
public void setApplicationContext(ApplicationContext arg0)
throws BeansException {
this.applicationContext = arg0;
}
}
上面代码中:
1、做一个filter,每次客户发出请求的时候就调用SpObserver.petSp(dataSourceName),将request中的dataSourceName传递给SpObserver对象。最后修改MultiDataSource的方法getDataSource()。
MyFilter.java
public class MyFilter implements Filter {
public MyFilter() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
Integer dataSourceIdx = (Integer)httpRequest.getSession().getAttribute(AppConstant.SESSION_DEAULT_SERVER_IDX_KEY);
if(dataSourceIdx == null || dataSourceIdx == -1){
//the test server's idx is 2
dataSourceIdx = 2;
}
SpObserver.putSp("dataSource" + dataSourceIdx);
chain.doFilter(request, response);
}
public void destroy() {
}
}
SpOvserver.java
public class SpObserver {
private static ThreadLocal local = new ThreadLocal();
public static void putSp(String sp) {
local.set(sp);
}
public static String getSp() {
return (String)local.get();
}
}
2、构造一个playerServerConstant从库中取db的信息。
PlayerServerConstant.java
public class PlayerServerInfoConstant implements ApplicationListener{
@Autowired
private GameServerInfoService gameServerService;
private ConcurrentHashMap<Object, DataSource> allDataSources = null;
private Logger logger = Logger.getLogger(PlayerServerInfoConstant.class);
private PlayerServerInfoConstant(){}
public Map<Object, DataSource> getServerMap(boolean refreshflag) throws PropertyVetoException{
if(allDataSources == null || refreshflag == true){
//synchronized(serverMap){
//if(serverMap == null){
allDataSources = new ConcurrentHashMap<Object, DataSource>();
for (GameServerInfo info : gameServerService.getGameServers()) {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://" + info.getIp() + ":" + info.getPort() + "/" + info.getDb() + "?useUnicode=true&characterEncoding=UTF-8");
cpds.setUser(info.getUsername());
cpds.setPassword(info.getPassword());
cpds.setTestConnectionOnCheckout(true);
logger.info("init dataSouce:" + info.getIp() + " " + info.getPort());
allDataSources.put("dataSource" + info.getIdx(), cpds);
}
// }
//}
}
return allDataSources;
}
public void empytDataSources(){
}
public void onApplicationEvent(ApplicationEvent arg0) {
try {
getServerMap(false);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
}
在dao中SeesionFactory的dataSource如何构造呢?
<bean id="gameServerDataSource" class="com.youai.gamemis.model.dao.MyRoutingDataSource">
<property name="resolvedDataSources">
<map key-type="java.lang.String">
<entry key="dataSource2" value-ref="dataSource"/>
</map>
</property>
<property name="resolvedDefaultDataSource" ref="dataSource"/>
</bean>
动态更新数据源的方式:
ConcurrentHashMap<Object, DataSource> newDataSource = new ConcurrentHashMap<Object, DataSource>(playerServerInfoConstant.getServerMap(true));
//modify dataSource at dataSourceConstant
BeanDefinitionBuilder userBeanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(PlayerServerInfoConstant.class);
userBeanDefinitionBuilder.addPropertyValue("gameServerService", gameServerService);
userBeanDefinitionBuilder.addPropertyValue("allDataSources", newDataSource);
BeanDefinition bd = userBeanDefinitionBuilder.getRawBeanDefinition();
bd.setScope("singleton");
beanFactory.registerBeanDefinition("playerServerInfoConstant", bd);
//refresh data at router
for (Entry<Object, DataSource> element : newDataSource.entrySet()) {
gameServerDataSource.putNewDataSource(element.getKey(), element.getValue());
}
好像很乱的样子,有问题,欢迎讨论。
ref:http://www.wzero.net/?p=62
http://www.iteye.com/topic/72486
http://westerly.blog.51cto.com/1077430/638818
http://blog.csdn.net/buyaore_wo/article/details/8119577