• MyBatis拦截器 Interceptor 实现多数据源切换


    实现思路

            对于无事务的数据库 DQL操作,检查其 sqlCommandType、是否使用了 for update、或者检查是否存在指定注解。

    前置知识

            AbstractRoutingDataSource

    http://t.csdn.cn/n68tvhttp://t.csdn.cn/n68tv数据源配置与切换

    1. public enum DataSources {
    2. // 主库-读数据源
    3. master,
    4. // 主库-写数据源
    5. slave,
    6. // 多数据源
    7. readmore
    8. }
    1. package com.gateway.admin.datasources;
    2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    3. import javax.sql.DataSource;
    4. import java.util.Map;
    5. /**
    6. * 数据源路由器
    7. */
    8. public class DataSourceRouter extends AbstractRoutingDataSource {
    9. // 也可以指定 ThreadLocal 的 initialValue 的具体实现
    10. private static final ThreadLocal contextHolder = new ThreadLocal<>();
    11. public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
    12. super.setDefaultTargetDataSource(defaultTargetDataSource);
    13. super.setTargetDataSources(targetDataSources);
    14. super.afterPropertiesSet();
    15. }
    16. /**
    17. *将targetDataSources映射中指定的给定查找键对象解析为用于与当前查找键匹配的实际查找键。
    18. */
    19. @Override
    20. protected Object determineCurrentLookupKey() {
    21. return contextHolder.get();
    22. }
    23. // 设置数据源,参数类型是上面生命的枚举
    24. public static void setDataSource(DataSources dataSource) {
    25. contextHolder.set(dataSource);
    26. }
    27. // 在 finally 中及时 remove
    28. public static void clearDataSource() {
    29. contextHolder.remove();
    30. }
    31. }

    1. package com.gateway.admin.datasources;
    2. import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    3. import org.springframework.boot.context.properties.ConfigurationProperties;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.springframework.context.annotation.Primary;
    7. import javax.sql.DataSource;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. /**
    11. * 多数据源配置类
    12. */
    13. @Configuration
    14. public class DynamicDataSourceConfig {
    15. //如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
    16. @Bean
    17. @ConfigurationProperties("spring.datasource.druid.first")
    18. public DataSource firstDataSource() {
    19. return DruidDataSourceBuilder.create().build();
    20. }
    21. @Bean
    22. @ConfigurationProperties("spring.datasource.druid.second")
    23. public DataSource secondDataSource() {
    24. return DruidDataSourceBuilder.create().build();
    25. }
    26. @Bean
    27. @ConfigurationProperties("spring.datasource.druid.three")
    28. public DataSource threeDataSource() {
    29. return DruidDataSourceBuilder.create().build();
    30. }
    31. @Bean
    32. @ConfigurationProperties("spring.datasource.druid.four")
    33. public DataSource fourDataSource() {
    34. return DruidDataSourceBuilder.create().build();
    35. }
    36. @Bean("dataSource")
    37. @Primary
    38. public DataSourceRouter dataSource(DataSource firstDataSource, DataSource secondDataSource, DataSource threeDataSource, DataSource fourDataSource) {
    39. Map targetDataSources = new HashMap<>();
    40. targetDataSources.put(DataSourceNames.master, master);
    41. targetDataSources.put(DataSourceNames.slave, slave);
    42. targetDataSources.put(DataSourceNames.readmore, readmore);
    43. // DataSourceNames.master, master 设置为默认数据源头
    44. return new DataSourceRouter(firstDataSource, targetDataSources);
    45. }
    46. }

    yml 配置

    1. spring:
    2. datasource:
    3. type: com.alibaba.druid.pool.DruidDataSource
    4. driverClassName: com.mysql.jdbc.Driver
    5. druid:
    6. first: #db1
    7. url: jdbc:mysql://127.0.0.1:3306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    8. username: root
    9. password: root
    10. second: #db2
    11. url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    12. username: root
    13. password: root
    14. three: #db3
    15. url: jdbc:mysql://127.0.0.1:3306/db3?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    16. username: root
    17. password: root

    具体实现

    annotation

    1. @Documented
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target(ElementType.METHOD)
    4. public @interface DataSource {
    5. DataSources value() default DataSources.master;
    6. }

     DynamicDataSourceInterceptor

    1. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    2. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    3. public class DynamicDataSourceInterceptor implements Interceptor {
    4. /**
    5. * 可以识别为 添加、更新、删除类型的 SQL 语句
    6. */
    7. public static final List UPDATE_SQL_LIST = Arrays.asList(SqlCommandType.INSERT, SqlCommandType.UPDATE, SqlCommandType.DELETE);
    8. /**
    9. * SQL 语句中出现的悲观锁标识
    10. */
    11. private static final String LOCK_KEYWORD = "for update";
    12. @Override
    13. public Object intercept(Invocation invocation) throws Throwable {
    14. // 通过 invocation 获取 MappedStatement 与 拦截方法的形参信息
    15. Object[] objects = invocation.getArgs();
    16. MappedStatement ms = (MappedStatement) objects[0];
    17. // 通过反射检查要执行的方法,如果标注了 @DataSource 则检查其 value
    18. String clazzStr = ms.getId().substring(0, ms.getId().lastIndexOf("."));
    19. String methodStr = ms.getId().substring(ms.getId().lastIndexOf(".") + 1);
    20. // 由于 mybatis 同一个接口方法不能重载
    21. Method[] mapperMethods = Class.forName(clazzStr).getMethods();
    22. Method targetMethod = null;
    23. for (Method mapperMethod : mapperMethods) {
    24. if (mapperMethod.getName().equals(methodStr)) {
    25. targetMethod = mapperMethod;
    26. break;
    27. }
    28. }
    29. DataSources dataSourceAnnotationValue = null;
    30. if (targetMethod != null && targetMethod.getAnnotation(DataSource.class) != null) {
    31. dataSourceAnnotationValue = targetMethod.getAnnotation(DataSource.class).value();
    32. }
    33. // 获取 sqlCommandType
    34. SqlCommandType sqlCommandType = ms.getSqlCommandType();
    35. // 获取 SQL
    36. BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
    37. String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]", " ");
    38. if (dataSourceAnnotationValue == DataSources.read && sqlCommandType.equals(SqlCommandType.SELECT)) {
    39. DataSourceTypeManager.set(DataSources.slave);
    40. } else if (dataSourceAnnotationValue == DataSources.write ||
    41. UPDATE_SQL_LIST.contains(sqlCommandType) ||
    42. sql.contains(LOCK_KEYWORD)) {
    43. DataSourceTypeManager.set(DataSources.slave);
    44. } else {
    45. DataSourceTypeManager.set(DataSources.readmore);
    46. }
    47. Object proceed;
    48. try {
    49. proceed = invocation.proceed();
    50. } catch (Throwable t) {
    51. throw t;
    52. } finally {
    53. DataSourceTypeManager.reset();
    54. }
    55. return proceed;
    56. }
    57. @Override
    58. public Object plugin(Object target) {
    59. if (target instanceof Executor) {
    60. return Plugin.wrap(target, this);
    61. } else {
    62. return target;
    63. }
    64. }
    65. @Override
    66. public void setProperties(Properties properties) {
    67. }
    68. }

    mybatis-config.xml

    1. .....
    2. <plugins>
    3. <plugin interceptor="interceptor.DynamicDataSourceInterceptor" />
    4. plugins>

    上盘硬菜,@Transaction源码深度解析 | Spring系列第48篇 (qq.com)

  • 相关阅读:
    selenium模块使用详解、打码平台使用、xpath使用、使用selenium爬取京东商品信息、scrapy框架介绍与安装
    如何开发一个求职招聘小程序?详细步骤解析与教程
    魔众题库系统 v7.5.0 连续增加题目,题目公开快捷操作
    DBeaver manual
    Codeforces Round 894 div3 题解 | JorbanS
    echarts-直角坐标系通用配置
    Quicker快速开发,简单的网页数据爬取(示例,获取天眼查指定公司基础工商数据)
    【软考】计算机指令系统寻址的几种方式及其速度的比较
    元宇宙:未来我们的每一个日常行为是否都能成为赚钱工具?
    Kubernetes v1.25 源码编译
  • 原文地址:https://blog.csdn.net/qq_34922830/article/details/127427867