springboot + Mybatis Plus配置多数据源+AOP动态切换

这篇文章迭代了两次,代码可能有些偏差,核心代码全部在这里了。
以下是application.yml 配置文件配置:

datasource:
      druid:
         test1:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: url
              username: username
              password: password
              max-idle: 10
              max-wait: 10000
              max-active: 30
              min-idle: 1
              initial-size: 1
              validation-query: SELECT 'x'
              test-on-borrow: false
              test-on-return: false
              test-while-idle: true
              time-between-eviction-runs-millis: 60000 # 注意控制连接数据库的时间,不然会断开连接
              min-evictable-idle-time-millis: 300000
              pool-prepared-statements: true
              filters: stat
          test2:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: url
              username: username
              password:  password
              ......

配置文件对应的数据实体类,我这里使用了lombok的@Data注解,如果不使用,需要写 get/set方法
这个是数据源配置的基类

@Data
public class BaseDataSourceProperties {
    protected String url;

    protected String username;

    protected String password;

    protected String driverClassName;

    protected int initialSize;

    protected int minIdle;

    protected int maxActive;

    protected int maxWait;

    protected int timeBetweenEvictionRunsMillis;

    protected int minEvictableIdleTimeMillis;

    protected String validationQuery;

    protected boolean testWhileIdle;

    protected boolean testOnBorrow;

    protected boolean testOnReturn;

    protected boolean poolPreparedStatements;

    protected String filters;
}

因为是多数据源,所以我这里是每一个数据源对应了一个子类(如果数据源属性一样也可不用写其子类)
以下为其中之一

public class FinanceDruidDataSourceProperties extends BaseDataSourceProperties {
	//对应配置文件里的配置键
	public final static String DS = "datasource.druid.test1";


}

加载配置文件内容到配置类
@ConfigurationProperties的prefix 要对应配置文件application.yml的前缀

@Configuration
public class DataSourcePropertiesFactory {

    @Bean("Finance")
    @ConfigurationProperties(prefix = FinanceDruidDataSourceProperties.DS, ignoreUnknownFields = true, ignoreInvalidFields = true)
    public BaseDataSourceProperties getFinanceDataSourceInstance(){
        BaseDataSourceProperties mp = new FinanceDruidDataSourceProperties();
        return mp;
    }


    @Bean("OrgV2")
    @ConfigurationProperties(prefix = OrgV2DruidDataSourceProperties.DS, ignoreUnknownFields = true, ignoreInvalidFields = true)
    public BaseDataSourceProperties getOrgV2DataSourceInstance(){
        BaseDataSourceProperties mp = new OrgV2DruidDataSourceProperties();
        return mp;
    }


}

使用配置类初始化mybatils配置
因为初始化mybatils配置过程大多数都是一致的,所以封装了一些方法到基类中

@Component
@Slf4j
public class BaseMyBatisDataSource {

    public DataSource duridDataSource(BaseDataSourceProperties config) {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setDriverClassName(config.getDriverClassName());
        datasource.setUrl(config.getUrl());
        datasource.setUsername(config.getUsername());
        datasource.setPassword(config.getPassword());
        datasource.setInitialSize(config.getInitialSize());
        datasource.setMinIdle(config.getMinIdle());
        datasource.setMaxActive(config.getMaxActive());
        datasource.setMaxWait(config.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(config.getValidationQuery());
        datasource.setTestWhileIdle(config.isTestWhileIdle());
        datasource.setTestOnBorrow(config.isTestOnBorrow());
        datasource.setTestOnReturn(config.isTestOnReturn());
        datasource.setPoolPreparedStatements(config.isPoolPreparedStatements());
        try {
            datasource.setFilters(config.getFilters());
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        return datasource;
    }

}

注意啦,下面是重点!!!!这里将告诉你如何实现AOP动态数据源

下面AbstractRoutingDataSource是重点是spring提供的多数据源类

/**
 * AbstractRoutingDataSource 是由spring提供的类 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上,
 * 不用每个数据源都去创建一次sqlSessionFactory。
 * sqlSessionFactory 顾名思义 ,就是用来创建session会话的工厂。如果存在多个Sessionfactory 那么Session是不是就乱套了,因此这种架构不可取。
 *
 * determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,
 * 有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
 *
 */

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为{}", DataSourceContextHolder.getDB());

        return DataSourceContextHolder.getDB();
    }

}

使用 ThreadLocal 保存 DataSources 数据源的key

/**
 * 使用 ThreadLocal 保存 DataSources 数据源的key
 */
public class DataSourceContextHolder {

    public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);

    /**
     * 默认数据源
     */
    public static final String DEFAULT_DS = "mysqlIot";
    /**
     * sqlserver数据源
     */
    public static final String DS_SQLSERVER = "sqlServerIot";
    /**
     * asset数据源
     */
    public static final String DS_ASSET = "mysqlAsset";

    /**
     * 本地线程
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    static {
        contextHolder.set(DEFAULT_DS);
    }

    // 设置数据源名
    public static void setDB(String dbType) {
        log.debug("切换到{}数据源", dbType);
        contextHolder.set(dbType);
    }

    // 获取数据源名
    public static String getDB() {
        return (contextHolder.get());
    }

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }

    /**
     * 判断当前数据源key是否存在
     * @param key
     * @return
     */
    public static boolean isDataSourceKey (String key) {
        String [] keys = {DEFAULT_DS,DS_SQLSERVER,DS_ASSET};
        for (int i = 0 ; i< keys.length;i ++) {
            if (keys[i].equals(key)) {
                return  true;
            }
        }
        return  false;
    }

 
}

下面是动态数据源如何在mybatisplus配置中使用,
动态数据源配置需要把 {分页插件 , 扫描包的位置 ,xmlsql文件的位置,数据源} 等等 需要什么功能都需要在此重新注入

@Configuration
@MapperScan(basePackages = {"com.ceway.finance.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory1") //mybaits mapper 搜索路径
@EnableTransactionManagement
public class FinanceMybatisDataSource extends BaseMyBatisDataSource{

	@Autowired
	@Qualifier("mysqlIot")
	private BaseDataSourceProperties mysqlIot;

	@Autowired
	@Qualifier("mysqlAsset")
	private BaseDataSourceProperties mysqlAsset;

	@Autowired
	@Qualifier("sqlServerIot")
	private BaseDataSourceProperties sqlServerIot;


/**
	 * 动态数据源配置  这里是重点--------------------------------------------------------------------------------------
	 * @return
	 */
	@Bean
	public DataSource getDataSource() {
		DataSource mysqlAssetDataSource = super.getDataSource(mysqlAsset);
		DataSource mysqlIotDataSource = super.getDataSource(mysqlIot);
		DataSource sqlServerIotDataSource = super.getDataSource(sqlServerIot);

		// 此类继承了spring的 AbstractRoutingDataSource 类 代替原有的
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		// 默认数据源
		dynamicDataSource.setDefaultTargetDataSource(mysqlIotDataSource);

		// 配置多数据源
		Map<Object, Object> dsMap = new ConcurrentHashMap<>(5);
		dsMap.put(DataSourceContextHolder.DEFAULT_DS, mysqlIotDataSource);
		dsMap.put(DataSourceContextHolder.DS_SQLSERVER, sqlServerIotDataSource);
		dsMap.put(DataSourceContextHolder.DS_ASSET, mysqlAssetDataSource);

		dynamicDataSource.setTargetDataSources(dsMap);
		
		return dynamicDataSource;

	}


	@Bean
	public PlatformTransactionManager transactionManager() {
		return new DataSourceTransactionManager(getDataSource());
	}
	
	/**
	 * 分页插件
	 */
	@Bean
	public PaginationInterceptor paginationInterceptor() {
		PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
	    return paginationInterceptor;
	}


	@Bean
	public SqlSessionFactory sqlSessionFactory1() throws Exception {
		MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); // 使用MybatisSqlSessionFactoryBean
		factoryBean.setDataSource(getDataSource()); // 使用数据源, 连接库
		// 下面一步很重要,配置mapper。xml的扫描位置
		factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping-finance/*.xml"));
		// 配置扫描包的位置
		factoryBean.setTypeAliasesPackage("com.ceway.finance.mapper");
		// 重新注入分页插件
		factoryBean.setPlugins(new Interceptor[]{paginationInterceptor()});
		return factoryBean.getObject();

	}

	@Bean
	public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
		SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
		return template;
	}
}

以下是注解内容

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default DataSourceContextHolder.DEFAULT_DS;
}

以下aop的内容,前置切入 切换数据源,后置恢复默认数据源

@Aspect
@Component
@Slf4j
public class DynamicDataSourceAop {
    @Before("@annotation(dataSource)")
    public void beforeSwitchDS(JoinPoint point,DataSource dataSource){
        String dataSourceKey = dataSource.name();
        log.info("切换数据源至={}",dataSourceKey);
        if (DataSourceContextHolder.isDataSourceKey(dataSourceKey)) {
            // 切换数据源
            DataSourceContextHolder.setDB(dataSourceKey);
        }else {
            throw new RuntimeException("数据源key书写错误");
        }



    }


    @After("@annotation(dataSource)")
    public void afterSwitchDS(JoinPoint point,DataSource dataSource){
        String dataSourceKey = dataSource.name();
        log.info("切换数据源至={}",dataSourceKey);
        if (DataSourceContextHolder.isDataSourceKey(dataSourceKey)) {
            // 切换数据源
            DataSourceContextHolder.setDB(DataSourceContextHolder.DEFAULT_DS);
        }else {
            throw new RuntimeException("数据源key书写错误");
        }

    }

下面是aop的使用

@Service
public class DiotServiceImpl extends ServiceImpl<DiotMapper, Diot> implements IDiotService {

    @Autowired
    private DiotMapper diotMapper;

    @Override
    @DataSource(name = DataSourceContextHolder.DS_SQLSERVER)   // 切换成你需要的数据源,使用完后后置aop换自动切换为默认的数据源
    public List<Diot> selectList() {
        return diotMapper.selectTest();
    }
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值