mybatis-plus实现动态数据源

从数据库读取数据源信息,然后实现动态切换数据源

实现的步骤

1 创建数据源实体类
2 获取数据源缓存。读取数据库,获取数据源信息,生成druidDataSource实体,放入Map中 key是数据源名称,value是对应数据源druidDataSource实体
3 明确本线程的数据源 创建线程本地变量ThreadLocal 存放线程的key
4 创建db注解 放在需要的方法上 例如 @Db(“master”)
5 切库 使用Aop在方法执行前通过ThreaLocal的key,替换注解的value,实现切库。

1 创建数据源实体类

    @ApiModelProperty("注册在spring中bean名字")
      @TableId("dbname")
    private String dbname;

    @ApiModelProperty("数据库IP")
    @TableField("databaseip")
    private String databaseip;

    @ApiModelProperty("数据库端口")
    @TableField("databaseport")
    private String databaseport;

    @ApiModelProperty("数据库名称")
    @TableField("databasename")
    private String databasename;

    @ApiModelProperty("数据库用户名")
    @TableField("username")
    private String username;

    @ApiModelProperty("数据库密码")
    @TableField("password")
    private String password;

2 获取数据源缓存。读取数据库,获取数据源信息,生成如druidDataSource的实体,放入Map中 key是数据源名称,value是对应数据源druidDataSource实体

@Component
@Slf4j
public class DataSourceCache implements InitializingBean {
    public static final Map<String, DataSource> DATA_SOURCE_MAP = new HashMap<>();

    @Autowired
    GlobalDatasourceEntityMapper globalDatasourceEntityMapper;
    @Autowired
    DynamicDataSource dynamicDataSource;
    @Override
    public void afterPropertiesSet() throws Exception {
        initDataSourceCache();
    }
// 初始化数据源
    public void initDataSourceCache() {
        try {
            LambdaQueryWrapper<GlobalDatasourceEntity> queryWrapper = new LambdaQueryWrapper<GlobalDatasourceEntity>()
                    .isNotNull(GlobalDatasourceEntity::getDbname)
                    .isNotNull(GlobalDatasourceEntity::getDatabaseip)
                    .isNotNull(GlobalDatasourceEntity::getDatabasename)
                    .isNotNull(GlobalDatasourceEntity::getDatabaseport)
                    .isNotNull(GlobalDatasourceEntity::getUsername)
                    .isNotNull(GlobalDatasourceEntity::getPassword);
            List<GlobalDatasourceEntity> globalDatasourceEntities = globalDatasourceEntityMapper.selectList(queryWrapper);
            globalDatasourceEntities.forEach(globalDatasourceEntity -> {
                String url = "jdbc:mysql://"+globalDatasourceEntity.getDatabaseip()+":"+globalDatasourceEntity.getDatabaseport()+"/"+globalDatasourceEntity.getDatabasename()+"?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true";
                DruidDataSource druidDataSource = new DruidDataSource();
                druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
                druidDataSource.setUrl(url);
                druidDataSource.setUsername(globalDatasourceEntity.getUsername());
                druidDataSource.setPassword(globalDatasourceEntity.getPassword());
                DATA_SOURCE_MAP.put(globalDatasourceEntity.getDbname(),druidDataSource);
            });
            setTargetDataSources(DATA_SOURCE_MAP);
            log.info("dataSource.size = "+DATA_SOURCE_MAP.entrySet().size());
        }catch (Exception e) {
            log.error("init dataSource error: {}",e.getMessage());
        }
    }
    private void setTargetDataSources(Map<String, DataSource> dataSourceMap) {
        try {
            //将当前的数据源与数据源名称绑定
            HashMap<Object, Object> targetDataSources = new HashMap<>();
            for (Map.Entry<String, DataSource> entry : DATA_SOURCE_MAP.entrySet()) {
                targetDataSources.put(entry.getKey(), entry.getValue());
            }
            dynamicDataSource.setTargetDataSources(targetDataSources);
        }catch (Exception e) {
            e.printStackTrace();
            log.error("绑定数据源出错 " + e.getMessage());
        }

    }
}

实现抽象路由数据源 重写方法setTargetDataSources方法

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();//实现切换
    }
//  数据源放入
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        // 同步到resolvedDataSources中
        super.afterPropertiesSet();
    }
}

将DynamicDataSource 存入spring中

@Configuration
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource")//读取配置文件中的数据源信息当作默认数据源
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .type(DruidDataSource.class)
                .build();
    }
    /**
     * 动态数据源
     */
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(Environment env) {
        DynamicDataSource dynamicRoutingDataSource = new DynamicDataSource();
        dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource());
        dynamicRoutingDataSource.setTargetDataSources(new HashMap<>());
        return dynamicRoutingDataSource;
    }
}

3 明确本线程的数据源 创建线程本地变量ThreadLocal 存放线程的key

public class DataSourceContextHolder {
//     存放数据源的key
    private static ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void set(String dbType) {
        contextHolder.set(dbType);
    }

    public static String get() {
        return contextHolder.get();
    }

    public static void clear() {
        contextHolder.remove();
    }
}

4 创建注解 放在需要的方法上 例如 @Db(“master”)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Db {
    String value() default "masterDataSource";
}

    @Db("shuiyi")
    public Result getDataAllForTime(String dbname,Integer num) {
		....
	}
    

5 切库 使用Aop在方法执行前通过ThreaLocal的key,替换注解的value,实现切库。

@Component
@Aspect
public class DbAop {
    @Around("@annotation(db)")
    public Object around(ProceedingJoinPoint point, Db db) throws Throwable {
        try {
            Object[] args = point.getArgs();
            if(args.length > 0) {
                String dbname = (String)args[0];
                DataSourceContextHolder.set(dbname);
            }
            return point.proceed();
        } finally {
            DataSourceContextHolder.clear();
        }
    }
}

ok

原理

分两步:装载数据源,切换数据源
1 设置多数据源:将数据源信息存入数据库,实现InitializingBean,重写afterPropertiesSet方法,在方法中调用AbstractRoutingDataSource类(2继承了这个类)中setTargetDataSource方法设置多数据源。

2 继承AbstractRoutingDataSource类,然后重写determineCurrentLookupKey(确定当前关键字)方法。返回对应数据库设置的key,这个key放在TreadLocal中,每一个线程都可以使用不同的数据源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值