当前位置: 首页 > 科技观察

支持监听SQL、感知事务状态、回溯数据源的动态数据源框架

时间:2023-03-18 22:23:55 科技观察

项目。在easymulti-datasource-spring-boot-starter之后,作者开发了hotkit-r2dbc。两个项目都支持动态数据源Switch,前者支持mybatis框架,后者支持响应式编程spring-data-r2dbc框架。既然都是ORM框架,不如合并成一个项目维护。GitHub上原来的easymulti-datasource-spring-boot-starter项目更名为easymulti-datasource,原来的easymulti-datasource-spring-boot-starter模块更名为easymulti-datasource-mybatis,版本号从3.0开始.1.新版本增加了easymulti-datasource-r2dbc(即原来的hotkit-r2dbc)。项目背景多数据源的动态切换似乎已经成为微服务的标配。做了那么多项目,发现每个项目都要配动态数据源,写个section来实现动态切换。因此,我将这些繁琐的配置打包为一个starter,准备使用。easymulti-datasource两个模块:easymulti-datasource-mybatis(原easymulti-datasource-spring-boot-starter)easymulti-datasource-r2dbc(原hotkit-r2dbc)easymulti-datasource-mybatismybatis版多数据源框架,提供declarative和Programmatic动态切换数据源功能。easymulti-datasource-mybatis自动集成mybatis-plus,提供两种动态多数据源模式,即主从数据源模式和非主从多数据源模式。每个数据源使用独立的连接池配置,可以为每个数据源单独配置连接池。支持多数据源的动态切换并不是easymulti-datasource-mybatis框架最大的亮点。easymulti-datasource-mybatis区别于其他动态数据源切换框架的主要特点如下:支持监听SQL,监听修改某个表的某些字段的sql,用于实现埋点事件;支持事务状态监听,注册事务监听器,用于完成事务回滚/提交时的一些后台操作;详细用法请参考wiki。在依赖配置maven中使用:com.github.wujiuyeeasymulti-datasource-mybatis${version}旧版本用于:com.github.wujiuyeeasymulti-datasource-spring-boot-starter${version}版本选择注释如下图所示。动态切换数据源使用注解切换数据源:@EasyMutiDataSource;使用API切换数据源:DataSourceContextHolder#setDataSource。在AOP中注册事务监听器,并在应用配置文件中开启跟踪事务方法调用链接的开关。配置如下。##监控交易方法调用链接easymuti:transaction:open-chain:true定义切面,拦截Mapper方法,在surround方法中实现更新缓存的逻辑。代码如下。TransactionInvokeContext.currentExistTransaction:判断当前调用链接是否有事务;TransactionInvokeContext.addCurrentTransactionMethodPopListener:为当前事务绑定一个监听器(PopTr??ansactionListener),当事务提交或回滚时会调用该监听器。如上代码所示,第一步是判断当前调用链路是否有事务。如果是,则在当前事务中注入监听器,监听器将完成缓存更新逻辑。如果没有事务,目标方法执行完成后,没有抛出异常时执行更新缓存逻辑。监听SQLeasymulti-datasource-mybatis支持sql埋点监控功能,支持监控事务状态。如果事务中存在当前执行的sql,事务提交后会回调sql监听器。第一步:开启sql埋点监控功能,开启交易调用链路跟踪功能。easymuti:transaction:open-chain:truesql-watcher:enable:true第二步:写观察者,可以有n个以上,多个观察者也可以观察同一张表甚至同一个字段。@Component@Slf4jpublicclassTestTableFieldObserverimplementsTableFieldObserver,InitializingBean{@OverridepublicSetobserveMetadatas(){//这里注册监控哪些表的哪些字段}/***监控sql时同步调用**@paramcommandType事件类型*@parammatchResult匹配ITEM*@returnreturnsasynchronousconsumer*/@OverridepublicAsyncConsumerobserve(CommandTypecommandType,MatchItemmatchResult){//同步消费//这里是sql执行前,可以在sql执行前做一些事情,比如新旧数据的对比,这里findouttheoldData//异步消费,当sql执行完成,或者事务方法执行完成(如果有事务),完成是指:正常执行完成或者异常退出方法returnthrowable->{//sql执行抛出异常不处理if(throwable!=null){return;}//消费事件//...};}}observe方法在监控sql时同步调用,该方法返回的AsyncConsumer在事务提交后回调调用。如果事务被回滚,则不会被调用。如果调用环节出现多个事务,根据事务传播机制,所有注册在该事务上的AsyncConsumers只有在当前方法所在的事务提交时才会被回调。easymulti-datasource-r2dbcspring-data-r2dbcversion用于响应式编程的多数据源组件。easymulti-datasource-r2dbc为spring-data-r2dbc实现了一个动态路由接口,并为响应式编程提供了在声明式和编程式多数据源之间动态切换的支持。它还支持两种多数据源模式,涵盖了常见的多数据源使用场景,即主从模式和集群模式。集群模式最多支持配置3个数据源,而主从模式支持一主一从。添加依赖和配置数据源使用easymulti-datasource-r2dbc后,项目中不需要添加spring-boot-starter-data-r2dbc依赖,也不需要添加spring-data-r2dbc依赖。easymulti-datasource-r2dbc的版本号对应spring-data-r2dbc的版本号:easymulti-datasource-r2dbcspring-data-r2dbc3.0.1-RELEASE1.1.0.RELEASE添加easymulti-datasource-r2dbc的依赖到项目中,如下。com.github.wujiuyeeasymulti-datasource-r2dbc${version}此时只需要addadditional你只需要依赖你想要的数据库类型对应的驱动,比如添加mysql的r2dbc驱动。dev.mikur2dbc-mysql0.8.2.RELEASE如果使用主从模式,使用以下配置。easymuti:database:r2dbc:master-slave-mode:master:url:r2dbc:mysql://127.0.0.1:3306/r2dbc_stuusername:rootpassword:pool:max-size:5idel-timeout:60slave:url:r2dbc:mysql://127.0.0.1:3306/r2dbc_stuusername:rootpassword:pool:max-size:5idel-timeout:60master会被设置为默认使用的数据源,如果有slave,配置一下,如果没有,可以为空.虽然slave是允许为空的,但是如果实在不需要多数据源的话就没有必要使用easymulti-datasource-r2dbc了。如果使用集群模式,请使用以下配置。easymuti:database:r2dbc:cluster-mode:first:url:r2dbc:mysql://127.0.0.1:3306/r2dbc_stuusername:rootpassword:pool:max-size:5idel-timeout:60second:url:r2dbc:mysql://127.0.0.1:3306/r2dbc_stuusername:rootpassword:pool:max-size:5idel-timeout:60third:url:r2dbc:mysql://127.0.0.1:3306/r2dbc_stuusername:rootpassword:pool:max-size:5idel-timeout:60其中,first将设置为默认数据源,second和third可以为空。声明式动态切换数据源声明式动态切换数据源是指使用注解来动态切换数据源。只需要在springbean的公共方法或类中添加@R2dbcDataBase注解,指定注解的value属性作为使用的数据源即可。示例代码如下。@ServicepublicclassPersonService{@ResourceprivatePersonRepositorypersonRepository;//方法返回值类型为Mono测试@R2dbcDataBase(MasterSlaveMode.Master)@Transactional(rollbackFor=Throwable.class)publicMonoaddPerson(Person...persons){MonotxOp=null;for(Personperson:persons){if(txOp==null){txOp=personRepository.insertPerson(person.getId(),person.getName(),person.getAge());}else{txOp=txOp.then(personRepository.insertPerson(person.getId(),person.getName(),person.getAge()));}}returntxOp;}//方法返回值类型为Fluxtest@R2dbcDataBase(MasterSlaveMode.Master)@Transactional(rollbackFor=Throwable.class)publicFluxaddPersons(Fluxpersons){returnpersons.flatMap(person->personRepository.insertPerson(person.getId(),person.getName(),person.getAge()));}}如果是主从模式,@R2dbcDataBase注解的value属性的可选值见MasterSlaveMode接口声明的常量;如果是Cluster模式,@R2dbcDataBase注解的value属性的可选值见ClusterMode接口声明的常量;以编程方式动态切换数据源声明式切换数据源的实现是依赖于以编程方式切换数据源来实现的,所以我们也可以直接写代码切换数据源不用将方法改成public暴露出来,只需要调用EasyMutiR2dbcRoutingConnectionFactory提供的静态方法putDataSource来写入Context使用的数据源,代码如下。publicclassRoutingTestextendsSupporSpringBootTest{@ResourceprivateDatabaseClientclient;@ResourceprivateReactiveTransactionManagerreactiveTransactionManager;@Testpublicvoidtest()throwsInterruptedException{TransactionalOperatoroperator=TransactionalOperator.create(reactiveTransactionManager);MonoatomicOperation=client.execute("插入人(id,name,age)VALUES,(:id,::age)").bind("id","joe").bind("name","Joe").bind("age",34).fetch().rowsUpdated().then(client.execute("INSERTINTOperson(id,name)VALUES(:id,:name)").bind("id","joe").bind("name","Joe").fetch().rowsUpdated())。then();//封装事务MonotxOperation=operator.transactional(atomicOperation);//封装切换数据源EasyMutiR2dbcRoutingConnectionFactory.putDataSource(txOperation,MasterSlaveMode.Slave).subscribe();TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);}}需要注意的是,如果需要使用事务,必须先调用TransactheoryOperator对象的transactional方法,然后调用EasyMutiR2dbcRoutingConnectionFactory的putDataSource方法转载本文,请联系Java美术公众号。