springboot+mybatisplus实现多数据源

需求:动态从数据库加载数据源配置

框架:springboot2.5.4、mybatis-plus3.4、druid1.2.5、swagger2.9.2

核心代码:通过继承AbstractRoutingDataSource实现多数据源

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 线程局部变量,保存当前数据源信息
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    private volatile Map<Object, Object> customDataSources = new HashMap<>();

    @Value("${source.max-active:2}")
    private int maxActive;

    @Value("${source.initial-size:1}")
    private int initialSize;

    @Value("${source.min-idle:1}")
    private int minIdle;

    @Value("${source.max-wait:30000}")
    private int maxWait;

    @Value("${spring.datasource.druid.time-between-eviction-runs-millis:60000}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.min-evictable-idle-time-millis:300000}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.test-while-idle:true}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.test-on-borrow:false}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.test-on-return:false}")
    private boolean testOnReturn;

    @Value("${spring.datasource.druid.filters:stat}")
    private String filters;

    @Value("${source.break-after-acquire-failure:true}")
    private boolean breakAfterAcquireFailure;

    @Value("${source.connection-error-retry-attempts:0}")
    private int connectionErrorRetryAttempts;

    /**
     *  配置数据源
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources 目标数据源
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> param) {
        super.setTargetDataSources(param);
        this.customDataSources = param;
    }

    @Override
    public Object determineCurrentLookupKey() {
        String dataSourceId = CONTEXT_HOLDER.get();
        if(StringUtils.isNotBlank(dataSourceId)) {
            Map<Object, Object> map = this.customDataSources;
            if(map.containsKey(dataSourceId)) {
                log.info("当前数据源是:{}", dataSourceId);
            } else {
                log.info("不存在数据源:{}", dataSourceId);
                return null;
            }
        } else {
            log.info("当前是默认数据源");
        }
        return dataSourceId;
    }

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public void checkCreateDataSource(Repository repository) {
        clearDataSource();
        String dataSourceId = repository.getRepositoryId();
        Map<Object, Object> map = this.customDataSources;
        setDataSource(repository.getRepositoryId());
        if(map.containsKey(dataSourceId)) {
            DruidDataSource druidDataSource = (DruidDataSource)map.get(dataSourceId);
            //连接是否有效
            boolean isAvailable  = true;
            DruidPooledConnection connection = null;
            try {
                connection = druidDataSource.getConnection();
            } catch (SQLException sqlException) {
                //抛出异常,则说明连接失效。删除链接
                log.error(sqlException.getMessage());
                isAvailable = false;
                deleteDataSource(dataSourceId);
            } finally {
                if(null != connection) {
                    try{
                        connection.close();
                    } catch (SQLException sqlException) {
                        log.error(sqlException.getMessage());
                    }
                }
            }
            if(!isAvailable) {
                createDataSource(repository);
            }
        } else {
            createDataSource(repository);
        }
    }

    /**
     * 创建数据库连接
     * @param repository
     */
    private void createDataSource(Repository repository) {
        String jdbcUrl = repository.getDbURL();
        String username = repository.getDatabaseUsername();
        //密码解密
        String password = repository.getDatabasePassword();//MD5Utils.convertMD5(repository.getDatabasePassword());
        String dirverName = repository.getDriverName();
        DruidDataSource instance = new DruidDataSource();
        if (StringUtils.isEmpty(dirverName)) {
            logger.error("Driver Class Not null: DbId=" + repository.getRepositoryId());
            return;
        } else {
            instance.setDriverClassName(dirverName);
        }
        instance.setName(repository.getRepositoryId());
        instance.setUrl(jdbcUrl.trim());
        instance.setUsername(username);
        instance.setPassword(password);
        instance.setInitialSize(initialSize);
        instance.setMinIdle(minIdle);
        instance.setMaxActive(maxActive);
        instance.setMaxWait(maxWait);
        instance.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        instance.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        instance.setTestWhileIdle(false);
        instance.setTestOnBorrow(testOnBorrow);
        instance.setTestOnReturn(testOnReturn);
        instance.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
        instance.setBreakAfterAcquireFailure(breakAfterAcquireFailure);
        try {
            instance.init();
            this.customDataSources.put(repository.getRepositoryId(), instance);
            // 将map赋值给父类的TargetDataSources
            setTargetDataSources(this.customDataSources);
            // 将TargetDataSources中的连接信息放入resolvedDataSources管理
            super.afterPropertiesSet();
        } catch (Exception e) {
            logger.error("Exception during pool initialization", e);
            return;
        }
    }

    /**
     * 删除无效的数据库连接
     * @param dataSourceId
     */
    private void deleteDataSource(String dataSourceId) {
        Map<Object, Object> map = this.customDataSources;
        Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
        for (DruidDataSource dataSource : druidDataSourceInstances) {
            if (dataSourceId.equals(dataSource.getName())) {
                map.remove(dataSourceId);
                //从实例中移除当前dataSource
                DruidDataSourceStatManager.removeDataSource(dataSource);
                // 将map赋值给父类的TargetDataSources
                setTargetDataSources(map);
                // 将TargetDataSources中的连接信息放入resolvedDataSources管理
                super.afterPropertiesSet();
            }
        }
    }

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }


    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

    public DataSource getCurrentDataSource() {
        return (DataSource)this.customDataSources.get(CONTEXT_HOLDER.get());
    }

}
public class DynamicDataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource masterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
}
server:
  port: 8080
spring:
  springmvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT&useOldAliasMetadataBehavior=true
    username: root
    password: root
    druid:
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'X' FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        allow:
        deny:
        login-username: admin
        login-password: admin
        reset-enable: false
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true
      use-global-data-source-stat: true
      filters: stat,wall
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

logging:
  level:
    com.example.demo: debug

#server.servlet.context-path=/demo

代码地址:demo: springboot + mybatisplus多数据源demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值