• Springboot 主从库切换配置


    应用场景:

    在主从库读写分离时,让程序自动根据业务来区分对主库还是从库进行读写操作,在所有的写操作时,自动对主库进行操作,所有的读操作时,则访问从库。

    应用前提:

    在两台机器上配置好两个数据库,建立主从关系,接下来在springboot的框架中配置

    首先在.yml或者.porperties文件中配置主从数据库

    #自定义druid主从连接
    druid:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #主节点
    master:
    url: jdbc:mysql://192.168.1.1:3306/datebase1?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=GMT
    username: root
    password: root
    driverClassName: com.mysql.cj.jdbc.Driver
    #下面为连接池的补充设置,应用到上面所有数据源中
    filters: stat,wall,log4j
    initialSize: 5
    maxActive: 20
    maxPoolPreparedStatementPerConnectionSize: 20
    maxWait: 60000
    minEvictableIdleTimeMillis: 300000
    minIdle: 5
    poolPreparedStatements: true
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    useGlobalDataSourceStat: true
    validationQuery: SELECT 1 FROM DUAL
    #从节点
    slave:
    url: jdbc:mysql://192.168.1.2:3306/datebase2?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=GMT
    username: root
    password: root
    driverClassName: com.mysql.cj.jdbc.Driver
    #下面为连接池的补充设置,应用到上面所有数据源中
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    logSlowSql: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties:
    druid:
    stat:
    mergeSql: true
    slowSqlMillis: 5000
    # 合并多个DruidDataSource的监控数据
    useGlobalDataSourceStat: true


    紧接着配置mybatis相关
    #mybatis
    mybatis:
    #扫描xml
    mapper-locations: classpath:com/email/support/mapping/*.xml
    #配置
    # configuration:
    #控制台sql日志输出
    # log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
    #mybatis返回类型省略全路径包名
    type-aliases-package: com.email.support.mapper



    然后添加datesource配置类


    /**
    * 数据库连接池配置
    */
    @Configuration
    @EnableTransactionManagement
    public class DatasourceConfig {
    private static Logger LOG = LoggerFactory.getLogger(DatasourceConfig.class);

    /**
    * 引入druid数据库连接池类型
    */
    @Value("${druid.datasource.type}")
    private Class<? extends DataSource> datasourceType;

    /**
    * 主数据源
    * @return DataSource
    */
    @Primary
    @Bean("masterDatasource")
    @ConfigurationProperties("druid.datasource.master")
    public DataSource masterDatasource() {
    LOG.info("=========== master run =========");
    return DataSourceBuilder.create().type(datasourceType).build();
    }

    /**
    * 从数据源
    * @return DataSource
    */
    @Bean("slaveDatasource")
    @ConfigurationProperties("druid.datasource.slave")
    public DataSource slaveDatasource() {
    LOG.info("============ slave run ==========");
    return DataSourceBuilder.create().type(datasourceType).build();
    }

    /**
    * druid的servlet
    * @return ServletRegistrationBean
    */
    @Bean
    public ServletRegistrationBean druidServlet() {
    ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>();
    servletRegistrationBean.setServlet(new StatViewServlet());
    servletRegistrationBean.addUrlMappings("/druid/*");
    servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
    servletRegistrationBean.addInitParameter("deny", "/deny");
    return servletRegistrationBean;
    }

    /**
    * druid的filter
    * @return FilterRegistrationBean
    */
    @Bean
    public FilterRegistrationBean druidFilter() {
    FilterRegistrationBean<WebStatFilter> webStatFilterRegistrationBean = new FilterRegistrationBean<>();
    webStatFilterRegistrationBean.setFilter(new WebStatFilter());
    webStatFilterRegistrationBean.addUrlPatterns("/*");
    webStatFilterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    return webStatFilterRegistrationBean;
    }
    }
    然后配置数据库的读写操作
    新建contextholder类
    **
    * 数据源本地线程操作
    */
    public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<DatasourseType> CONTEXT_HOLDER = new ThreadLocal<>();

    public enum DatasourseType{
    /**
    * 数据源类型
    */
    MASTER,
    SLAVE;

    }

    /**
    * 设置本地线程变量
    * @param datasourseType 数据类型
    */
    public static void setContextHolder(DatasourseType datasourseType){
    CONTEXT_HOLDER.set(datasourseType);
    }

    /**
    * 获取本地线程变量的值 默认返回主数据源
    * @return DatasourseType
    */
    public static DatasourseType getContextHolder(){
    DatasourseType datasourseType = CONTEXT_HOLDER.get();
    return Objects.isNull(datasourseType) ? DatasourseType.MASTER :datasourseType;
    }

    /**
    * 清空本地线程变量
    */
    public static void removeDataSourceType(){
    CONTEXT_HOLDER.remove();
    }

    }


    添加mybatis配置类继承MybatisAutoConfiguration接口  注意:mybatis和datesource有强依赖关系   所以必须先添加datesource的相关配置再进行mybatis的配置
    然后将datesource通过@Resource注解 进行注入 放入mybatis的sqlsessionfactory进行管理

    /**
    * mybatis配置
    */
    @Configuration
    public class MybatisConfig extends MybatisAutoConfiguration {
    /**
    * 主数据源 根据名称注入
    */
    @Resource(name = "slaveDatasource")
    private DataSource slaveDatasource;

    @Resource(name = "masterDatasource")
    private DataSource masterDatasource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
    return super.sqlSessionFactory(datasourceStrategy());
    }

    /**
    * 数据源策略 ----此方法是在添加DynamicDataSource后 将数据源注入进sqlsessionfactroy进行数据源管理
    * @return 数据源
    */
    @SuppressWarnings("unchecked")
    private AbstractRoutingDataSource datasourceStrategy(){
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    //软引用map
    ClassLoaderRepository.SoftHashMap softHashMap = new ClassLoaderRepository.SoftHashMap();
    //设置主从
    softHashMap.put(DynamicDataSourceContextHolder.DatasourseType.MASTER, masterDatasource);
    softHashMap.put(DynamicDataSourceContextHolder.DatasourseType.SLAVE, slaveDatasource);
    //默认使用主数据源
    dynamicDataSource.setTargetDataSources(softHashMap);
    //将两个数据源放入map
    dynamicDataSource.setDefaultTargetDataSource(masterDatasource);
    return dynamicDataSource;
    }
    }

     
    
    
    接下来就是要把主从库动态切换的配置
    新建RoutingDataSource 继承 AbstractRoutingDataSource
    
    
    
    
    
    
    /**
    * 配置数据源容器对象
    */
    public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getContextHolder();
    }

    }

    接下来使用自定义注解告诉方法访问哪个数据库
    建立SlaveDatasource注解
    /**
    * slave数据源标识
    */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SlaveDatasource {

    }


    新建自定义注解的aop切面
    
    
    /**
    * slave数据源aop
    */
    @Aspect
    @Component
    public class SlaveDatasourceAop implements Ordered {
    private static final Logger LOGGER = LoggerFactory.getLogger(SlaveDatasourceAop.class);

    @Pointcut("@annotation(com.email.support.annotation.SlaveDatasource)")
    public void slaveDatasource(){}

    @Around("slaveDatasource()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
    DynamicDataSourceContextHolder.setContextHolder(DynamicDataSourceContextHolder.DatasourseType.SLAVE);
    LOGGER.info("========== change to slave ==========");
    return joinPoint.proceed();
    }finally {
    DynamicDataSourceContextHolder.removeDataSourceType();
    }
    }

    @Override
    public int getOrder() {
    return 0;
    }
    }
     
    至此配置完成  下面测试查看效果

    添加测试方法


    //测试主从库切换
    @Resource(name = "slaveDatasource")
    private DataSource slaveDatasource;

    @Resource(name = "masterDatasource")
    private DataSource masterDatasource;

    @Test
    public void contextLoads() throws Exception{
    Connection c1=masterDatasource.getConnection("主数据库账号","主数据库密码");
    System.err.println("c1:" + c1.getMetaData().getURL());
    Connection c2=slaveDatasource.getConnection("从数据库账号","从数据库密码");
    System.err.println("c2:" + c2.getMetaData().getURL());
    }



    输出结果

    c1:为我的主库地址

    c2:为我的从库地址

  • 相关阅读:
    移动端支付6位密码框
    移动端canvas刮刮乐
    原生ajax请求json数据
    canvas绘制video
    移动端阻止默认长按选中文本和弹出菜单、点击阴影
    前端移动端相关的代码(pc端与移动端自动切换)
    统一管理网站中的某些需要定期更新的时间届数 倒计时 ( 换届 之类的网站)( 兼容ie )
    Hbuilder 常用快捷键汇总
    文件找不到,路径错误问题
    图片清晰度问题
  • 原文地址:https://www.cnblogs.com/hxd9588/p/13544837.html
Copyright © 2020-2023  润新知