Spring | 基于SpringBoot的多数据源实战 - 使用seata实现多数据源的全局事务管理

引言

在软件开发中,多数据源 的应用越来越普遍,特别是在微服务架构业务模块化 的场景下。多数据源能够让不同的业务模块和微服务拥有各自独立的数据存储,大大提高了系统的灵活性和可维护性。本文将深入探讨多数据源的配置和实施,以及在Spring Boot 环境下,如何通过Spring Data JPAGradle来实现多数据源的管理和应用。

本文讨论的多数据源指的是关系型数据库,即一个服务有多个这样的数据库。

1.1 多数据源的必要性

随着业务的发展和演变,单一数据源已经无法满足多元化和复杂化的业务需求多数据源 的应用不仅能够更好地支持业务的发展,还能够有效地实现资源隔离 和管理,减少系统的耦合度,提高服务的稳定性和可用性。使用多数据源可以有如下几个优点:

  1. 性能优化 :不同的关系型数据库有各自的优势和特性,一些数据库更适合 取操作,而另一些数据库更适合入操作。通过将读写负载分配到不同的数据库实例上,可以优化性能。
  2. 数据隔离:在一个系统中,有些数据可能更重要,需要更高的安全性和可靠性;而其他数据可能不太重要,可以容忍一定程度的数据丢失。这个时候,将不同类型的数据存储在不同的数据库中,可以实现数据的隔离,满足不同的数据安全和可靠性需求。
  3. 业务逻辑隔离:在复杂的系统中,不同的模块或子系统可能有不同的业务逻辑和数据处理需求。为不同的模块或子系统使用不同的数据库,可以简化系统的设计和维护。

1.2 多数据源的应用场景

常见到的几个大型项目的业务场景如下:

  • 金融系统 :金融系统通常要处理大量的事务数据和报表数据。将事务处理和报表生成分配到不同的数据库可以优化性能和简化系统设计。
  • 电商平台 :电商平台通常会涉及到用户信息、订单数据、商品数据等多种类型的数据。为这些不同类型的数据使用不同的数据库实例可以实现数据和业务逻辑的隔离
  • ERP系统:企业资源规划(ERP)系统通常包含多个模块,例如财务、人力资源和供应链管理。为每个模块使用独立的数据库可以使得开发和维护更加便捷。

以下所有示例均已上传至Github上,大家可以将项目拉取到本地进行运行 Github示例(如果对Gradle还不熟练,建议翻看我之前的文章):gradle-spring-boot-demo


实战演示

本章将详细说明如何在Spring Boot项目中实施多数据源。我们会一步一步地演示如何配置两个H2数据库实例作为我们的数据源。

2.1 创建实体类

首先,我们创建两个实体类,一个用于主数据源,一个用于次数据源。我们在这里以User实体为例。请确保您的实体类在正确的包中。

java 复制代码
// 主数据源实体
@Entity
@Data
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

// 次数据源实体
@Entity
@Table(name = "orders")
@Data
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderNumber;
    private Double amount;
}

2.2 配置数据源

接下来,我们需要在application.yml中配置两个数据源:

yaml 复制代码
spring:
  datasource:
    primary:
      jdbc-url: jdbc:h2:file:./multi-datasource/data/testdb1
      driver-class-name: org.h2.Driver
      username: sa
      password: password
    secondary:
      jdbc-url: jdbc:h2:file:./multi-datasource/data/testdb2
      driver-class-name: org.h2.Driver
      username: sa
      password: password
  h2:
    console:
      enabled: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

这里,我们配置了两个H2数据库实例,一个作为主数据源,一个作为次数据源。

2.3 实现数据源配置类

为了实现多数据源,我们需要创建两个配置类,PrimaryDataSourceConfigSecondaryDataSourceConfig,并在其中定义DataSourceEntityManagerFactoryTransactionManager的beans。

java 复制代码
@Configuration
@EnableJpaRepositories(
        basePackages = "org.kfaino.datasource.repository.primary")
@EntityScan(basePackages = {"org.kfaino.datasource.entity.primary"})
public class PrimaryDataSourceConfig {

    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("primaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("org.kfaino.datasource.entity.primary") // 设置实体类的包路径
                .persistenceUnit("primary")
                .properties(hibernateProperties())
                .build();
    }

    private Map<String, Object> hibernateProperties() {
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.show_sql", true);
        return properties;
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

}

@Configuration
@EnableJpaRepositories(
        basePackages = "org.kfaino.datasource.repository.secondary",
        entityManagerFactoryRef = "secondaryEntityManagerFactory",
        transactionManagerRef = "secondaryTransactionManager")
@EntityScan(basePackages = {"org.kfaino.datasource.entity.primary"})
public class SecondaryDataSourceConfig {

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "secondaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("org.kfaino.datasource.entity.secondary") // 设置实体类的包路径
                .persistenceUnit("secondary")
                .properties(hibernateProperties())
                .build();
    }

    private Map<String, Object> hibernateProperties() {
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.show_sql", true);
        return properties;
    }

    @Bean(name = "secondaryTransactionManager")
    public PlatformTransactionManager secondaryTransactionManager(
            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory secondaryEntityManagerFactory) {
        return new JpaTransactionManager(secondaryEntityManagerFactory);
    }

}

💡 温馨提示: 请注意,在这个配置类中,我们为两个数据源分别定义了DataSourceEntityManagerFactoryTransactionManager@Primary注解用于指定主数据源相关的beans。

2.4 配置Repository类

我们需要创建两个Repository类,每个类操作一个数据源的实体。在这里,我们可以使用Spring Data JPA的JpaRepository接口。

java 复制代码
// 主数据源Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

// 次数据源Repository
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {}

2.5 运行与验证

运行Spring Boot应用,在控制台能够看到两个数据源都被成功配置。 并且可以通过相应的Repository类进行数据操作。我们写个Controller来和数据库进行交互,限于篇幅,其它代码我已省略,如需完整实例,可以直接从GitHub仓库下载:

java 复制代码
@RestController
@RequestMapping("user")
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private OrderService orderService;
    @Resource
    private UserOrderService userOrderService;
    
    @PostMapping("/createUser")
    public User createUser(@Valid @RequestBody UserDTO userDTO) {
        return userService.createUser(userDTO);
    }

    @PostMapping("/createOrder")
    public Order createOrder(@Valid @RequestBody OrderDTO orderDTO) {
        return orderService.createOrder(orderDTO);
    }


    @PostMapping("/createMix")
    public User createMix(@Valid @RequestBody UserOrderDTO userOrderDTO) {
        return userOrderService.createUserAndOrder(userOrderDTO);
    }
}

接着执行这三个请求: 两张不同数据源的表均被创建完毕:

💡 注意: 在进行实际的数据操作时,如果需要特定的事务管理器,可以在Service类或Repository类上使用@Transactional(transactionManager = "指定的事务管理器")来进行指定。


事务管理与数据一致性

3.1 事务管理

在多数据源中,事务管理 是至关重要的,它确保我们的系统在执行多个操作时能够维持数据的完整性和一致性。我们用代码演示一个案例,我们在UserOrderService中故意写一个会报错代码:

java 复制代码
    @Transactional("transactionManager")
    public User createUserAndOrder(UserOrderDTO userOrderDTO) {
        OrderDTO orderDTO = new OrderDTO();
        BeanUtils.copyProperties(userOrderDTO, orderDTO);
        orderService.createOrder(orderDTO);
        // 报错
        int i = 1/0;
        User user = new User();
        BeanUtils.copyProperties(userOrderDTO, user);
        userRepository.save(user);
        return user;
    }

我们调用下该方法:

由于使用的是不同的事务管理器,尽管transactionManager事务管理器回滚了,我们依然可以看到orders表被提交:

3.2 使用Seata完成全局事务管理

💡本节案例只是简单的全局事务管理,为了演示测试方便,除了seata本身并未引入其它中间件

3.2.1 安装seata-server

要想使用seata需要先安装seata-server,下载路径如下:github.com/seata/seata... 我这里使用的是window系统,下载zip即可: 下载后双击运行: 在控制台看到seata正常启动了:

3.2.2 配置IDEA

  1. 引入seata依赖,我这里使用的是gradle,只需要引入这行即可:

    java 复制代码
        implementation 'io.seata:seata-spring-boot-starter:1.7.1' // 请检查最新的版本号
  2. 新增application.yml配置

    yml 复制代码
    seata:
      enabled: true
      application-id: multi-datasource
      tx-service-group: my_test_tx_group
      enable-auto-data-source-proxy: true
      config:
        type: file
        file:
          data-file: classpath:file.conf
      registry:
        type: file
        file:
          data-file: classpath:registry.conf
  3. 新增file.conf文件

    java 复制代码
    service {
      # tx-service-group映射
      vgroupMapping.my_test_tx_group = "default"
      vgroupMapping.default = "default"
    
      # Seata Server的地址,是你的Seata Server所在的IP地址和端口
      default.grouplist = "127.0.0.1:8091"
    
      # 其他相关配置
      enableDegrade = false
      disableGlobalTransaction = false
    }
    
    store {
      mode = "file"
      file {
        # 本地事务日志存储的目录
        dir = "sessionStore"
      }
    }
  4. 新增registry.conf文件

    java 复制代码
    registry {
      type = "file"
    
      file {
        name = "file.conf"
      }
    }
    
    config {
      type = "file"
      file {
        name = "file.conf"
      }
    }
  5. UserOrderService新增如下方法:

    java 复制代码
        @GlobalTransactional
        public void createUserAndOrderByGlobalTransaction(UserOrderDTO userOrderDTO) {
            OrderDTO orderDTO = new OrderDTO();
            BeanUtils.copyProperties(userOrderDTO, orderDTO);
            orderService.createOrder(orderDTO);
            // 报错
            int i = 1/0;
    
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(userOrderDTO, userDTO);
            userService.createUser(userDTO);
        }
  6. 新增controller方法:

    java 复制代码
        @PostMapping("/createMixGlobalTransaction")
        public void createMixByGlobalTransaction(@Valid @RequestBody UserOrderDTO userOrderDTO) {
            userOrderService.createUserAndOrderByGlobalTransaction(userOrderDTO);
        }
  7. 请求controller 执行结果如下: 全局事务生效,事务被回滚。


总结

至此,本章结束。本文深入探讨了多数据源的优势和应用常见,并为你展示了在Spring Boot如何进行多数据源的使用和整合。同时,我们也探讨了多数据源中存在的事务问题,并通过seata的全局事务管理解决这个问题。

参考文献

  1. SpringBoot如何整合多个数据源,看这篇就够了 - 腾讯云
  2. Spring Boot 整合多数据源,这才叫优雅 - 掘金
  3. springboot整合mybatis跨库操作、配置多个数据源DataSource - CSDN
  4. SpringBoot整合MyBatis多数据源 - 腾讯云
  5. 5分钟学会springboot整合多数据源 - 思否
  6. springboot(七):springboot+mybatis多数据源最简解决方案 - 阿里云
相关推荐
后端小张3 小时前
基于飞算AI的图书管理系统设计与实现
spring boot
金銀銅鐵8 小时前
Spring 中的 initializeBean 方法的内部逻辑小总结
spring
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
阿杆1 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C2 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*2 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*2 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu2 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
计算机毕业设计木哥2 天前
计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
java·vue.js·spring boot·mysql·spark·毕业设计·课程设计
青衫客362 天前
Spring异步编程- 浅谈 Reactor 核心操作符
java·spring·响应式编程