项目中引入多数据源的配置方式

在日常开发的时候,我们经常会遇到这样的一个需求:需要查询另外的一个数据库的数据。要怎么操作呢?我们一般采取的是API调用的方式。最简单的办法是直接查询获取数据,在自己的系统 中进行相关操作,但是需要注意<font color="red">管控数据库用户账号权限</font>。

关于在一个项目中配置多数据源,首先我们得知道有两种方式:<font color="red">静态配置数据源</font>和<font color="red">动态配置数据源</font>。

静态配置数据源 vs 动态配置数据源:

静态配置数据源:

在项目启动时,所有数据源的信息(如数据库URL、用户名、密码等)就已经确定,并且在运行过程中不会改变。

动态配置数据源:

数据源可以在运行时<font color="red">根据业务逻辑动态加载和切换</font>,一个是动态加载,一个是动态切换。而不需要重启应用。数据源的配置信息可以来自数据库、配置中心或其他外部服务。

静态配置数据源示例:

@Configuration
@MapperScan(basePackages = "com.xuanyuanzi.mapper.*.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.ds1")
    @Primary
    public DataSource primaryDataSource() {
        return DruidDataSourceBuilder.create().build();
    } 
    
    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        MybatisConfiguration configuration = new MybatisConfiguration();
        // 开启驼峰命名
        configuration.setMapUnderscoreToCamelCase(false); 
        configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);
        bean.setConfiguration(configuration);
        bean.setPlugins(paginationInterceptor());
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        return bean.getObject();
    }
    
    @Bean(name = "primaryTransactionManager")
    @Primary
    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

包含的步骤有:数据源配置->SqlSessionFactory 配置->事务管理器配置。

我们都知道Mybatis是基于SqlSession 并执行 SQL 语句,所以上面的步骤就是为了构建SqlSessionFactory

SqlSession session = sqlSessionFactory.openSession()
String sql = "SELECT * FROM users WHERE id = ?";
User user = session.selectOne(sql, 1);
System.out.println(user);

需要注意的点:

<p style="color: red">在配置多数据源注入后,MybatisPlusConfig文件配置的分页插件会失效,需要在构建SqlSessionFactory时重新指定。</p>

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

SecondaryDataSourceConfig.java文件……。

动态配置数据源示例:

使用动态配置数据源的方式,就不得不提到<font color="red">数据源切换</font>的问题,需要保证上下文环境数据源能被正常切换。

实现数据源切换的关键代码:

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    
    public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

AbstractRoutingDataSource的getConnection->determineTargetDataSource->继承子类必须重写determineCurrentLookupKey,该方法返回一个对象(通常是字符串),作为选择数据源的键。

DataSourceContextHolder.getDataSource()这一步骤需要配合Threadlocal类,保存每个线程独有的变量,

为了避免手动:DATASOURCE_HOLDER.set(dataSourceName),需要:

添加一个注解,比如说DS,同时使用切面类DSAspect:

@Aspect
@Component
public class DSAspect {

    @Pointcut("@annotation(com.xuanyuanzi.annotation.DS)")
    public void dynamicDataSource() {
    }

    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)) {
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.removeDataSource();
        }
    }
}

有时候我们的业务会要求我们从保存有其他数据源的数据库表中添加这些数据源,然后再根据不同的情况切换这些数据源,即所谓的<font color="red">动态添加数据源</font>:

可以通过实现CommandLineRunner类,从数据库读取数据,塞到一个包含数据库连接信息的数组里面,如数据库URL、用户名、密码、连接驱动,存入map的key值……

public Boolean createDataSource(List<DataSourceEntity> dataSources) {
    try {
        if (!CollectionUtils.isEmpty(dataSources)) {
            for (DataSourceEntity ds : dataSources) {
                //校验数据库是否可以连接
                Class.forName(ds.getDriverClassName());
                DriverManager.getConnection(ds.getUrl(), ds.getUserName(), ds.getPassWord());
                //定义数据源
                DruidDataSource dataSource = new DruidDataSource();
                BeanUtils.copyProperties(ds, dataSource);
                //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
                dataSource.setTestOnBorrow(true);
                //建议配置为true,不影响性能,并且保证安全性。
                //申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
                dataSource.setTestWhileIdle(true);
                //用来检测连接是否有效的sql,要求是一个查询语句。
                dataSource.setValidationQuery("select   1   ");
                dataSource.init();
                this.targetDataSourceMap.put(ds.getKey(), dataSource);
            }
            super.setTargetDataSources(this.targetDataSourceMap);
            //   将TargetDataSources中的连接信息放入resolvedDataSources管理
            super.afterPropertiesSet();
            return Boolean.TRUE;
        }
    } catch (ClassNotFoundException | SQLException e) {
        log.error("---程序报错---:{}", e.getMessage());
    }
    return Boolean.FALSE;
}

这里问一个问题:为什么动态配置数据源可以公用一个mybatis的配置文件?

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

<strong>答:</strong>首先通过静态配置多数据源的方式每个数据源都有独立的配置,比如不同的包名和Bean名称导致重复的代码和配置,每个单独的数据源配置就是一个单独的Sqlsessionfactory

而通过动态配置多数据源的方式配置的DynamicDataSource 是一个代理类,它在运行时根据 determineCurrentLookupKey() 返回的标识(如 "db1"),从 targetDataSources 中获取真实的物理数据源。这样就导致了所有 Mapper 接口和 XML 文件都可以共享同一个 SqlSessionFactorySqlSessionTemplate,因为数据源的切换由动态代理实现,而非与固定组件绑定。

DynamicTableNameInnerInterceptor有啥用

另外一般我们使用baomidou的多数据源引入方式:

<!-- dynamic-datasource 多数据源-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>

结尾

giao!!!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容