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


目录
打赏
0
1
0
0
1
分享
相关文章
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
24 14
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
38 12
|
4月前
|
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
86 4
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
82 0
springBoot:项目建立&配置修改&yaml的使用&resource 文件夹(二)
本文档介绍了如何创建一个基于Maven的项目,并配置阿里云仓库、数据库连接、端口号、自定义启动横幅及多环境配置等。同时,详细说明了如何使用YAML格式进行配置,以及如何处理静态资源和模板文件。文档还涵盖了Spring Boot项目的`application.properties`和`application.yaml`文件的配置方法,包括设置数据库驱动、URL、用户名、密码等关键信息,以及如何通过配置文件管理不同环境下的应用设置。
515 1
SpringBoot自动配置及自定义Starter
Java程序员依赖Spring框架简化开发,但复杂的配置文件增加了负担。SpringBoot以“约定大于配置”理念简化了这一过程,通过引入各种Starter并加载默认配置,几乎做到开箱即用。
127 10
SpringBoot自动配置及自定义Starter
|
4月前
|
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
87 0
SpringBoot配置跨模块扫描问题解决方案
在分布式项目中,使用Maven进行多模块开发时,某些模块(如xxx-common)没有启动类。如何将这些模块中的类注册为Spring管理的Bean对象?本文通过案例分析,介绍了两种解决方案:常规方案是通过`@SpringBootApplication(scanBasePackages)`指定扫描路径;推荐方案是保持各模块包结构一致(如com.xxx),利用SpringBoot默认扫描规则自动识别其他模块中的组件,简化配置。
SpringBoot配置跨模块扫描问题解决方案
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
186 14
|
3月前
|
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
184 1
SpringBoot入门(7)- 配置热部署devtools工具
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等