spring boot + mybatis如何实现数据库的读写分离
介绍
随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段。
方案使用了AbstractRoutingDataSource和mybatisplugin来动态的选择数据源
选择这个方案的原因主要是不需要改动原有业务代码,非常友好
注:
demo中使用了mybatis-plus,实际使用mybatis也是一样的
demo中使用的数据库是postgres,实际任一类型主从备份的数据库示例都是一样的
demo中使用了alibaba的druid数据源,实际其他类型的数据源也是一样的
环境
首先,我们需要两个数据库实例,一为master,一为slave。
所有的写操作,我们在master节点上操作
所有的读操作,我们在slave节点上操作
需要注意的是:对于一次有读有写的事务,事务内的读操作也不应该在slave节点上,所有操作都应该在master节点上
先跑起来两个pg的实例,其中15432端口对应的master节点,15433端口对应的slave节点:
dockerrun\ --namepg-master\ -p15432:5432\ --env'PG_PASSWORD=postgres'\ --env'REPLICATION_MODE=master'\ --env'REPLICATION_USER=repluser'\ --env'REPLICATION_PASS=repluserpass'\ -dsameersbn/postgresql:10-2 dockerrun\ --namepg-slave\ -p15433:5432\ --linkpg-master:master\ --env'PG_PASSWORD=postgres'\ --env'REPLICATION_MODE=slave'\ --env'REPLICATION_SSLMODE=prefer'\ --env'REPLICATION_HOST=master'\ --env'REPLICATION_PORT=5432'\ --env'REPLICATION_USER=repluser'\ --env'REPLICATION_PASS=repluserpass'\ -dsameersbn/postgresql:10-2
实现
整个实现主要有3个部分:
- 配置两个数据源
- 实现AbstractRoutingDataSource来动态的使用数据源
- 实现mybatisplugin来动态的选择数据源
配置数据源
将数据库连接信息配置到application.yml文件中
spring: mvc: servlet: path:/api datasource: write: driver-class-name:org.postgresql.Driver url:"${DB_URL_WRITE:jdbc:postgresql://localhost:15432/postgres}" username:"${DB_USERNAME_WRITE:postgres}" password:"${DB_PASSWORD_WRITE:postgres}" read: driver-class-name:org.postgresql.Driver url:"${DB_URL_READ:jdbc:postgresql://localhost:15433/postgres}" username:"${DB_USERNAME_READ:postgres}" password:"${DB_PASSWORD_READ:postgres}" mybatis-plus: configuration: map-underscore-to-camel-case:true
write写数据源,对应到master节点的15432端口
read读数据源,对应到slave节点的15433端口
将两个数据源信息注入为DataSourceProperties:
@Configuration publicclassDataSourcePropertiesConfig{ @Primary @Bean("writeDataSourceProperties") @ConfigurationProperties("datasource.write") publicDataSourcePropertieswriteDataSourceProperties(){ returnnewDataSourceProperties(); } @Bean("readDataSourceProperties") @ConfigurationProperties("datasource.read") publicDataSourcePropertiesreadDataSourceProperties(){ returnnewDataSourceProperties(); } }
实现AbstractRoutingDataSource
spring提供了AbstractRoutingDataSource,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:
@Component publicclassCustomRoutingDataSourceextendsAbstractRoutingDataSource{ @Resource(name="writeDataSourceProperties") privateDataSourcePropertieswriteProperties; @Resource(name="readDataSourceProperties") privateDataSourcePropertiesreadProperties; @Override publicvoidafterPropertiesSet(){ DataSourcewriteDataSource= writeProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build(); DataSourcereadDataSource= readProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build(); setDefaultTargetDataSource(writeDataSource); Map
AbstractRoutingDataSource内部维护了一个Map
在初始化过程中,我们将write、read两个数据源加入到这个map
调用数据源时:determineCurrentLookupKey()方法返回了需要使用的数据源对应的key
当前线程需要使用的数据源对应的key,是在DataSourceHolder类中维护的:
publicclassDataSourceHolder{ publicstaticfinalStringWRITE_DATASOURCE="write"; publicstaticfinalStringREAD_DATASOURCE="read"; privatestaticfinalThreadLocallocal=newThreadLocal<>(); publicstaticvoidputDataSource(StringdataSource){ local.set(dataSource); } publicstaticStringgetDataSource(){ returnlocal.get(); } publicstaticvoidclearDataSource(){ local.remove(); } }
实现mybatisplugin
上面提到了当前线程使用的数据源对应的key,这个key需要在mybatisplugin根据sql类型来确定
MybatisDataSourceInterceptor类:
@Component @Intercepts({ @Signature(type=Executor.class,method="update", args={MappedStatement.class,Object.class}), @Signature(type=Executor.class,method="query", args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}), @Signature(type=Executor.class,method="query", args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class, CacheKey.class,BoundSql.class})}) publicclassMybatisDataSourceInterceptorimplementsInterceptor{ @Override publicObjectintercept(Invocationinvocation)throwsThrowable{ booleansynchronizationActive=TransactionSynchronizationManager.isSynchronizationActive(); if(!synchronizationActive){ Object[]objects=invocation.getArgs(); MappedStatementms=(MappedStatement)objects[0]; if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){ DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE); } } returninvocation.proceed(); } @Override publicObjectplugin(Objecttarget){ returnPlugin.wrap(target,this); } @Override publicvoidsetProperties(Propertiesproperties){ } }
仅当未在事务中,并且调用的sql是select类型时,在DataSourceHolder中将数据源设为read
其他情况下,AbstractRoutingDataSource会使用默认的write数据源
至此,项目已经可以自动的在读、写数据源间切换,无需修改原有的业务代码
最后,提供demo使用依赖版本
org.springframework.boot spring-boot-starter-parent 2.1.7.RELEASE org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.postgresql postgresql 42.2.2 com.alibaba druid-spring-boot-starter 1.1.9 com.baomidou mybatisplus-spring-boot-starter 1.0.5 com.baomidou mybatis-plus 2.1.9 io.springfox springfox-swagger2 2.8.0 io.springfox springfox-swagger-ui 2.8.0 org.projectlombok lombok 1.16.20 org.springframework.boot spring-boot-starter-test test
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。