热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javamybaits读写分离_springboot+mybatis如何实现数据库的读写分离

介绍随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段。方案使用了AbstractRoutingDataSource和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 dataSourceMap &#61; new HashMap<>();

dataSourceMap.put(WRITE_DATASOURCE, writeDataSource);

dataSourceMap.put(READ_DATASOURCE, readDataSource);

setTargetDataSources(dataSourceMap);

super.afterPropertiesSet();

}

&#64;Override

protected Object determineCurrentLookupKey() {

String key &#61; DataSourceHolder.getDataSource();

if (key &#61;&#61; null) {

// default datasource

return WRITE_DATASOURCE;

}

return key;

}

}

AbstractRoutingDataSource内部维护了一个Map的Map

在初始化过程中&#xff0c;我们将write、read两个数据源加入到这个map

调用数据源时&#xff1a;determineCurrentLookupKey()方法返回了需要使用的数据源对应的key

当前线程需要使用的数据源对应的key&#xff0c;是在DataSourceHolder类中维护的&#xff1a;public class DataSourceHolder {

public static final String WRITE_DATASOURCE &#61; "write";

public static final String READ_DATASOURCE &#61; "read";

private static final ThreadLocal local &#61; 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&#xff0c;这个key需要在mybatis plugin根据sql类型来确定

MybatisDataSourceInterceptor类&#xff1a;&#64;Component

&#64;Intercepts({

&#64;Signature(type &#61; Executor.class, method &#61; "update",

args &#61; {MappedStatement.class, Object.class}),

&#64;Signature(type &#61; Executor.class, method &#61; "query",

args &#61; {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),

&#64;Signature(type &#61; Executor.class, method &#61; "query",

args &#61; {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,

CacheKey.class, BoundSql.class})})

public class MybatisDataSourceInterceptor implements Interceptor {

&#64;Override

public Object intercept(Invocation invocation) throws Throwable {

boolean synchronizationActive &#61; TransactionSynchronizationManager.isSynchronizationActive();

if(!synchronizationActive) {

Object[] objects &#61; invocation.getArgs();

MappedStatement ms &#61; (MappedStatement) objects[0];

if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {

DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE);

}

}

return invocation.proceed();

}

&#64;Override

public Object plugin(Object target) {

return Plugin.wrap(target, this);

}

&#64;Override

public void setProperties(Properties properties) {

}

}

仅当未在事务中&#xff0c;并且调用的sql是select类型时&#xff0c;在DataSourceHolder中将数据源设为read

其他情况下&#xff0c;AbstractRoutingDataSource会使用默认的write数据源

至此&#xff0c;项目已经可以自动的在读、写数据源间切换&#xff0c;无需修改原有的业务代码

最后&#xff0c;提供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



推荐阅读
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 标题: ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Oracle10g备份导入的方法及注意事项
    本文介绍了使用Oracle10g进行备份导入的方法及相关注意事项,同时还介绍了2019年独角兽企业重金招聘Python工程师的标准。内容包括导出exp命令、删用户、创建数据库、授权等操作,以及导入imp命令的使用。详细介绍了导入时的参数设置,如full、ignore、buffer、commit、feedback等。转载来源于https://my.oschina.net/u/1767754/blog/377593。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
author-avatar
觴儿_996
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有