zoukankan      html  css  js  c++  java
  • Spring boot实现数据库读写分离

    背景

    数据库配置主从之后,如何在代码层面实现读写分离?

    用户自定义设置数据库路由

    Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据库,这样我们可以在执行查询之前,设置读取从库,在执行完成后,恢复到主库。

    实现可动态路由的数据源,在每次数据库查询操作前执行
    ReadWriteSplitRoutingDataSource.java

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * @author songrgg
     * @since 1.0
     */
    public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DbContextHolder.getDbType();
        }
    }
    

    线程私有路由配置,用于ReadWriteSplitRoutingDataSource动态读取配置
    DbContextHolder.java

    /**
     * @author songrgg
     * @since 1.0
     */
    public class DbContextHolder {
        public enum DbType {
            MASTER,
            SLAVE
        }
    
        private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();
    
        public static void setDbType(DbType dbType) {
            if(dbType == null){
                throw new NullPointerException();
            }
            contextHolder.set(dbType);
        }
    
        public static DbType getDbType() {
            return contextHolder.get() == null ? DbType.MASTER : contextHolder.get();
        }
    
        public static void clearDbType() {
            contextHolder.remove();
        }
    }
    

    AOP优化代码

    利用AOP将设置数据库的操作从代码中抽离,这里的粒度控制在方法级别,所以利用注解的形式标注这个方法涉及的数据库事务只读,走从库。

    只读注解,用于标注方法的数据库操作只走从库。
    ReadOnlyConnection.java

    package com.wallstreetcn.hatano.config;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Indicates the database operations is bound to the slave database.
     * AOP interceptor will set the database to the slave with this interface.
     * @author songrgg
     * @since 1.0
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ReadOnlyConnection {
    }
    

    ReadOnlyConnectionInterceptor.java

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    
    /**
     * Intercept the database operations, bind database to read-only database as this annotation
     * is applied.
     * @author songrgg
     * @since 1.0
     */
    @Aspect
    @Component
    public class ReadOnlyConnectionInterceptor implements Ordered {
    
        private static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
    
        @Around("@annotation(readOnlyConnection)")
        public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
            try {
                logger.info("set database connection to read only");
                DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
                Object result = proceedingJoinPoint.proceed();
                return result;
            } finally {
                DbContextHolder.clearDbType();
                logger.info("restore database connection");
            }
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    

    UserService.java

    @ReadOnlyConnection
    public List<User> getUsers(Integer page, Integer limit) {
        return repository.findAll(new PageRequest(page, limit));
    }
    

    配置Druid数据库连接池

    build.gradle

    compile("com.alibaba:druid:1.0.18")
    

    groovy依赖注入

    配置dataSource为可路由数据源
    context.groovy

    import com.alibaba.druid.pool.DruidDataSource
    import DbContextHolder
    import ReadWriteSplitRoutingDataSource
    
    ** SOME INITIALIZED CODE LOAD PROPERTIES **
    def dataSourceMaster = new DruidDataSource()
    dataSourceMaster.url = properties.get('datasource.master.url')
    println("master set to " + dataSourceMaster.url)
    dataSourceMaster.username = properties.get('datasource.master.username')
    dataSourceMaster.password = properties.get('datasource.master.password')
    
    def dataSourceSlave = new DruidDataSource()
    dataSourceSlave.url = properties.get('datasource.slave.url')
    println("slave set to " + dataSourceSlave.url)
    dataSourceSlave.username = properties.get('datasource.slave.username')
    dataSourceSlave.password = properties.get('datasource.slave.password')	
    beans {
        dataSource(ReadWriteSplitRoutingDataSource) { bean ->
            targetDataSources = [
                    (DbContextHolder.DbType.MASTER): dataSourceMaster,
                    (DbContextHolder.DbType.SLAVE): dataSourceSlave
            ]
        }
    }
    

    参考资料

    1. Dynamic DataSource Routing with Spring @Transactional
    2. Alibaba Druid
  • 相关阅读:
    jenkins使用
    pytest+allure生成接口自动化测试报告
    charles系列
    go语言安装使用
    go语言介绍
    Nginx
    python面试题-python相关
    pyhon全栈开发学习目录
    补充【第二章】supervisor守护进程保障服务
    python全栈开发基础【补充】python中列表排序,字典排序,列表中的字典排序
  • 原文地址:https://www.cnblogs.com/pier2/p/spring-boot-read-write-split.html
Copyright © 2011-2022 走看看