zoukankan      html  css  js  c++  java
  • 多数据源实现读写分离

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

    实现对主从机数据库的读写分离,源码地址 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"/>
  • 相关阅读:
    java集合(5)-List集合
    String.matches()的用法
    1.在配置XML文件时出现reference file contains errors (http://www.springframework.org/schema/beans/...解决方案
    SVN教程(包括小乌龟) 全图解
    刚刚进公司不会SVN 菜鸟感觉好蛋疼-----------SVN学习记
    第二部分初始阶段 第六章 用例
    第二部分初始阶段 第五章 进化式需求
    SQL中分页与distinct冲突解决方案
    Java项目打war包的方法
    H5不能少的功能-滑动分页
  • 原文地址:https://www.cnblogs.com/CaesarLinsa/p/8129024.html
Copyright © 2011-2022 走看看