前几天完成了mysql搭建一个主从复制,现在用搭建好的环境来springk来实现读写分离

参考

  • 1.首先看spring是如何得到连接的
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");
        //可看出,实现其determineCurrentLookupKey() 方法,该方法返回Map的key,从而来调用不同的数据源
        Object lookupKey = determineCurrentLookupKey();
        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 + "]");
        }
        return dataSource;
    }
  • 2.自定义一个数据源类DynamicDataSource用他来继承spring中提供的
    AbstractRoutingDataSource,中实现determineCurrentLookupKey
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * spring提供的AbstractRoutingDataSource类中每次与数据库连接时都会调用determineCurrentLookupKey()
 * 来得到对应数据源的key值
 *由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    @Override
    protected Object determineCurrentLookupKey() {
        // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

/**
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 */
public class DynamicDataSourceHolder {

    //使用ThreadLocal保证线程安全
    private static final ThreadLocal<String> holder=new ThreadLocal<String>();

    public static void setDataSourceKkey(String key){
        holder.set(key);
    }
    public static String getDataSourceKey(){
        return holder.get();
    }
}
  • 3.通过自定义注解来标识方法,看改调用那个数据源
/**
 * 对每个mapper接口中的方法进行标记;例:@DataSource("master"或是"slave")
 * 在调用该方法是通过aop切入选择数据源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}
  • 4.配置一个切面来在对要切入的方法进行处理
@Aspect
@Service
public class DataSourceAspect {
    /**
     * 通过在调用对应的接口时,通过aop切入来选择对应的数据源
     * @param point
     */

    //@Before("execution(* com.itarget.dao.mapper.*.*(..)))")
    public void before(JoinPoint point){
        //得到代理对象
        Object target=point.getTarget();
        //等到切入时的方面名
        String method=point.getSignature().getName();
        //等到代理对象实现的接口类
        Class<?>[] classes=target.getClass().getInterfaces();
        Class<?>[] parameterTypes=((MethodSignature)point.getSignature()).getMethod().getParameterTypes();

        try {
            //通过通过方法名,和参数类型来得到方法
            Method m=classes[0].getMethod(method, parameterTypes);
            if(m!=null&&m.isAnnotationPresent(DataSource.class)){
                DataSource dataSource=m.getAnnotation(DataSource.class);
                //通过得到注解来的等到传入的value值是(master或slave)
                DynamicDataSourceHolder.setDataSourceKkey(dataSource.value());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
  • 5.在配置文件中配置
<!-- 配置master数据源 -->
 <bean name="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${master.url}" />
    <property name="username" value="${master.userName}" />
    <property name="password" value="${master.password}" />

    <!-- 初始化连接大小 -->
     <property name="initialSize" value="2" />
    <!-- 连接池最大使用连接数量 -->
     <property name="maxActive" value="500" />
      <!-- 连接池最小空闲 -->
     <property name="minIdle" value="2" />
     <!-- 获取连接最大等待时间 -->
     <property name="maxWait" value="60000" />
     <!-- 缓存PreparedStatements,也就是PSCache,支持游标的数据库才有用 如oracle mysql5.5以上  -->
      <property name="poolPreparedStatements" value="true"/>
      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
      <property name="timeBetweenEvictionRunsMillis" value="600000" />
      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
       <property name="minEvictableIdleTimeMillis" value="25200000" />

       <!-- 对长时间不使用的连接进行关闭 -->
      <property name="removeAbandoned" value="true" />
        <!-- 1800秒,也就是30分钟 -->
       <property name="removeAbandonedTimeout" value="1800" />
              <!-- 关闭abanded连接时输出错误日志 -->
       <property name="logAbandoned" value="true" />

       <!-- 监控数据库 -->
        <!-- <property name="filters" value="stat" /> -->
       <property name="filters" value="mergeStat" />
       </bean>

       <!-- 配置slave数据源 -->
 <bean name="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
   <property name="url" value="${slave.url}" />
   <property name="username" value="${slave.userName}" />
   <property name="password" value="${slave.password}" />

   <!-- 初始化连接大小 -->
   <property name="initialSize" value="2" />
    <!-- 连接池最大使用连接数量 -->
   <property name="maxActive" value="500" />
   <!-- 连接池最小空闲 -->
   <property name="minIdle" value="2" />
   <!-- 获取连接最大等待时间 -->
   <property name="maxWait" value="60000" /> 
   <!-- 缓存PreparedStatements,也就是PSCache,支持游标的数据库才有用 如oracle mysql5.5以上  -->
   <property name="poolPreparedStatements" value="true"/>


   <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
  <property name="timeBetweenEvictionRunsMillis" value="600000" />
 <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
  <property name="minEvictableIdleTimeMillis" value="25200000" />

  <!-- 对长时间不使用的连接进行关闭 -->
 <property name="removeAbandoned" value="true" />
   <!-- 1800秒,也就是30分钟 -->
  <property name="removeAbandonedTimeout" value="1800" />
 <!-- 关闭abanded连接时输出错误日志 -->
  <property name="logAbandoned" value="true" />

   <!-- 监控数据库 -->
     <!-- <property name="filters" value="stat" /> -->
  <property name="filters" value="mergeStat" />
   </bean>
       <!--根据自己的设定的数据源类来配置数据源-->
 <bean id="dataSource" class="com.system.dateSource.DynamicDataSource">
   <property name="targetDataSources">
         <map key-type="java.lang.String">
      <!--这里设置的key应该与你用@DataSource注解标记时传入的value值保持一致-->
         <entry key="master" value-ref="masterDataSource"></entry>
         <entry key="slave" value-ref="slaveDataSource"></entry>
         </map>
   </property>
   <property name="defaultTargetDataSource" ref="masterDataSource"/>

 </bean>



<!--配置切面-->
 <!--自动为spring容器中那些配置@aspectJ切面的bean创建代理-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="manyDataSourceAspect"class="com.system.dateSource.aspect.DataSourceAspect"/>
       <aop:config>
       <aop:aspect id="b" ref="manyDataSourceAspect">
         <!--配置切点配置exection中第一*代表返回值,第二个*代表所有子包*所有方法-->
        <aop:pointcut id="pc" expression="execution(* com.itarget.dao.mapper.*.*(..))"/>
        <aop:before method="before" pointcut-ref="pc"/>
      </aop:aspect>
       </aop:config>