# DBSelector **Repository Path**: sense7/dbselector ## Basic Information - **Project Name**: DBSelector - **Description**: 声明式数据库执行节点、规则,hint node strategy。 - **Primary Language**: Java - **License**: MIT - **Default Branch**: dev-1.0.1 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-08-19 - **Last Updated**: 2023-12-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: MySQL, SpringBoot, MyBatis ## README # 工程简介 #### DBSelector #### sql自动填充hint前缀 达到选择主从库或者指定节点的目的 # 参考文档 https://www.alibabacloud.com/help/zh/apsaradb-for-rds/latest/execute-hints-on-an-apsaradb-rds-for-mysql-instance # 环境依赖 * mybatis * springboot # 使用方法 ## demo工程 https://gitee.com/sense7/dbselector-demo ## 引入流程 ### 引入依赖 ```pom com.stanwind DBSelector 1.0.0-SNAPSHOT ``` #### 项目未传中央仓库,可以先git clone到本地后 mvn install到本地仓库后引入 ### XXApplication ```java @SpringBootApplication @EnableDBSelector @MapperScan("com.demo.mbdemo.dao") public class MbdemoApplication { public static void main(String[] args) { SpringApplication.run(MbdemoApplication.class, args); } } ``` ### method #### 从主库select ```java @DBNode(type = NodeType.MASTER) public void t1() { log.info("force master"); demoMapper.selectById(1L); } ``` #### 从指定节点 ```java @DBNode(type = NodeType.ASSIGN, node = "node1") public void t3() { log.info("assign node1"); mpDemoMapper.selectById(1L); } ``` #### 自定义节点路由 - 写法一 注解注入 ```java 无参数 @DBNode(type = NodeType.ASSIGN, router = DemoRouter.class) public void t333(@DBNodeParam Long id){ log.info("assign router id {}",id); mpDemoMapper.selectById(id); } 解析字段(如果写了prefix 取出的时候也要携带该前缀) @DBNode(type = NodeType.ASSIGN, router = DemoRouter.class) public void t333(@DBNodeParam(argPrefix="m_t333", keyField="id") UserVO user){ log.info("assign router id {}",user.getId()); mpDemoMapper.selectById(user.getId()); } ``` - 写法二 手动上下文传参 ```java public void t33() { //需要获取代理对象 DemoService service = context.getBean(DemoService.class); for (long i = 1; i < 5; i++) { //将自定义router需要的参数通过上下文传递 NodeContext.getContext().addObject(DemoPO.USER_ID, i); service.t333(i); } } @DBNode(type = NodeType.ASSIGN, router = DemoRouter.class) public void t333(Long id) { log.info("assign router id {}", id); mpDemoMapper.selectById(id); } ``` ```java public class DemoRouter implements NodeRouter { public static final List nodes = new ArrayList<>(); static { nodes.add("round_node1"); nodes.add("round_node2"); nodes.add("round_node3"); } @Override public String route() { //从上下文中获取传入的参数 Long id = NodeContext.getContext().getObject(DemoPO.USER_ID, Long.class); long index = id % nodes.size(); return nodes.get((int) index); } ``` # 原理 * 解析注解参数,转换成sql前缀 * 通过threadlocal传递到上下文 * 基于mybatis的Interceptor对sql进行更改填入前缀 # TODO * 跨线程留存上下文,执行完毕后清除。最开始考虑的inheritableThreadLocal,但是只能新建线程传递,对池化线程无效。考虑复写submit、execute方法修饰线程runable,callable方法对上下文进行拷贝。 # 延伸阅读 #### 使用MySQL命令行进行连接并使用Hint语句时,需要在命令中增加-c选项,否则Hint会被MySQL命令行工具过滤。

#### 支持通过/*FORCE_MASTER*/和/*FORCE_SLAVE*/指定在主实例或备实例执行查询命令。

## 说明

#### 因为Hint的路由优先级最高,例如Hint不受一致性、事务的约束,需要您评估是否可以用于业务。

#### Hint语句里不能包含改变环境变量的语句,例如/*FORCE_SLAVE*/ set names utf8; ,可能导致后续业务出错。 #### 支持通过/*force_node='<实例ID>'*/命令指定在某个实例执行查询命令。例如/*force_node='rr-bpxxxxx'*/ show processlist;,该show processlist;命令只在rr-bpxxxxx实例执行。如果这个实例发生故障,则返回报错force hint server node is not found, please check.。 #### 支持通过/*force_proxy_internal*/set force_node = '<实例ID>';命令永久指定在某个实例执行查询命令。例如/*force_proxy_internal*/set force_node = 'rr-bpxxxxx';,执行该命令后,后续所有命令只发往rr-bpxxxxx实例,如果这个实例发生故障,则返回报错set force node 'rr-bpxxxxx' is not found, please check.。 #### 说明 通常不建议使用/*force_proxy_internal*/语法,会导致后续所有请求都发往该实例,读写分离失效。