Giter Site home page Giter Site logo

db-read-write-separate's Introduction

数据库读写分离

数据库读写分离环境搭建:https://github.com/niezhiliang/mysql-master-slave-docker

数据库读写分离一般分为两种,一种是静态的,一种是动态的,顾名思义静态是配置了不会变,有局限性,而动态的会根据业务的需求自动切换数据源

静态

这种方式一般适用于项目需要依赖两个不同的数据库,而不是所谓的读写分离的主从数据库。比如一个数据库没有用户表,他想用自己另一个项目中的 用户,这个时候用这种方式就比较合适啦。要想用这种方式来配置数据库的读写分离也不是不行,那就把读写业务拆分出来嘛。 生成两套xml和mapper.java, 一套用来处理 写操作,另一套用来进行读操作。功能能实现,但在写代码的时候就特别烦,有时候读的业务写到写的数据库就尴尬啦。

这种方式比较简单,就不做说明啦,相信我自己也不会忘的。😝

动态

这种方式就比较适用于数据库的读写分离啦。这种方式解决了静态方式的缺陷,共用一套mapper文件,无需拆分读写业务,用到aop对mapper的方法进行分析,判断是读操作还是写操作, 并切换到相应的数据源。架构搭建好以后,编码的时候无需关心什么读跟写,只需专注于业务实现就可以啦。

  • 配置好所有的读写数据源

    @Configuration
    public class DataBaseConfiguration {
    
        @Value("${spring.datasource.type}")
        private Class<? extends DataSource> dataSourceType;
    
        //这个就是我们的写数据源,也就是master库
        @Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
        @Primary
        @ConfigurationProperties(prefix = "spring.write.datasource")
        public DataSource writeDataSource() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
        
        //配置第一个读库 所有读数据源都需要将其配置成bean 并添加到读数据源集合中
        @Bean(name = "readDataSourceOne")
        @ConfigurationProperties(prefix = "spring.read.one.datasource")
        public DataSource readDataSourceOne() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
        
        //配置第二个读库
        @Bean(name = "readDataSourceTwo")
        @ConfigurationProperties(prefix = "spring.read.two.datasource")
        public DataSource readDataSourceTwo() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
    
        //读写库集合的Bean
        @Bean("readDataSources")
        public List<DataSource> readDataSources() {
            List<DataSource> dataSources = new ArrayList<DataSource>();
            dataSources.add(readDataSourceOne());
            dataSources.add(readDataSourceTwo());
            return dataSources;
        }
    }
  • 配置动态的sqlSessionFactory

@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@MapperScan(basePackages = {"com.niezhiliang.db.read.write.separate.dynamic.type.mapper"})
public class TxxsbatisConfiguration {
    
    //我们这里使用的是alibaba druid连接池
    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    //读取配置的读数据库的数量(从库)
    @Value("${spring.datasource.readSize}")
    private String dataSourceSize;

    //我们的写数据库,也就是主数据库
    @Resource(name = "writeDataSource")
    private DataSource dataSource;
    
    //所有的读数据源集合
    @Resource(name = "readDataSources")
    private List<DataSource> readDataSources;


    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(roundRobinDataSourceProxy());
        sqlSessionFactoryBean.setTypeAliasesPackage("com.niezhiliang.db.read.write.separate.dynamic.type.domain");
        //mapper.xml文件位置
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
        .getResources("classpath:mappers*//*.xml"));
        sqlSessionFactoryBean.getObject().getConfiguration()
                .setMapUnderscoreToCamelCase(true);

        return sqlSessionFactoryBean.getObject();
    }


    //配置动态数据源的路由对象
    @Bean
    public AbstractRoutingDataSource roundRobinDataSourceProxy() {
        TxxsAbstractRoutingDataSource proxy = new TxxsAbstractRoutingDataSource(readDataSources.size());
        Map<Object,Object> targetDataSourceSize = new HashMap<Object,Object>();
        targetDataSourceSize.put(dataSourceType.getTypeName(),dataSource);

        for (int i = 0; i < readDataSources.size(); i++) {
            targetDataSourceSize.put(i,readDataSources.get(i));
        }
        //如果没有读数据集 则默认为写数据库
        proxy.setDefaultTargetDataSource(dataSource);
        //配置读数据源
        proxy.setTargetDataSources(targetDataSourceSize);

        return proxy;
    }

}
  • 配置一个线程池,用于切换数据源(通过改变当前线程变量的方法,达到动态改变数据源)
public class DataSourceContextHolder {
    private static final ThreadLocal<String> local = new ThreadLocal<String>();

    public static ThreadLocal<String> getLocal() {
        return local;
    }

    /**
     * 读可能是多个库
     */
    public static void read() {
        local.set(DataSourceType.read.getType());
    }

    /**
     * 写只有一个库
     */
    public static void write() {
        local.set(DataSourceType.write.getType());
    }

    public static String getJdbcType() {
        return local.get();
    }
}
  • aop拦截所有的数据库请求
@Aspect
@Component
public class DataSourceAop {

    public static final Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
    
    //拦截mapper所有的读操作,将数据源动态切换到读数据源
    @Before("execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.select*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*..count*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.read();
        logger.info("dataSource切换到:Read");
    }
    //拦截mapper所有的写操作,将数据源动态切换到写数据源
    @Before(("execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.insert*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.update*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.delete*(..))"))
    public void setWriteDataSourceType() {
        DataSourceContextHolder.write();
        logger.info("dataSource切换到:Write");
    }
}
  • 动态数据源切换
public class TxxsAbstractRoutingDataSource extends AbstractRoutingDataSource {

    private  int dataSourceNumber;

    private AtomicInteger count = new AtomicInteger(0);


    public TxxsAbstractRoutingDataSource(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        //获取当前线程是进行读还是写
        String typeKey = DataSourceContextHolder.getJdbcType();
        if (typeKey.equals(DataSourceType.write.getType()))
            return DataSourceType.write.getType();
        // 读 简单负载均衡 count会一直累加,那后模读数据源的数量 达到简单的负载均衡
        int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}
                  

db-read-write-separate's People

Contributors

niezhiliang avatar

Watchers

 avatar

Forkers

wlhebut172

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.