• Spring 数据库读写分离


    读写分离常见有俩种方式

      1 第一种方式比较常用就是定义2个数据库连接,一个是Master,另一个是Slave。更新数据时我们取Master,查询数据时取Slave。太过简单不做介绍。

      2 第二种方数据源式动态切换,将数据源动态织入到程序中,达到动态选择读取主库还是从库的目的。主要使用的技术是:annotation,Spring AOP ,反射。下面介绍这种方式

    首先创建DatabaseConfiguration
    package com.testdatasource.third.configuration.datasource;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.baomidou.mybatisplus.MybatisConfiguration;
    import com.baomidou.mybatisplus.entity.GlobalConfiguration;
    import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
    import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
    import com.testdatasource.common.enums.DatasourceType;
    import com.testdatasource.third.configuration.properties.DruidProperties;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.type.JdbcType;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    
    @Configuration
    @EnableAutoConfiguration
    public class DatabaseConfiguration {
        @Autowired
        private ApplicationContext appContext;
    
        @Autowired
        private DruidProperties druidProperties;
    
    
        @Bean(name = "readDatasource")
        @Primary
        @ConfigurationProperties(prefix = "read.datasource")
        public DataSource readDatasource() {
            return getReadDruidDataSource();
        }
    
        /**
         * 写入数据源连接池配置
         */
        @Bean(name = "writeDatasource")
        @ConfigurationProperties(prefix = "write.datasource")
        public DataSource writeDatasource() {
            return getWriteDruidDataSource();
        }
    
        private DruidDataSource getReadDruidDataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            druidProperties.configR(dataSource);
            return dataSource;
        }
    
        private DruidDataSource getWriteDruidDataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            druidProperties.configW(dataSource);
            return dataSource;
        }
    
        //初始化数据源bean,这个bean很重要,后面事务控制也会用到
        @Bean
        public AbstractRoutingDataSource roundRobinDataSouceProxy(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) {
            DynamicDataSource proxy = new DynamicDataSource();
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            targetDataSources.put( DatasourceType.read.getName(), read);
            targetDataSources.put(DatasourceType.write.getName(), write);
            proxy.setDefaultTargetDataSource(read);
            proxy.setTargetDataSources(targetDataSources);
            return proxy;
        }
        //初始化SqlSessionFactory,将自定义的多数据源ReadWriteSplitRoutingDataSource类实例注入到工厂中
        @Bean
        public SqlSessionFactory sqlSessionFactory(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) throws Exception {
    
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory. setDataSource(this.roundRobinDataSouceProxy(read, write));
            sqlSessionFactory.setMapperLocations(
                    new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*Mapper.xml"));
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setCacheEnabled(false);
            sqlSessionFactory.setConfiguration(configuration);
            sqlSessionFactory.setGlobalConfig(globalConfiguration());
            return sqlSessionFactory.getObject();
        }
    
        @Bean
        public GlobalConfiguration globalConfiguration() {
            GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
            conf.setLogicDeleteValue("-1");
            conf.setLogicNotDeleteValue("1");
            conf.setIdType(0);
            conf.setDbColumnUnderline(true);
            conf.setRefresh(true);
            return conf;
        }
    }
    View Code

    创建 DataSourceContextHolder

    package com.testdatasource.third.configuration.datasource;
    
    
    import com.testdatasource.common.enums.DatasourceType;
    
    /**
     * ClassName:DataSourceContextHolder
     *
     * @Description : 当前线程数据源
     */
    public class DataSourceContextHolder {
        /**
         * 默认数据源
         */
        public static final String DEFAULT_DS = DatasourceType.read.getName();
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
        // 设置数据源名
        public static void setDB(String dbType) {
            contextHolder.set(dbType);
        }
    
        // 获取数据源名
        public static String getDB() {
            return (contextHolder.get());
        }
    
        // 清除数据源名
        public static void clearDB() {
            contextHolder.remove();
        }
    }
    View Code

    创建 DynamicDataSource

    package com.testdatasource.third.configuration.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * ClassName:DynamicDataSource
     * 
     * @Description : 动态数据源
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDB();
        }
    }
    View Code

    创建 MybatisPlusConfig

    package com.testdatasource.third.configuration.datasource;
    
    import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * ClassName:MybatisPlusConfig 
     * @Description : mybatis分页插件
     * @version  
     */
    @Configuration
    public class MybatisPlusConfig {
    
        /**
         * mybatis-plus分页插件<br>
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
            return paginationInterceptor;
        }
    }
    View Code

    创建 MyDataSourceTransactionManagerAutoConfiguration

    package com.testdatasource.third.configuration.datasource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    /**
     *
     * 多数据源事物
     */
    @Configuration
    @EnableTransactionManagement
    public class MyDataSourceTransactionManagerAutoConfiguration extends DataSourceTransactionManagerAutoConfiguration {
        @Autowired
        private ApplicationContext appContext;
        /**
         * 自定义事务
         * MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
         * @return
         */
        @Bean(name = "transactionManager")
        public DataSourceTransactionManager transactionManagers() {
            return new DataSourceTransactionManager((DataSource)appContext.getBean("roundRobinDataSouceProxy"));
        }
    }
    View Code

    数据源枚举类

    package com.testdatasource.common.enums;
    
    /**
     * ClassName:DatasourceType
     * 
     * @Description :数据库读写类型
     * @version
     */
    public enum DatasourceType {
        write("write"), read("read");
    
        private String name;
    
        private DatasourceType(String name) {
            this.name = name();
        }
    
        public String getName() {
            return this.name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    View Code

    数据源配置类

    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.sql.SQLException;
    
    
    /**
     * ClassName:DruidProperties
     * 
     * @Description :
     *              druid连接池配置文件属性,说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可
     *              </p>
     */
    @Component
    @ConfigurationProperties(prefix = "spring.datasource")
    public class DruidProperties {
    
        @Value("${spring.datasource.read_url}")
        private String rurl;
        @Value("${spring.datasource.read_username}")
        private String rusername;
        @Value("${spring.datasource.read_password}")
        private String rpassword;
        @Value("${spring.datasource.write_url}")
        private String wurl;
        @Value("${spring.datasource.write_username}")
        private String wusername;
        @Value("${spring.datasource.write_password}")
        private String wpassword;
    
        private String driverClassName = "com.mysql.jdbc.Driver";
    
        private Integer initialSize = 2;
    
        private Integer minIdle = 1;
    
        private Integer maxActive = 20;
    
        private Integer maxWait = 60000;
    
        private Integer timeBetweenEvictionRunsMillis = 60000;
    
        private Integer minEvictableIdleTimeMillis = 300000;
    
        private String validationQuery = "SELECT 1 FROM DUAL";
    
        private Boolean testWhileIdle = true;
    
        private Boolean testOnBorrow = false;
    
        private Boolean testOnReturn = false;
    
        private Boolean poolPreparedStatements = true;
    
        private Integer maxPoolPreparedStatementPerConnectionSize = 20;
    
        private String filters = "stat";
    
        public void configR(DruidDataSource dataSource) {
    
            dataSource.setUrl(rurl);
            dataSource.setUsername(rusername);
    //        dataSource.setPassword(AESUtil.decrypt(rpassword, null));
            dataSource.setPassword(AESUtils.AESDecode(null,rpassword));
    
            dataSource.setDriverClassName(driverClassName);
            dataSource.setInitialSize(initialSize); // 定义初始连接数
            dataSource.setMinIdle(minIdle); // 最小空闲
            dataSource.setMaxActive(maxActive); // 定义最大连接数
            dataSource.setMaxWait(maxWait); // 最长等待时间
    
            // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    
            // 配置一个连接在池中最小生存的时间,单位是毫秒
            dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            dataSource.setValidationQuery(validationQuery);
            dataSource.setTestWhileIdle(testWhileIdle);
            dataSource.setTestOnBorrow(testOnBorrow);
            dataSource.setTestOnReturn(testOnReturn);
    
            // 打开PSCache,并且指定每个连接上PSCache的大小
            dataSource.setPoolPreparedStatements(poolPreparedStatements);
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
    
            try {
                dataSource.setFilters(filters);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        public void configW(DruidDataSource dataSource) {
    
            dataSource.setUrl(wurl);
            dataSource.setUsername(wusername);
            dataSource.setPassword(AESUtils.AESDecode(null,wpassword));
            
            
            dataSource.setDriverClassName(driverClassName);
            dataSource.setInitialSize(initialSize); // 定义初始连接数
            dataSource.setMinIdle(minIdle); // 最小空闲
            dataSource.setMaxActive(maxActive); // 定义最大连接数
            dataSource.setMaxWait(maxWait); // 最长等待时间
    
            // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    
            // 配置一个连接在池中最小生存的时间,单位是毫秒
            dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            dataSource.setValidationQuery(validationQuery);
            dataSource.setTestWhileIdle(testWhileIdle);
            dataSource.setTestOnBorrow(testOnBorrow);
            dataSource.setTestOnReturn(testOnReturn);
    
            // 打开PSCache,并且指定每个连接上PSCache的大小
            dataSource.setPoolPreparedStatements(poolPreparedStatements);
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
    
            try {
                dataSource.setFilters(filters);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        public String getRurl() {
            return rurl;
        }
    
        public void setRurl(String rurl) {
            this.rurl = rurl;
        }
    
        public String getRusername() {
            return rusername;
        }
    
        public void setRusername(String rusername) {
            this.rusername = rusername;
        }
    
        public String getRpassword() {
            return rpassword;
        }
    
        public void setRpassword(String rpassword) {
            this.rpassword = rpassword;
        }
    
        public String getWurl() {
            return wurl;
        }
    
        public void setWurl(String wurl) {
            this.wurl = wurl;
        }
    
        public String getWusername() {
            return wusername;
        }
    
        public void setWusername(String wusername) {
            this.wusername = wusername;
        }
    
        public String getWpassword() {
            return wpassword;
        }
    
        public void setWpassword(String wpassword) {
            this.wpassword = wpassword;
        }
    
        public String getDriverClassName() {
            return driverClassName;
        }
    
        public void setDriverClassName(String driverClassName) {
            this.driverClassName = driverClassName;
        }
    
        public Integer getInitialSize() {
            return initialSize;
        }
    
        public void setInitialSize(Integer initialSize) {
            this.initialSize = initialSize;
        }
    
        public Integer getMinIdle() {
            return minIdle;
        }
    
        public void setMinIdle(Integer minIdle) {
            this.minIdle = minIdle;
        }
    
        public Integer getMaxActive() {
            return maxActive;
        }
    
        public void setMaxActive(Integer maxActive) {
            this.maxActive = maxActive;
        }
    
        public Integer getMaxWait() {
            return maxWait;
        }
    
        public void setMaxWait(Integer maxWait) {
            this.maxWait = maxWait;
        }
    
        public Integer getTimeBetweenEvictionRunsMillis() {
            return timeBetweenEvictionRunsMillis;
        }
    
        public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
            this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        }
    
        public Integer getMinEvictableIdleTimeMillis() {
            return minEvictableIdleTimeMillis;
        }
    
        public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
            this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        }
    
        public String getValidationQuery() {
            return validationQuery;
        }
    
        public void setValidationQuery(String validationQuery) {
            this.validationQuery = validationQuery;
        }
    
        public Boolean getTestWhileIdle() {
            return testWhileIdle;
        }
    
        public void setTestWhileIdle(Boolean testWhileIdle) {
            this.testWhileIdle = testWhileIdle;
        }
    
        public Boolean getTestOnBorrow() {
            return testOnBorrow;
        }
    
        public void setTestOnBorrow(Boolean testOnBorrow) {
            this.testOnBorrow = testOnBorrow;
        }
    
        public Boolean getTestOnReturn() {
            return testOnReturn;
        }
    
        public void setTestOnReturn(Boolean testOnReturn) {
            this.testOnReturn = testOnReturn;
        }
    
        public Boolean getPoolPreparedStatements() {
            return poolPreparedStatements;
        }
    
        public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
            this.poolPreparedStatements = poolPreparedStatements;
        }
    
        public Integer getMaxPoolPreparedStatementPerConnectionSize() {
            return maxPoolPreparedStatementPerConnectionSize;
        }
    
        public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
            this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
        }
    
        public String getFilters() {
            return filters;
        }
    
        public void setFilters(String filters) {
            this.filters = filters;
        }
    }
    View Code

     annotation类作用于强制写库或者读库

    package com.testdatasource.common.annotation;
    
    
    import com.testdatasource.common.enums.DatasourceType;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * ClassName:DS
     * @Description :自定义切换数据源注解
     * @version
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DS {
        DatasourceType value() default DatasourceType.write;
    }
    View Code

    切面类DynamicDataSourceAspect 用于辅助自定义切面注解切换数据源

    package com.testdatasource.third.aspect;
    
    import com.testdatasource.common.annotation.DS;
    import com.testdatasource.third.configuration.datasource.DataSourceContextHolder;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * ClassName:DynamicDataSourceAspect
     *
     * @Description : 自定义切面注解切换数据源
     * @version
     */
    @Aspect
    @Component
    @Order(value = 2)
    public class DynamicDataSourceAspect {
        
        @Pointcut("@annotation(com.testdatasource.common.annotation.DS)")
        public void ds() {
    
        }
        
        @Before("ds()")
        public void beforeSwitchDS(JoinPoint point) {
    
            // 获得当前访问的class
            Class<?> className = point.getTarget().getClass();
    
            // 获得访问的方法名
            String methodName = point.getSignature().getName();
            // 得到方法的参数的类型
            Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
            String dataSource = DataSourceContextHolder.DEFAULT_DS;
            try {
                // 得到访问的方法对象
                Method method = className.getMethod(methodName, argClass);
    
                // 判断是否存在@DS注解
                if (method.isAnnotationPresent(DS.class)) {
                    DS annotation = method.getAnnotation(DS.class);
                    // 取出注解中的数据源名
                    dataSource = annotation.value().getName();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 切换数据源
            DataSourceContextHolder.setDB(dataSource);
    
        }
    
        @After("ds()")
        public void afterSwitchDS(JoinPoint point) {
            DataSourceContextHolder.clearDB();
        }
    }
    View Code

    yml配置

    spring:
      datasource:
        type: com.alibaba.druid.pool.xa.DruidXADataSource
        read_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
        read_username: root
        read_password: root
        write_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
        write_username: root
        write_password: root

    好了 这样配置就没问题了。

  • 相关阅读:
    Windows下Git多账号配置,同一电脑多个ssh-key的管理
    Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)
    处理Linux下subversion尝试连接自建的VisualSVN server报“Key usage violation in certificate has been detected”错误的问题
    #Linux学习笔记# Linux文件的属性以及权限说明
    #Linux学习笔记# Linux文件的所有者、群组和其他人
    #Linux学习笔记# Linux在线帮助文档man page
    #Linux学习笔记# Linux系统查看文件内容的命令
    #Linux学习笔记# 自定义shell终端提示符
    Docker私有仓库Registry的搭建验证
    LinuxMint下Docker的安装部署和验证
  • 原文地址:https://www.cnblogs.com/java-xz/p/10553040.html
Copyright © 2020-2023  润新知