Spring配置多个数据源

Spring 配置多数据源实现数据库读写分离

博客分类:

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

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

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

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

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

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

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

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

Java代码  

  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}

Java代码  

  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 的数据源接口,定义如下:

Java代码  

  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接口:

Java代码  

  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方法定义如下:

Java代码  

  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的定义:

Java代码  

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

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

Java代码  

  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定义如下:

Java代码  

  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的表结构一致,但数据不同,数据库配置如下:

Xml代码  

  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配置

Xml代码  

  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库:

Java代码  

  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

 二、配置动态数据源

Xml代码  

  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>

Java代码  

  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. }

Java代码  

  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. }

查询前设置值:

Java代码  

  1. MutiDataSourceUtil.determineTargetDataSourceByInstanceUUID(EsbServiceInstanceV.getInstanceUUID());
  2. Map<String,Object> result = esbServiceMonitorDao.findEsbServiceInstanceVPagedList(pageQueryParameter, EsbServiceInstanceV);
  3. // reset datasource
  4. DataSourceContextHolder.clearDataSourceType();

Java代码  

  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:

Xml代码  

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

实例号如:

N1-AB2DFE3C48BA43D699529868B20152CC

时间: 2024-08-13 08:02:18

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

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

问题:有开源框架mysql的 ,还有旧系统 sqlserver2000的,解决这些问题总有些成长. 解决sqlserver安装环境:http://qdh68.blog.163.com/blog/static/13756126201261742437357/ 别说sqlserver2000不怎么样,最起码那友好的管理叫你明白数据库. 2.  先说配置jdbc:如果sqlserver 2000以上还好 找到jar包 ,按目录加载到maven仓库,安装一下 http://outofmemory.cn/

spring 配置多个数据源的文件

<?xml version="1.0" encoding="UTF-8"?><!-- Repository and Service layers --><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns

Spring配置DataSource数据源

在Spring框架中有如下3种获得DataSource对象的方法: 1.从JNDI获得DataSource. 2.从第三方的连接池获得DataSource. 3.使用DriverManagerDataSource获得DataSource. 一.从JNDI获得DataSource SpringJNDI数据源配置信息: <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean&qu

Spring配置数据源的常用方式

Spring配置数据源的常用方式 在应用程序中配置数据源 (1).在classpath中创建连接参数的配置文件,如db-config.properties,内容如下: driverClass=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/zzp username=root password=admin (2).在Spring的配置文件中引入参数配置文件,代码如下: <!-- 配置spring资源文件 --> <bean id=

spring 配置双数据源并读写分离

摘自 开源项目Ibase4j    关键思想在于AbstractRoutingSource 类 还有方法名称和切入点去控制使用哪个数据源    1.首先在配置文件配置多个数据源 并且交给继承自spring AbstractRoutingSource去管理 datasource.xml配置如下 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springfram

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

今天在使用Spring配置c3p0数据源时,使用的数据库是mysql,服务器是tomcat,运行时报了一个 java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector 网络上找了很久都没有解决,最后发现是因为:         C3P0 少了个 jar 包,mchange-commons-java-0.2.3.1.jar 因为c3p0.jar依赖另一个jar包,就是mchange-commons-java-0.2.3.1.jar.

Spring配置数据源的几种形式

Spring中提供了4种不同形式的数据源配置方式: 1.Spring自带的数据源(DriverMangerDataSource); 2.DBCP数据源; 3.C3P0数据源; 4.JNDI数据源. 以上数据源配置需要用的Jar包在http://www.java2s.com/Code/Jar/c/Catalogc.htm中都可以下载到 下面详细介绍这四种数据源配置方式: DriverMangerDataSource 其对应的配置文件XML代码 <bean id="dataSource&quo

复习Spring第三课--数据源配置的多种方式

spring数据源配置可以说分为:spring容器自带连接池.项目中创建连接池.服务器创建连接池三种 一.spring容器自带连接池   Spring本身也提供了一个简单的数据源实现类DriverManagerDataSource ,它位于org.springframework.jdbc.datasource包中.这个类实现了javax.sql.DataSource接口,但 它并没有提供池化连接的机制,每次调用getConnection()获取新连接时,只是简单地创建一个新的连接.因此,这个数据

Spring配置动态数据源-读写分离和多数据源

在现在互联网系统中,随着用户量的增长,单数据源通常无法满足系统的负载要求.因此为了解决用户量增长带来的压力,在数据库层面会采用读写分离技术和数据库拆分等技术.读写分离就是就是一个Master数据库,多个Slave数据库,Master数据库负责数据的写操作,slave库负责数据读操作,通过slave库来降低Master库的负载.因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验.我们通常的做法