关于springboot:mdsspringboot一个基于SpringBoot2x的支持任意场景的多数据源框架

35次阅读

共计 8563 个字符,预计需要花费 22 分钟才能阅读完成。

mds-spring-boot

根本简介

mds-spring-boot 是什么?

mds-spring-boot 是一个基于 SpringBoot2.x 的、全场景反对的、多数据源框架,反对 Spring-JDBC、Mybatis、Mybatis-Plus、Mybatis-Tiny、ShardingSphere、Mycat 等,反对本地事务及残缺的基于 Spring-@Transactional 申明式事务(及事务流传个性)。

我的项目地址:https://github.com/penggle/md…

个性及限度

全场景反对:

  • 反对单纯的 Spring-JDBC 原生用法(即不应用其余 ORM 框架)
    • Spring-JDBC(spring-boot-starter-jdbc)是最根本的反对
    • SpringBoot-Mybatis(mybatis-spring-boot-starter)是可选反对,即应用时须要手动引入 mybatis-spring-boot-starter 依赖
  • 反对单纯的 Mybatis 原生用法(即不应用 Mybatis-Plus、Mybatis-Tiny 等懒人框架)
  • 反对 MybatisPlus、MybatisTiny 等懒人框架用法
  • 反对本地事务,不反对分布式事务
    • 【反对本地事务】即反对在同一个 JVM 中通过 JDBC 操作多个数据源(数据库),保障其正确的 Spring 事务流传个性
  • 反对传统单库多数据源场景,这也是最常见的
  • 反对客户端分库分表的多数据源场景
    • 【客户端分库分表】即诸如 ShardingSphere-JDBC 这样的客户端 (嵌入在利用外部的) 分库分表框架
    • 这种状况下还反对混用,即存在单库数据源(例如我的项目中大多数表不须要分库分表,其不属于 ShardingSphere 治理,而属于利用自身来治理),也存在 ShardingSphereDataSource(比方我的项目中有几个表的确需借助 ShardingSphere 来做分库分表),这种混用的场景在理论我的项目中也十分常见。示例代码就是这种混用形式
  • 反对服务端分库分表多数据源场景
    • 【服务端分库分表】即诸如 ShardingSphere-Proxy、Mycat 这样的服务端 (伪装成一个数据库 Server) 分库分表中间件
    • 这种状况下跟传统的单库多数据源场景一样了,因为作为利用的客户端看来,ShardingSphere-Proxy、Mycat 这样的分库分表中间件就是一个数据库 Server
  • 再回头看看,是不是反对全场景?(基本上是全场景了吧)

疾速入门

Talk is cheap,show me the code!

  • 第一步引入依赖
    <dependency>
        <groupId>io.github.penggle</groupId>
        <artifactId>mds-spring-boot-starter</artifactId>
        <!-- 版本阐明:2.1 指的是基于 mybatis-spring-boot-starter 2.1.x 版本的意思 -->
        <version>2.1</version>
    </dependency>
    
    <!-- 当然 mybatis-spring-boot-starter 是须要手动引入的 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
  • 在 SpringBoot 启动类上启用多数据源反对

    @SpringBootApplication
    @EnableMultiDataSource({@NamedDatabase("product"), @NamedDatabase("order")})
    public class GmdsExample1Application {public static void main(String[] args) {SpringApplication.run(GmdsExample1Application.class, args);
        }
    
    }
  • XxxMapper 接口上标记所属数据源

    @NamedDatabase("product")
    public interface ProductMapper {...}
    
    @NamedDatabase("order")
    public interface MainOrderMapper {...}
    
    @NamedDatabase("order")
    public interface OrderLineMapper {...}
  • application.yml 配置

    • springboot-datasource 配置(必要配置

      spring:
          #数据源配置
          datasource:
              #公共连接池配置
              hikari:
                  #最小闲暇连贯数量
                  minimum-idle: 5
                  #闲暇连贯存活最大工夫,默认 600000(10 分钟)
                  idle-timeout: 180000
                  #连接池最大连接数,默认是 10
                  maximum-pool-size: 10
                  #池中连贯的默认主动提交行为,默认值 true
                  auto-commit: true
                  #池中连贯的最长生命周期,0 示意有限生命周期,默认 1800000(30 分钟)
                  max-lifetime: 1800000
                  #期待来自池的连贯的最大毫秒数,默认 30000(30 秒)
                  connection-timeout: 30000
                  #连贯测试语句
                  connection-test-query: SELECT 1
              #商品库配置(逻辑名称)
              product:
                  username: root
                  password: 123456
                  url: jdbc:mysql://127.0.0.1:3306/ec_product?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false&rewriteBatchedStatements=true&useCursorFetch=true
              #订单库配置(逻辑名称)
              order:
                  username: root
                  password: 123456
                  url: jdbc:mysql://127.0.0.1:3306/ec_order?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false&rewriteBatchedStatements=true&useCursorFetch=true
    • mybatis-spring-boot-starter 模块配置(可选配置

      #mybatis-springboot 配置
      mybatis:
          config-location: classpath:config/mybatis/mybatis-config.xml
          mapper-locations: classpath*:com/penglecode/codeforce/mybatismds/examples/**/*Mapper.xml
          type-aliases-package: com.penglecode.codeforce.mybatismds.examples
          type-aliases-super-type: com.penglecode.codeforce.common.domain.DomainObject
          #商品库的 Mybatis 非凡配置
          product:
              config-location: classpath:config/mybatis/mybatis-config.xml
              mapper-locations: classpath*:com/penglecode/codeforce/mybatismds/examples/product/**/*Mapper.xml
              type-aliases-package: com.penglecode.codeforce.mybatismds.examples
              type-aliases-super-type: com.penglecode.codeforce.common.domain.DomainObject
  • 好了,配置结束,启动我的项目后,主动注册如下 bean 到 Spring 利用上下文中去:

    • {database}DataSourceProperties:指定数据库的org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
    • {database}DataSource:指定数据库的javax.sql.DataSource
    • {database}JdbcTemplate:指定数据库的org.springframework.jdbc.core.JdbcTemplate
    • {database}TransactionManager:指定数据库的org.springframework.transaction.PlatformTransactionManager
    • {database}SqlSessionFactory:指定数据库的org.apache.ibatis.session.SqlSessionFactory
    • {database}SqlSessionTemplate:指定数据库的org.mybatis.spring.SqlSessionTemplate
    • {database}XxxMapper:指定数据库的各个实体的 XxxMapper 接口代理
    • allTransactionManager:默认的全局多数据源事务管理器org.springframework.data.transaction.ChainedTransactionManager
  • 本地事务应用示例:

    碍于篇幅应用精简代码,具体代码见示例代码

    • 测试 Propagation.REQUIRED 流传个性(示例代码)

      因为商品表的库存字段是 UNSIGNED,多运行几次会导致扣库存失败,此时能够看看商品表与订单表是否全副回滚了事务

      @Transactional(transactionManager="product,order", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
      //@Transactional(transactionManager="productTransactionManager,order", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
      //@Transactional(transactionManager="all", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
      //@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
      // 在本例中以上几种 @Transactional 的写法都是等效的,然而倡议:用到那几个库就指明其事务管理器的名称或别名
      public void createOrder1(Order order) {initOrder(order);
          mainOrderService.createMainOrder(order); // 创立主订单
          orderLineService.createOrderLines(order.getOrderLines()); // 创立订单明细
          for(OrderLine orderLine : order.getOrderLines()) {//decrProductInventory1()办法上设置的事务流传个性是 Propagation.REQUIRED
              productService.decrProductInventory1(orderLine.getProductId(), orderLine.getQuantity()); // 扣库存
          }
      }
  • 测试 Propagation.REQUIRES_NEW 流传个性(示例代码)

    @Transactional(transactionManager="product,order", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
    //@Transactional(transactionManager="productTransactionManager,order", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
    //@Transactional(transactionManager="all", propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
    //@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
    // 在本例中以上几种 @Transactional 的写法都是等效的,然而倡议:用到那几个库就指明其事务管理器的名称或别名
    public void createOrder2(Order order) {initOrder(order);
        mainOrderService.createMainOrder(order); // 创立主订单
        orderLineService.createOrderLines(order.getOrderLines()); // 创立订单明细
        for(OrderLine orderLine : order.getOrderLines()) {//decrProductInventory2()办法上设置的事务流传个性是 Propagation.REQUIRES_NEW
            productService.decrProductInventory2(orderLine.getProductId(), orderLine.getQuantity()); // 扣库存
        }
        Assert.isTrue(order.getOrderId().equals(System.currentTimeMillis()), "我不是故意的:测试 Propagation.REQUIRES_NEW");
    }

应用示例

  • 动静注册生命周期

    mds-spring-boot 框架提供了一个钩子接口:MdsComponentsRegistrationLifecycle

    public interface MdsComponentsRegistrationLifecycle {
    
        /**
         * 在动静注册 MDS 组件之前做一些事件
         *
         * @param mdsAnnotationMetadata     - 能够取到 {@link EnableMultiDataSource} 注解的元数据信息
         * @param registry                  - Spring Bean 注册器
         */
        default void beforeMdsComponentsRegistered(AnnotationMetadata mdsAnnotationMetadata, BeanDefinitionRegistry registry) { }
    
        /**
         * 在 MDS 之 JDBC 组件 (DataSource、JdbcTemplate、TransactionManager) 动静注册结束之后做一些事件
         *
         * @param mdsAnnotationMetadata
         * @param registry
         * @param mdsComponentBeans
         */
        default void onMdsJdbcComponentsRegistered(AnnotationMetadata mdsAnnotationMetadata, BeanDefinitionRegistry registry, MdsComponentBeans mdsComponentBeans) { }
    
        /**
         * 在 MDS 之 Mybatis 组件 (SqlSessionFactory、SqlSessionTemplate、XxxMapper) 动静注册结束之后做一些事件
         *
         * @param mdsAnnotationMetadata
         * @param registry
         * @param mdsComponentBeans
         */
        default void onMdsMybatisComponentsRegistered(AnnotationMetadata mdsAnnotationMetadata, BeanDefinitionRegistry registry, MdsComponentBeans mdsComponentBeans) { }
    
        /**
         * 在所有 MDS 组件动静注册结束之后做一些事件
         *
         * @param mdsAnnotationMetadata     - 能够取到 {@link EnableMultiDataSource} 注解的元数据信息
         * @param registry                  - Spring Bean 注册器
         * @param mdsComponentBeans         - 已动静注册的 MDS 组件
         */
        default void afterMdsComponentsRegistered(AnnotationMetadata mdsAnnotationMetadata, BeanDefinitionRegistry registry, MdsComponentBeans mdsComponentBeans) {}}

    基于它,咱们无能很多事件,咱们能够自定义一个实现,并注册到 Spring 上下文中,这在遇到内部数据源的时候特地有用,例如 ” 基于 ShardingSphere-JDBC 的示例 ”

  • 内部数据源反对

    什么叫内部数据源?即数据源不是通过约定的基于如下多数据源配置主动构建进去的状况:

    spring:
        datasource:
            #db1 库配置(逻辑名称)
            db1:
                username: root
                password: 123456
                url: jdbc:mysql://127.0.0.1:3306/myapp_db1
    @SpringBootApplication
    @EnableMultiDataSource({@NamedDatabase("db1"), @NamedDatabase("db2")})
    public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);
        }
    
    }

    此时基于约定的配置,并没有发现 db2 的 yaml 配置,此时 mds-spring-boot 框架会去 Spring 上下文中找名字为 db2DataSource 的 bean,如果没有则会报错。此时 db2 就称之为 内部数据源

    具体请看上面的基于 ShardingSphere-JDBC 的示例

  • 基于 ShardingSphere-JDBC 的示例

    首先为了兼顾灵活性,mds-spring-boot 框架外部并没有任何非凡解决 ShardingSphere 的中央,对于 mds-spring-boot 框架来说,ShardingSphereDataSource 实例是一个 内部数据源,仅要求其在运行时存在于 Spring 上下文中即可。

    因为 mds-spring-boot 框架动静注册数据拜访层 bean 的机会太早了,以至于 ShardingSphere 的主动配置类 org.apache.shardingsphere.spring.boot.ShardingSphereAutoConfigurationorg.apache.shardingsphere.sharding.spring.boot.ShardingRuleSpringBootConfiguration 都还没来得及注册到 Spring 上下文中,此时通过实现 MdsComponentsRegistrationLifecycle 来在 Spring 启动的晚期阶段勾起对下面两个配置类的加载与主动配置工作。这个外面水很深(须要深刻了解 AbstractApplicationContext#refresh()办法和 ConfigurationClassPostProcessor 的工作机制)具体可见基于 ShardingSphere-JDBC 的示例

  • 基于 MybatisTiny 的示例

    MybatisTiny 是自己的一个与 MybatisPlus 相似的框架,基于 MybatisTiny 的示例见这里

  • 基于 MybatisPlus 的示例

    基于 MybatisPlus 的示例见这里

  • 示例所应用的数据库及表构造

    所有示例所应用的数据库均为 MySQL 数据库,表构造见这里

正文完
 0