Spring配置mysql读写分离动态加载

1 篇文章 0 订阅
1 篇文章 0 订阅

       我写的和网上的略有不同,主要是通过动态注入数据源的方式来动态确定数据源。首先介绍网上主流的一主多从: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);  
 }

}

大体就是以上的完成了,就可以实现数据库的负载均衡,以及动态加载数据源



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值