多数据源实现读写分离

客户端对数据库的访问实现读写分离,从主机上写入数据,从机上读取数据,可以减轻数据库的压力。

实现对主从机数据库的读写分离,源码地址 https://github.com/CaesarLinsa/SpringTest/tree/master

1.配置多数据源,并注入到sqlSessionFactoryBean中,配置见下:

 <!--1. 写数据源配置 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <!-- 连接池最大数量 -->
        <property name="maxActive" value="${jdbc.maxActive}"></property>
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="${jdbc.maxIdle}"></property>
    </bean>

    <!--1. 读数据源配置 -->
    <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${readjdbc.driver}" />
        <property name="url" value="${readjdbc.url}" />
        <property name="username" value="${readjdbc.username}" />
        <property name="password" value="${readjdbc.password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="${readjdbc.initialSize}"></property>
        <!-- 连接池最大数量 -->
        <property name="maxActive" value="${readjdbc.maxActive}"></property>
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="${readjdbc.maxIdle}"></property>
    </bean>
   <!-- 动态配置数据源 -->
    <bean id ="dataSources" class= "com.soft.datasource.DynamicDataSource" >
        <property name ="targetDataSources">
            <map key-type ="java.lang.String">
                <entry value-ref ="dataSource" key= "dataSource"></entry >
                <entry value-ref ="readDataSource" key= "readDataSource"></entry >
            </map >
        </property >
    </bean >

两数据源的配置文件jdbc.propertise见下:

jdbc.username=root
jdbc.password=
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/SpringTest?useUnicode=true&characterEncoding=UTF-8
jdbc.initialSize=0
jdbc.maxActive=20
jdbc.maxIdle=20
jdbc.minIdle=1
jdbc.maxWait=60000
readjdbc.username=root
readjdbc.password=123
readjdbc.driver=com.mysql.jdbc.Driver
readjdbc.url=jdbc:mysql://192.168.1.102:3306/springMVC_test?useUnicode=true&characterEncoding=UTF-8
readjdbc.initialSize=0
readjdbc.maxActive=20
readjdbc.maxIdle=20
readjdbc.minIdle=1
readjdbc.maxWait=60000

2.重写AbstractRoutingDataSource,定义获取数据源是readDataSource或者是DataSource,只需要在Set方法中注入此字符串即可。但考虑到并发访问数据源的切换,使用ThreadLocal本地存储方式。定义ThreadLocal,DataSourceContextHolder类。当ThreadLocal中set 数据源,并从重写的DynamicDataSource类中获取。若访问完成则释放ThreadLocal。

DataSourceContextHolder类,只是对字符串的并发持有:

public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static void setDbType(String dbType) {

        contextHolder.set(dbType);
    }

    public static String getDbType() {

        return ((String) contextHolder.get());
    }

    public static void clearDbType() {

        contextHolder.remove();
    }
}

DynamicDataSource类,根据不同的数据源字段,进行不同的jdbc访问

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    public Logger getParentLogger() {
        return null;
    }
    @Override
    protected Object determineCurrentLookupKey() {

        return DataSourceContextHolder. getDbType();

    }
}

 可以在Service中使用DataSourceContextHolder.setDbType('数据源字段'),实现对不同主机访问。数据源字段定义为一个DataSourceType类,见下:

public class DataSourceType {
    public static final String DateBase_Slave="readDataSource";
    public static final String DateBase_master="dataSource";
}

   但考虑到每个方法都要写相同的东西,此处又对service进行AOP注入加注解,当方法上面有@DataBase(value = DataSourceType.DateBase_master),则是主机操作,@DataBase(value = DataSourceType.DateBase_Slave),则是对从机的操作。DataBase注解类见下:

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DataBase {
    String value() default "";
}

  在AOP中需要考虑,方法上方是否存在DataSource注解,若存在则获取其Value值,并Set到DataSourceContextHolder中,若无,则方法前后不进行任何操作,具体代码见下:

package com.soft.AOP;

import com.soft.datasource.DataSourceContextHolder;
import com.soft.util.DataBase;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;

@Aspect
@Component
public class DateBaseAOP {


    /**
     * @param joinPoint
     * 方法上有DataBase注解则获取DataBse中value值,动态注入数据源
     *
     */
    @Around(value="execution(* com.soft.service.*.*(..))")
    public Object aroundService(ProceedingJoinPoint joinPoint){

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Annotation[]  Annotations = methodSignature.getMethod().getDeclaredAnnotations();
        DataBase dataBase = findDataBase(Annotations);

        Object value=null;

        if(dataBase!=null){
            DataSourceContextHolder. setDbType(dataBase.value());
        }
        try {
          value=joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        if(dataBase!=null){
            DataSourceContextHolder.clearDbType();
        }
        return value;
    }

    /**
     *
     * @param  annotations 查询方法上@DataBase注解是否存在
     * @return  存在返回@DataBase注解对象,不存在则返回null
     */
    DataBase findDataBase( Annotation[] annotations ) {
        for ( Annotation annotation : annotations ) {
            if (annotation.annotationType() == DataBase.class) {
                return DataBase.class.cast(annotation);
            }
        }
        return null;
    }

}

AOP一定要注意,在bean.xml,扫描注解下面加上支持AOP,否则AOP无法生效。另外切面方法一定要有返回值,否在在Service方法中有返回值而切入方法没有,无法获取查询数据库的数据。

  <context:component-scan base-package="com.soft" />
   <aop:aspectj-autoproxy proxy-target-class="true"/>
原文地址:https://www.cnblogs.com/CaesarLinsa/p/8129024.html