SpringBoot读写分离配置与事务

简介: SpringBoot读写分离配置与事务

引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>


跟mybatis-plus属于同一个开源组织 苞米豆

配置文件

spring:
  datasource:
    dynamic:
      primary: master
      # 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: true
      datasource:
        master:
          url: jdbc:mysql://192.168.101.128:3307/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
        slave:
          url: jdbc:mysql://192.168.101.128:3308/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
# 按需开启日志
logging:
  level:
    com.baomidou.dynamic: debug


方法或类上加上@DS注解即可切换数据源


该框架获取数据库连接的核心逻辑是以下这段

在被DS注解标记的方法上, 会被此拦截器拦截, 获取到注解上定义的值, 并存入栈结构中

com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor


public Object invoke(MethodInvocation invocation) throws Throwable {
    String dsKey = determineDatasourceKey(invocation);
    //获取注解值入栈
    DynamicDataSourceContextHolder.push(dsKey);
    try {
        return invocation.proceed();
    } finally {
        //方法结束后出栈
        DynamicDataSourceContextHolder.poll();
    }
}


然后com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource#getConnection()

public Connection getConnection() throws SQLException {
    String xid = TransactionContext.getXID();
    if (StringUtils.isEmpty(xid)) {
        return determineDataSource().getConnection();
    } else {
        //获取栈顶的一个值
        String ds = DynamicDataSourceContextHolder.peek();
        ds = StringUtils.isEmpty(ds) ? "default" : ds;
        //根据值获取对应数据库的连接
        ConnectionProxy connection = ConnectionFactory.getConnection(ds);
        return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
    }
}


可以发现框架是通过维护一个栈结构进行对应数据源的切换, 类似方法的栈, 因为方法间可能嵌套调用, 所以使用此结构便于管理


但Spring的@Transactional会影响@DS

例如

@Autowired
@Lazy
CurrentService currentService;
@DS("master")
@Transactional
public void updateUser() {
  baseMapper.updateById(user);
    System.out.println(currentService.get());
}
@DS("slave")
public User get() {
  return baseMapper.selectById(1);
}


在这里, master和slave是使用binlog搭建的读写分离架构

但实际get方法却能读取到updateUser所做的修改, 通过Debug也能看到真正的数据库连接属性, get方法还是使用的master库

因为在Spring管理下, 获取到数据库连接后, 会和当前线程进行绑定, 如果后面的方法被判断为不需要新建连接, 则复用之前与线程绑定的连接, 那么即使有DS注解, 也切换不了库

如何判断需不需要新建连接? 看被调用方法是否定义了事务传播属性.

org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction方法中

if (isExistingTransaction(transaction)) {
  //找到现有事务 -> 检查传播行为以了解行为方式
    //当前已经存在一个事务
    return handleExistingTransaction(def, transaction, debugEnabled);
}


继续调用org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction

//判断当前方法的隔离属性是否为PROPAGATION_REQUIRES_NEW
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
    if (debugEnabled) {
        logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
    }
    SuspendedResourcesHolder suspendedResources = suspend(transaction);
    try {
        return startTransaction(definition, transaction, debugEnabled, suspendedResources);
    }
    catch (RuntimeException | Error beginEx) {
        resumeAfterBeginException(transaction, suspendedResources, beginEx);
        throw beginEx;
    }
}


继续调用org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
      boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}


继续调用org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
  //获取当前的数据源, 此处才能让@DS注解生效
    Connection newCon = obtainDataSource().getConnection();
  if (logger.isDebugEnabled()) {
    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
  }
    //将数据库连接绑定到事务
  txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}


所以如果想让get方法读取从库, 则需要定义传播属性以便让Spring建立新连接


第二种方法就是使用该框架的@DSTransactional

该方法也会进行事务管理, 但功能比较简陋

这个注解还提供了一个本地事务的功能: 解决多数据源的事务问题.

但这个功能也有问题, 不建议使用

看这个方法 com.baomidou.dynamic.datasource.tx.ConnectionFactory#notify

public static void notify(Boolean state) {
    try {
        Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
        //获取当前线程所有的数据库连接, 通知其进行回滚/提交, 可能存在某一个事务提交成功, 某一事务提交失败.
        //并不能保证最终一致性
        for (ConnectionProxy connectionProxy : concurrentHashMap.values()) {
            connectionProxy.notify(state);
        }
    } finally {
        CONNECTION_HOLDER.remove();
    }
}


对于这种多库事务, 建议使用Seata或消息队列

贴心的是, 框架还与Seata进行了整合

引入依赖

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>


spring:
  datasource:
    dynamic:
      #seata1.0之后支持自动代理 这里直接配置true
      seata: true


目录
相关文章
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
23天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
36 4
|
27天前
|
Java 数据库连接 Maven
springBoot:项目建立&配置修改&yaml的使用&resource 文件夹(二)
本文档介绍了如何创建一个基于Maven的项目,并配置阿里云仓库、数据库连接、端口号、自定义启动横幅及多环境配置等。同时,详细说明了如何使用YAML格式进行配置,以及如何处理静态资源和模板文件。文档还涵盖了Spring Boot项目的`application.properties`和`application.yaml`文件的配置方法,包括设置数据库驱动、URL、用户名、密码等关键信息,以及如何通过配置文件管理不同环境下的应用设置。
|
20天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
31 0
|
7天前
|
Java Spring 容器
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
28 3
|
18天前
|
druid Java Maven
|
30天前
|
消息中间件 Java 大数据
大数据-56 Kafka SpringBoot与Kafka 基础简单配置和使用 Java代码 POM文件
大数据-56 Kafka SpringBoot与Kafka 基础简单配置和使用 Java代码 POM文件
62 2
|
1月前
|
数据采集 监控 Java
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
本文是关于SpringBoot日志的详细教程,涵盖日志的定义、用途、SLF4J框架的使用、日志级别、持久化、文件分割及格式配置等内容。
87 0
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
|
25天前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
102 0
|
27天前
|
机器学习/深度学习 移动开发 自然语言处理
基于人工智能技术的智能导诊系统源码,SpringBoot作为后端服务的框架,提供快速开发,自动配置和生产级特性
当身体不适却不知该挂哪个科室时,智能导诊系统应运而生。患者只需选择不适部位和症状,系统即可迅速推荐正确科室,避免排错队浪费时间。该系统基于SpringBoot、Redis、MyBatis Plus等技术架构,支持多渠道接入,具备自然语言理解和多输入方式,确保高效精准的导诊体验。无论是线上医疗平台还是大型医院,智能导诊系统均能有效优化就诊流程。