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

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数据库,表构造见这里

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理