spring boot + mybatis如何实现数据库的读写分离
介绍
随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段。
方案使用了abstractroutingdatasource和mybatis plugin来动态的选择数据源
选择这个方案的原因主要是不需要改动原有业务代码,非常友好
注:
demo中使用了mybatis-plus,实际使用mybatis也是一样的
demo中使用的数据库是postgres,实际任一类型主从备份的数据库示例都是一样的
demo中使用了alibaba的druid数据源,实际其他类型的数据源也是一样的
环境
首先,我们需要两个数据库实例,一为master,一为slave。
所有的写操作,我们在master节点上操作
所有的读操作,我们在slave节点上操作
需要注意的是:对于一次有读有写的事务,事务内的读操作也不应该在slave节点上,所有操作都应该在master节点上
先跑起来两个pg的实例,其中15432端口对应的master节点,15433端口对应的slave节点:
docker run \ --name pg-master \ -p 15432:5432 \ --env 'pg_password=postgres' \ --env 'replication_mode=master' \ --env 'replication_user=repluser' \ --env 'replication_pass=repluserpass' \ -d sameersbn/postgresql:10-2 docker run \ --name pg-slave \ -p 15433:5432 \ --link pg-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' \ -d sameersbn/postgresql:10-2
实现
整个实现主要有3个部分:
- 配置两个数据源
- 实现abstractroutingdatasource来动态的使用数据源
- 实现mybatis plugin来动态的选择数据源
配置数据源
将数据库连接信息配置到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 public class datasourcepropertiesconfig { @primary @bean("writedatasourceproperties") @configurationproperties("datasource.write") public datasourceproperties writedatasourceproperties() { return new datasourceproperties(); } @bean("readdatasourceproperties") @configurationproperties("datasource.read") public datasourceproperties readdatasourceproperties() { return new datasourceproperties(); } }
实现abstractroutingdatasource
spring提供了abstractroutingdatasource,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:
@component public class customroutingdatasource extends abstractroutingdatasource { @resource(name = "writedatasourceproperties") private datasourceproperties writeproperties; @resource(name = "readdatasourceproperties") private datasourceproperties readproperties; @override public void afterpropertiesset() { datasource writedatasource = writeproperties.initializedatasourcebuilder().type(druiddatasource.class).build(); datasource readdatasource = readproperties.initializedatasourcebuilder().type(druiddatasource.class).build(); setdefaulttargetdatasource(writedatasource); map<object, object> datasourcemap = new hashmap<>(); datasourcemap.put(write_datasource, writedatasource); datasourcemap.put(read_datasource, readdatasource); settargetdatasources(datasourcemap); super.afterpropertiesset(); } @override protected object determinecurrentlookupkey() { string key = datasourceholder.getdatasource(); if (key == null) { // default datasource return write_datasource; } return key; } }
abstractroutingdatasource内部维护了一个map<object, object>的map
在初始化过程中,我们将write、read两个数据源加入到这个map
调用数据源时:determinecurrentlookupkey()方法返回了需要使用的数据源对应的key
当前线程需要使用的数据源对应的key,是在datasourceholder类中维护的:
public class datasourceholder { public static final string write_datasource = "write"; public static final string read_datasource = "read"; private static final threadlocal<string> local = new threadlocal<>(); public static void putdatasource(string datasource) { local.set(datasource); } public static string getdatasource() { return local.get(); } public static void cleardatasource() { local.remove(); } }
实现mybatis plugin
上面提到了当前线程使用的数据源对应的key,这个key需要在mybatis plugin根据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})}) public class mybatisdatasourceinterceptor implements interceptor { @override public object intercept(invocation invocation) throws throwable { boolean synchronizationactive = transactionsynchronizationmanager.issynchronizationactive(); if(!synchronizationactive) { object[] objects = invocation.getargs(); mappedstatement ms = (mappedstatement) objects[0]; if (ms.getsqlcommandtype().equals(sqlcommandtype.select)) { datasourceholder.putdatasource(datasourceholder.read_datasource); } } return invocation.proceed(); } @override public object plugin(object target) { return plugin.wrap(target, this); } @override public void setproperties(properties properties) { } }
仅当未在事务中,并且调用的sql是select类型时,在datasourceholder中将数据源设为read
其他情况下,abstractroutingdatasource会使用默认的write数据源
至此,项目已经可以自动的在读、写数据源间切换,无需修改原有的业务代码
最后,提供demo使用依赖版本
<parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.1.7.release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.postgresql</groupid> <artifactid>postgresql</artifactid> <version>42.2.2</version> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>druid-spring-boot-starter</artifactid> <version>1.1.9</version> </dependency> <dependency> <groupid>com.baomidou</groupid> <artifactid>mybatisplus-spring-boot-starter</artifactid> <version>1.0.5</version> </dependency> <dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus</artifactid> <version>2.1.9</version> </dependency> <dependency> <groupid>io.springfox</groupid> <artifactid>springfox-swagger2</artifactid> <version>2.8.0</version> </dependency> <dependency> <groupid>io.springfox</groupid> <artifactid>springfox-swagger-ui</artifactid> <version>2.8.0</version> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <version>1.16.20</version> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> </dependencies>
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
推荐阅读
-
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
-
Spring+MyBatis实现数据读写分离的实例代码
-
Yii实现多数据库主从读写分离的方法
-
spring boot + mybatis如何实现数据库的读写分离
-
Spring Boot+MyBatis+MySQL读写分离
-
利用mycat实现mysql数据库读写分离的示例
-
spring boot整合mybatis利用Mysql实现主键UUID的方法
-
spring集成mybatis实现mysql读写分离
-
spring boot环境下实现restful+前后端分离的网页开发
-
Spring Boot(六):如何优雅的使用 Mybatis