现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库。Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验。我们通常的做法就是把查询从主库中抽取出来,采用多个从库,使用负载均衡,减轻每个从库的查询压力。

  采用读写分离技术的目标:有效减轻Master库的压力,又可以把用户查询数据的请求分发到不同的Slave库,从而保证系统的健壮性。我们看下采用读写分离的背景。

  随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力也就越来越大,采用传统的方式,比如:数据库或者SQL的优化基本已达不到要求,这个时候可以采用读写分离的策 略来改变现状。

  具体到开发中,如何方便的实现读写分离呢?目前常用的有两种方式:

  1 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。这种方式很简单,我就不赘述了。

  2 第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:annotation,Spring AOP ,反射。下面会详细的介绍实现方式。

   在介绍实现方式之前,我们先准备一些必要的知识,spring 的AbstractRoutingDataSource 类

     AbstractRoutingDataSource这个类 是spring2.0以后增加的,我们先来看下AbstractRoutingDataSource的定义:

  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}
  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
  2. private Map<Object, Object> targetDataSources;
  3. private Object defaultTargetDataSource;
  4. private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
  5. private Map<Object, DataSource> resolvedDataSources;
  6. private DataSource resolvedDefaultDataSource;

    AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。

DataSource   是javax.sql 的数据源接口,定义如下:

  1. public interface DataSource  extends CommonDataSource,Wrapper {
  2. Connection getConnection() throws SQLException;
  3. Connection getConnection(String username, String password)
  4. throws SQLException;
  5. }

DataSource 接口定义了2个方法,都是获取数据库连接。我们在看下AbstractRoutingDataSource 如何实现了DataSource接口:

  1. public Connection getConnection() throws SQLException {
  2. return determineTargetDataSource().getConnection();
  3. }
  4. public Connection getConnection(String username, String password) throws SQLException {
  5. return determineTargetDataSource().getConnection(username, password);
  6. }

很显然就是调用自己的determineTargetDataSource()  方法获取到connection。determineTargetDataSource方法定义如下:

  1. protected DataSource determineTargetDataSource() {
  2. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  3. Object lookupKey = determineCurrentLookupKey();
  4. DataSource dataSource = this.resolvedDataSources.get(lookupKey);
  5. if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
  6. dataSource = this.resolvedDefaultDataSource;
  7. }
  8. if (dataSource == null) {
  9. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  10. }
  11. return dataSource;
  12. }

我们最关心的还是下面2句话:

  Object lookupKey = determineCurrentLookupKey();

DataSource dataSource = this.resolvedDataSources.get(lookupKey);

determineCurrentLookupKey方法返回lookupKey,resolvedDataSources方法就是根据lookupKey从Map中获得数据源。resolvedDataSources 和determineCurrentLookupKey定义如下:

  private Map<Object, DataSource> resolvedDataSources;

  protected abstract Object determineCurrentLookupKey()

  看到以上定义,我们是不是有点思路了,resolvedDataSources是Map类型,我们可以把MasterDataSource和SlaveDataSource存到Map中,如下:

    key        value

    master           MasterDataSource

    slave              SlaveDataSource

  我们在写一个类DynamicDataSource  继承AbstractRoutingDataSource,实现其determineCurrentLookupKey() 方法,该方法返回Map的key,master或slave。

  好了,说了这么多,有点烦了,下面我们看下怎么实现。

 上面已经提到了我们要使用的技术,我们先看下annotation的定义:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface DataSource {
  4. String value();
  5. }

我们还需要实现spring的抽象类AbstractRoutingDataSource,就是实现determineCurrentLookupKey方法:

  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. // TODO Auto-generated method stub
  5. return DynamicDataSourceHolder.getDataSouce();
  6. }
  7. }
  8. public class DynamicDataSourceHolder {
  9. public static final ThreadLocal<String> holder = new ThreadLocal<String>();
  10. public static void putDataSource(String name) {
  11. holder.set(name);
  12. }
  13. public static String getDataSouce() {
  14. return holder.get();
  15. }
  16. }

从DynamicDataSource 的定义看出,他返回的是DynamicDataSourceHolder.getDataSouce()值,我们需要在程序运行时调用DynamicDataSourceHolder.putDataSource()方法,对其赋值。下面是我们实现的核心部分,也就是AOP部分,DataSourceAspect定义如下:

  1. public class DataSourceAspect {
  2. public void before(JoinPoint point)
  3. {
  4. Object target = point.getTarget();
  5. String method = point.getSignature().getName();
  6. Class<?>[] classz = target.getClass().getInterfaces();
  7. Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
  8. .getMethod().getParameterTypes();
  9. try {
  10. Method m = classz[0].getMethod(method, parameterTypes);
  11. if (m != null && m.isAnnotationPresent(DataSource.class)) {
  12. DataSource data = m
  13. .getAnnotation(DataSource.class);
  14. DynamicDataSourceHolder.putDataSource(data.value());
  15. System.out.println(data.value());
  16. }
  17. } catch (Exception e) {
  18. // TODO: handle exception
  19. }
  20. }
  21. }

为了方便测试,我定义了2个数据库,shop模拟Master库,test模拟Slave库,shop和test的表结构一致,但数据不同,数据库配置如下:

  1. <bean id="masterdataSource"
  2. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  3. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  4. <property name="url" value="jdbc:mysql://127.0.0.1:3306/shop" />
  5. <property name="username" value="root" />
  6. <property name="password" value="yangyanping0615" />
  7. </bean>
  8. <bean id="slavedataSource"
  9. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  10. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  11. <property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
  12. <property name="username" value="root" />
  13. <property name="password" value="yangyanping0615" />
  14. </bean>
  15. <beans:bean id="dataSource" class="com.air.shop.common.db.DynamicDataSource">
  16. <property name="targetDataSources">
  17. <map key-type="java.lang.String">
  18. <!-- write -->
  19. <entry key="master" value-ref="masterdataSource"/>
  20. <!-- read -->
  21. <entry key="slave" value-ref="slavedataSource"/>
  22. </map>
  23. </property>
  24. <property name="defaultTargetDataSource" ref="masterdataSource"/>
  25. </beans:bean>
  26. <bean id="transactionManager"
  27. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  28. <property name="dataSource" ref="dataSource" />
  29. </bean>
  30. <!-- 配置SqlSessionFactoryBean -->
  31. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  32. <property name="dataSource" ref="dataSource" />
  33. <property name="configLocation" value="classpath:config/mybatis-config.xml" />
  34. </bean>

  在spring的配置中增加aop配置

  1. <!-- 配置数据库注解aop -->
  2. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  3. <beans:bean id="manyDataSourceAspect" class="com.air.shop.proxy.DataSourceAspect" />
  4. <aop:config>
  5. <aop:aspect id="c" ref="manyDataSourceAspect">
  6. <aop:pointcut id="tx" expression="execution(* com.air.shop.mapper.*.*(..))"/>
  7. <aop:before pointcut-ref="tx" method="before"/>
  8. </aop:aspect>
  9. </aop:config>
  10. <!-- 配置数据库注解aop -->

   下面是MyBatis的UserMapper的定义,为了方便测试,登录读取的是Master库,用户列表读取Slave库:

  1. public interface UserMapper {
  2. @DataSource("master")
  3. public void add(User user);
  4. @DataSource("master")
  5. public void update(User user);
  6. @DataSource("master")
  7. public void delete(int id);
  8. @DataSource("slave")
  9. public User loadbyid(int id);
  10. @DataSource("master")
  11. public User loadbyname(String name);
  12. @DataSource("slave")
  13. public List<User> list();
  14. }

   好了,运行我们的Eclipse看看效果,输入用户名admin 登录看看效果


 

从图中可以看出,登录的用户和用户列表的数据是不同的,也验证了我们的实现,登录读取Master库,用户列表读取Slave库。

例子来源:

http://www.cnblogs.com/surge/p/3582248.html

 二、配置动态数据源

  1. <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  2. <property name="jndiName">
  3. <value>java:/datasources/visesbdb</value>
  4. </property>
  5. </bean>
  6. <!-- config dynamicDataSource -->
  7. <bean id="dynamicDataSource" class="com.vispractice.soa.lightesb.common.datasource.MutiDataSourceBean">
  8. <property name="targetDataSources">
  9. <map key-type="java.lang.String">
  10. <entry value-ref="dataSource" key="dataSource"></entry>
  11. </map>
  12. </property>
  13. <property name="defaultTargetDataSource" ref="dataSource"></property>
  14. </bean>
  15. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
  16. <!-- Hibernate SessionFactory -->
  17. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  18. <property name="dataSource" ref="dynamicDataSource"/>
  19. <property name="packagesToScan">
  20. <list>
  21. <value>com.vispractice.soa.lightesb.bean</value>
  22. </list>
  23. </property>
  24. <property name="hibernateProperties">
  25. <props>
  26. <prop key="connection.useUnicode">true</prop>
  27. <prop key="connection.characterEncoding">UTF-8</prop>
  28. <prop key="hibernate.dialect">${hibernate.dialect}</prop>
  29. <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
  30. <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
  31. <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
  32. <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
  33. <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
  34. </props>
  35. </property>
  36. </bean>
  37. <!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
  38. <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  39. <property name="sessionFactory" ref="sessionFactory"/>
  40. </bean>
  1. /**
  2. * 在applicationContext中配置本地数据源作为默认数据源
  3. * 读取project-datasource-jndi.properties中的jndi名称获取其他节点的数据源
  4. * 该文件放在D:\jboss-5.1.0.GA\server\default\conf\props 目录下
  5. *
  6. */
  7. public class MutiDataSourceBean extends AbstractRoutingDataSource implements ApplicationContextAware {
  8. private static final Logger logger = LoggerFactory.getLogger(MutiDataSourceBean.class);
  9. private static ApplicationContext ctx;
  10. private Map<Object,Object> tds = new HashMap<Object,Object>();
  11. @Override
  12. public void setApplicationContext(ApplicationContext applicationContext)
  13. throws BeansException {
  14. ctx = applicationContext;
  15. }
  16. @Override
  17. protected Object determineCurrentLookupKey() {
  18. return DataSourceContextHolder.getDataSourceType();
  19. }
  20. //重写InitializingBean类中方法
  21. @Override
  22. public void afterPropertiesSet() {
  23. logger.info("Init MutiDataSource start...");
  24. try {
  25. initailizeMutiDataSource();
  26. } catch (Exception e) {
  27. logger.error("Init MutiDataSource error...", e);
  28. }
  29. logger.info("Init MutiDataSource end...");
  30. super.afterPropertiesSet();
  31. }
  32. /**
  33. * 读取配置文件中的jndi名称,获取数据源
  34. * @throws Exception
  35. */
  36. private void initailizeMutiDataSource() throws Exception {
  37. // 读取数据源配置文件
  38. ResourceBundle lw = ResourceBundle.getBundle("props.project-datasource-jndi");
  39. // 初始化jndi context
  40. Context jndiCtx = new InitialContext();
  41. DefaultListableBeanFactory dlbf  = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
  42. // 获取配置的数据源
  43. for(String key : lw.keySet()){
  44. Object ds = jndiCtx.lookup(lw.getString(key));
  45. // 将数据源交给spring管理
  46. dlbf.registerSingleton(key, ds);
  47. tds.put(key, ds);
  48. }
  49. super.setTargetDataSources(tds);
  50. }
  51. @Override
  52. public void setTargetDataSources(Map<Object, Object> targetDataSources) {
  53. tds = targetDataSources;
  54. super.setTargetDataSources(targetDataSources);
  55. }
  56. }
  1. /**
  2. * 通过ThreadLocal来存储当前所使用数据源对应的key
  3. *
  4. */
  5. public class DataSourceContextHolder {
  6. private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
  7. public static void setDataSourceType(String dataSourceType) {
  8. contextHolder.set(dataSourceType);
  9. }
  10. public static String getDataSourceType() {
  11. return contextHolder.get();
  12. }
  13. public static void clearDataSourceType() {
  14. contextHolder.remove();
  15. }
  16. }

查询前设置值:

  1. MutiDataSourceUtil.determineTargetDataSourceByInstanceUUID(EsbServiceInstanceV.getInstanceUUID());
  2. Map<String,Object> result = esbServiceMonitorDao.findEsbServiceInstanceVPagedList(pageQueryParameter, EsbServiceInstanceV);
  3. // reset datasource
  4. DataSourceContextHolder.clearDataSourceType();
  1. public class MutiDataSourceUtil {
  2. /**
  3. * 通过实例UUID切换到对应的数据源
  4. *
  5. * @param instanceUUID
  6. */
  7. public static void determineTargetDataSourceByInstanceUUID(String instanceUUID) {
  8. if(StringUtils.isNotBlank(instanceUUID) && StringUtils.contains(instanceUUID, '-')){
  9. DataSourceContextHolder.setDataSourceType(StringUtils.substringBefore(instanceUUID, "-"));
  10. }
  11. }
  12. }

lightesb-datasource-jndi.properties:

  1. N1=java:/datasources/visesbdb
  2. N2=java:/datasources/n2visesbdb

实例号如:

N1-AB2DFE3C48BA43D699529868B20152CC

Spring配置多个数据源的更多相关文章

  1. Spring 配置多个数据源,并实现动态切换

    1.配置两个不同的数据源,如下 <!-- 数据源配置1 --> <bean id="testDataSource1" class="com.alibab ...

  2. SSH框架系列:Spring配置多个数据源

    分类: [java]2013-12-09 16:59 1247人阅读 评论(0) 收藏 举报 1.问题的引入 对于普通的SSH框架而言,一般配置一个数据源,一个SessionFactory,一个事务管 ...

  3. spring 配置多个数据源的文件

    <?xml version="1.0" encoding="UTF-8"?><!-- Repository and Service layer ...

  4. Spring配置,JDBC数据源及事务

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  5. Spring配置多个数据源,并实现数据源的动态切换转载)

    1.首先在config.properties文件中配置两个数据库连接的基本数据.这个省略了 2.在spring配置文件中配置这两个数据源: 数据源1 <!-- initialSize初始化时建立 ...

  6. Spring配置c3p0数据源时出错报:java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector

    今天在使用Spring配置c3p0数据源时,使用的数据库是mysql,服务器是tomcat,运行时报了一个 java.lang.NoClassDefFoundError: com/mchange/v2 ...

  7. Spring配置数据源

    Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0.可以在Spring配置文件中利用这两者中任何一个配置数据源. DBCP数据源 DBCP类包位于 ...

  8. Spring配置数据源的几种形式

    Spring中提供了4种不同形式的数据源配置方式: 1.Spring自带的数据源(DriverMangerDataSource); 2.DBCP数据源; 3.C3P0数据源; 4.JNDI数据源. 以 ...

  9. Spring配置DataSource数据源

    在Spring框架中有例如以下3种获得DataSource对象的方法: 1.从JNDI获得DataSource. 2.从第三方的连接池获得DataSource. 3.使用DriverManagerDa ...

随机推荐

  1. Spring_DI利用set方法赋值Demo

    Person.java public class Person { private Long pid; private String pname; private Student student; p ...

  2. 第八篇、SVN在Mac上使用

    Mac自带svn软件 1.创建目录 svn-repository/source-code 2.svnadmin create /Users/liaokailin/svn-repository/sour ...

  3. 4 - 执行TestNG

    TestNG以如下几种方式被调用 命令行 ant Eclipse IntelliJ's IDEA 这部分对如何使用命令行方式调用TestNG进行阐述. 假设TestNG已经在你的classpath中, ...

  4. 关于fork( )函数父子进程返回值的问题

    fork()是linux的系统调用函数sys_fork()的提供给用户的接口函数,fork()函数会实现对中断int 0x80的调用过程并把调用结果返回给用户程序. fork()的函数定义是在init ...

  5. 浏览器JS报错Uncaught RangeError: Maximum call stack size exceeded?

    JavaScript错误:Uncaught RangeError: Maximum call stack size exceeded 堆栈溢出 原因:有小类到大类的递归查询导致溢出 解决方法思想: A ...

  6. PHP面向对象(OOP):__set(),__get(),__isset(),__unset()四个方法的应用

    一般来说,总是把类的属性定义为private,这更符合现实的逻辑.但是, 对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数”__get()”和”__set()”来获取和赋值其属性 ...

  7. 隐藏和显示 ng-show ng-hide

    <div ng-controller='DeathraymenueController'>    <button ng-click="toggleMenue()" ...

  8. BAE 环境下配置 struts2 + spring + hibernate(SSH)(三)spring&hibernate

    1.在lib中加入必要的包,导入后结果如下: lib打包下载:SSH-lib.jar  (struts2.3.1.2  spring3.0.5 hibernate3.6.10.Final) 只包含必要 ...

  9. assert sys.modules[modname] is old_mod

    使用了pypiwin32 包中的pythoncom的时候,当跑在apache下,日志报错: [Thu Aug 27 17:06:44 2015] [error] [client 127.0.0.1] ...

  10. Oracle数据库基础知识_字符串操作相关2

    6.LPAD,RPAD 作用:左/右边的字符串填充一些特定的字符语法: LPAD(string , n, [pad_String])          string:可是字符或者参数          ...