你的位置:首页 > ASP.net教程

[ASP.net教程]Spring 数据源配置三:多数据源


在上一节中,我们讲述了多数据的情况:

1. 数据源不同(数据库厂商不同, 业务范围不同, 业务数据不同)

2. SQL mapper 文件不同,

3. mybatis + 数据方言不同

即最为简单的多数据, 将多个数据源叠加在一起,不同service---》dao--->sessionFactory;

 

如果上述的所有条件都相同,仅仅是数据的的多个拷贝情况,想做主备(读写分离),那我们当然可以用2套复制的代码和配置,但是显然不是最佳的实现方式。

这里,我们就讲解一下多数据源的读写分离,怎么实现。

 

首先,我们从读写分离的入口来看,  读read (对应JAVA/ getXXX,  queryXXX),   写write(对应updateXXX,  insertXXX),  如果能在这些方法执行时,自动切换到只读数据库/只写数据,那么就可以实现读写分离, 那我们自然可以想到spring 的AOP, 并以annotation的方式实现。

 

具体实现步骤如下:

1.   实现自定义的数据源注解: @DataSource("read")  / @DataSource("write")

 1 package com.robin.it.ds; 2  3 import java.lang.annotation.Retention; 4 import java.lang.annotation.Target; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.ElementType; 7 /**  8  * RUNTIME  9  * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 10  * @author Robin.yi 11  * 12 */ 13 14 @Retention(RetentionPolicy.RUNTIME) 15 @Target(ElementType.METHOD) 16 public @interface DataSource {17     String value(); 18 }

2.  实现一个数据库选择器ChooseDataSource

package com.robin.it.ds;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class ChooseDataSource extends AbstractRoutingDataSource {  @Override  protected Object determineCurrentLookupKey() {    return HandleDataSource.getDataSource();  }}============package com.robin.it.ds;public class HandleDataSource {     public static final ThreadLocal<String> holder = new ThreadLocal<String>();        public static void putDataSource(String datasource) {       holder.set(datasource);     }          public static String getDataSource() {       return holder.get();     }   }

 

3.   实现AOP 的的数据拦截,切换功能

package com.robin.it.ds;import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.reflect.MethodSignature;//@Aspect //@Component public class DataSourceAspect {   //@Pointcut("execution(* com.apc.cms.service.*.*(..))")    public void pointCut(){};      // @Before(value = "pointCut()")    public void before(JoinPoint point)     {       Object target = point.getTarget();       System.out.println(target.toString());       String method = point.getSignature().getName();       System.out.println(method);       Class<?>[] classz = target.getClass().getInterfaces();        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())           .getMethod().getParameterTypes();       try {         Method m = classz[0].getMethod(method, parameterTypes);         System.out.println(m.getName());         if (m != null && m.isAnnotationPresent(DataSource.class)) {           DataSource data = m.getAnnotation(DataSource.class);           System.out.println("==============================={}"+data.value());           HandleDataSource.putDataSource(data.value());         }else{          System.out.println("000000000000000000000");        }                } catch (Exception e) {         e.printStackTrace();       }     } } 

 注意:  黄色高亮部分,利用反射读取 Interface的 的定义(包括annotation 的申明);动态读取参数,进行数据源切换;

 

4.  定义接口服务

package com.robin.it.permission.service;import com.robin.it.ds.DataSource;import com.robin.it.permission.module.User;/** * Created by Robin.yi on 2015-4-11. */public interface IUserService {     @DataSource("read")  public User getUser(Integer userId);   @DataSource("read")   public User getUser(String account, String password);        @DataSource("write")  public void insertUser(User user);}

 主意:上面是决定走读库/写库的语句:   @DataSource("write")   /  @DataSource("read")

  接口的实现无特别说明,此次省略。

 

5.  整个Spring 的applicationContent.

<?     ="http://www.w3.org/2001/     ="http://www.springframework.org/schema/context"     ="http://www.springframework.org/schema/tx"     ="http://www.springframework.org/schema/aop"     ="http://www.springframework.org/schema/util"     xsi:schemaLocation="http://www.springframework.org/schema/beans                http://www.springframework.org/schema/beans/spring-beans-4.1.xsd                http://www.springframework.org/schema/context                http://www.springframework.org/schema/context/spring-context-4.1.xsd                http://www.springframework.org/schema/tx                http://www.springframework.org/schema/tx/spring-tx-4.1.xsd                http://www.springframework.org/schema/aop                http://www.springframework.org/schema/aop/spring-aop-4.1.xsd                 http://www.springframework.org/schema/util                http://www.springframework.org/schema/util/spring-util-4.1.xsd ">  <description>    This file is used to define the authority info.  </description>   <!-- 用于扫描 Spring 的各类annotation bean 变量:    @autowired ; @Resource   -->  <context:annotation-config/>   <!-- 用于扫描类中组建:     如: @Controller, @Service, @Component   -->   <context:component-scan base-package="com.robin.it.permission.*">     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />   </context:component-scan>        <tx:annotation-driven />                       <bean id="configProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">    <property name="ignoreResourceNotFound" value="false" />    <property name="locations">      <list>        <!-- 这里支持多种寻址方式:classpath和file -->         <value>classpath:/database.properties</value>         <value>classpath:/config.properties</value>         <!-- 推荐使用file的方式引入,这样可以将配置和代码分离 -->        <!-- <value>file:/opt/demo/config/demo-message.properties</value>-->      </list>    </property>  </bean>    <util:properties id="props" location="classpath:/config.properties"/>    <!-- MYSQL 配置  -->    <bean id="readDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">    <property name="driverClassName"><value>${mysql.jdbc.driverClassName}</value></property>    <property name="url"><value>${mysql.jdbc.url}</value></property>    <property name="username"><value>${mysql.jdbc.username}</value></property>    <property name="password"><value>${mysql.jdbc.password}</value></property>  </bean>    <bean id="writeDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">    <property name="driverClassName"><value>${mysql2.jdbc.driverClassName}</value></property>    <property name="url"><value>${mysql2.jdbc.url}</value></property>    <property name="username"><value>${mysql2.jdbc.username}</value></property>    <property name="password"><value>${mysql2.jdbc.password}</value></property>  </bean>   <bean id="dataSource" class="com.robin.it.ds.ChooseDataSource">     <property name="targetDataSources">         <map key-type="java.lang.String">           <!-- write -->          <entry key="write" value-ref="writeDataSource"/>           <!-- read -->          <entry key="read" value-ref="readDataSource"/>         </map>              </property>      <property name="defaultTargetDataSource" ref="writeDataSource"/>    </bean>      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     <property name="dataSource" ref="dataSource" />    </bean>      <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">    <property name="dataSource" ref="dataSource" />    <!-- 指定sqlMapConfig总配置文件,订制的environment在spring容器中不在生效-->     <property name="configLocation" value="classpath:mybatis-config-mysql.指定实体类映射文件,可以指定同时指定某一包以及子包下面的所有配置文件,mapperLocations和configLocation有一个即可,     当需要为实体类指定别名时,可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 -->     <property name="mapperLocations">        <list>        <value>classpath*:/mysqlmapper/*Mapper. <aop:aspectj-autoproxy proxy-target-/>       <bean id="dataSourceAspect" />          <aop:config>       <aop:aspect id="c" ref="dataSourceAspect">         <aop:pointcut id="tx" expression="execution(* com.robin.it.permission.service.*.*(..))"/>         <aop:before pointcut-ref="tx" method="before"/>       </aop:aspect>     </aop:config> </beans>

重点关注标为黄色的部分: 如何配置多数据源;  当知悉对应service  [com.robin.it.permission.service.*.*(..)] , 的相关方法之前(aop:before),  执行DataSourceAspect切面方法;

数据库配置文件database.properties 参考,上一章节。

到此,已完成多数据源的,读写分离配置。