zoukankan      html  css  js  c++  java
  • AbstractRoutingDataSource -- Spring提供的轻量级数据源切换方式

    参考RuoYi开源项目https://gitee.com/y_project/RuoYi.git 动态数据库

    AbstractRoutingDataSource -- Spring提供的轻量级数据源切换方式

    简单多数据源配置

    在一个普通Spring + Mybatis项目中,如果使用了多数据源,可以通过在dao层注入不同的SqlSessionTemplate来实现与不同数据库交互的目的。单个SqlSessionTemplate注入容器的过程如下:

        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
        <!-- 注册sqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="configLocation" value="classpath:config/mybatis-config.xml"/>
            <property name="typeAliasesPackage" value="com.wch.base.domain"/>
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        </bean>
    
        <!-- 配置dao接口扫描,配置sqlSessionTemplate -->
        <context:component-scan base-package="com.wch.base.mapper"/>
        <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg ref="sqlSessionFactory"/>
        </bean>
    

    如果项目需要连接多个数据源,从DataSource、SqlSessionFactory、SqlSessionTemplate都需要配置多次,难于维护和管理。

    AbstractRoutingDataSource

    AbstractRoutingDataSource是spring-jdbc包提供的一个了AbstractDataSource的抽象类,它实现了DataSource接口的用于获取数据库连接的方法。

        @Override
        public Connection getConnection() throws SQLException {
            return determineTargetDataSource().getConnection();
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return determineTargetDataSource().getConnection(username, password);
        }
    
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            // 获取指定数据源关键字
            Object lookupKey = determineCurrentLookupKey();
            DataSource dataSource = this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }
            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            }
            return dataSource;
        }
    
        public void setTargetDataSources(Map<Object, Object> targetDataSources) {
            this.targetDataSources = targetDataSources;
        }
    

    AbstractRoutingDataSource的内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连接。

    数据源动态切换

    AbstractRoutingDataSource提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关键字,路由到指定数据源,获取连接。

    数据源切换方法

    维护一个static变量datasourceContext用于记录每个线程需要使用的数据源关键字。并提供切换、读取、清除数据源配置信息的方法。

    @Slf4j
    public class DataSourceContextHolder {
    
        private static ThreadLocal<String> datasourceContext = new ThreadLocal<>();
    
        public static void switchDataSource(String datasource) {
            log.debug("switchDataSource: {}", datasource);
            datasourceContext.set(datasource);
        }
    
        public static String getDataSource() {
            return datasourceContext.get();
        }
    
        public static void clear() {
            datasourceContext.remove();
        }
    }
    
    实现AbstractRoutingDataSource
    public class RoutingDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDataSource();
        }
    }
    
    配置数据源信息
    # master datasource
    ds.financial.master.driverClassName=com.mysql.jdbc.Driver
    ds.financial.master.url=jdbc:mysql://127.0.0.1:3306/financial?useUnicode=true&characterEncoding=utf8&useSSL=true
    ds.financial.master.username=root
    ds.financial.master.password=
    ds.financial.master.type=com.alibaba.druid.pool.DruidDataSource
    
    # slave datasource
    ds.financial.slave.driverClassName=com.mysql.jdbc.Driver
    ds.financial.slave.url=jdbc:mysql://127.0.0.1:3306/financial_slave?useUnicode=true&characterEncoding=utf8&useSSL=true
    ds.financial.slave.username=root
    ds.financial.slave.password=
    ds.financial.slave.type=com.alibaba.druid.pool.DruidDataSource
    
    注入数据源
    @Configuration
    public class DataSourceConfig {
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "ds.financial.master")
        public DataSource financialMasterDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "ds.financial.slave")
        public DataSource financialSlaveDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "routingDataSource")
        public RoutingDataSource routingDataSource() {
            Map<Object, Object> dataSourceMap = new HashMap<>();
            dataSourceMap.put("financial-master", financialMasterDataSource());
            dataSourceMap.put("financial-slave", financialSlaveDataSource());
    
            RoutingDataSource routingDataSource = new RoutingDataSource();
            routingDataSource.setTargetDataSources(dataSourceMap);
            routingDataSource.setDefaultTargetDataSource(financialMasterDataSource());
            return routingDataSource;
        }
    }
    
    Mybatis配置
    @Configuration
    public class MyBatisConfig {
    
        @Resource(name = "routingDataSource")
        private RoutingDataSource routingDataSource;
    
        /**
         * routingDataSource sqlSessionFactory
         *
         * @return
         */
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(routingDataSource);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
            sqlSessionFactoryBean.setTypeAliasesPackage("com.wch.financial.domain");
            return sqlSessionFactoryBean.getObject();
        }
    
        /**
         * 注册 sqlSessionTemplate
         *
         * @param sqlSessionFactory
         * @return
         */
        @Bean
        public SqlSessionTemplate financialMasterSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    
    }
    
    关键字标记生命周期管理

    当一个线程执行某个dao方法时读取此方法配置的数据源关键字并写入datasourceContext,在此方法执行完成后清除datasourceContext中的标记。此生命周期使用aop实现。

    标记注解

    用于dao类或方法上。

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
    
        String name() default "";
    }
    
    标记注解的方法的切面处理
    @Aspect
    @Component
    public class HandleDatasourceAspect {
    
        @Pointcut("@annotation(com.wch.financial.config.DataSource)")
        public void pointcut() {
        }
    
        @Before("pointcut()")
        public void beforeExecute(JoinPoint joinPoint) {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            DataSource annotation = method.getAnnotation(DataSource.class);
            if (null == annotation) {
                annotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            }
            if (null != annotation) {
                // 切换数据源
                DataSourceContextHolder.switchDataSource(annotation.name());
            }
        }
    
        @After("pointcut()")
        public void afterExecute() {
            DataSourceContextHolder.clear();
        }
    }
    

    使用

    在dao实现类的类或方法上标注@DataSource注解。

    @Repository
    public class ProductDaoImpl implements ProductDao {
    
        @Resource
        private SqlSessionTemplate sst;
    
        @Override
        @DataSource(name = "financial-slave")
        public List<Product> selectProduct() {
            return sst.selectList("financial.product.selectProduct");
        }
    
    }


    作者:wch853
    链接:https://www.jianshu.com/p/b158476dd33c
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    poj 3068 Bridge Across Islands
    XidianOJ 1086 Flappy v8
    XidianOJ 1036 分配宝藏
    XidianOJ 1090 爬树的V8
    XidianOJ 1088 AK后的V8
    XidianOJ 1062 Black King Bar
    XidianOJ 1091 看Dota视频的V8
    XidianOJ 1098 突击数论前的xry111
    XidianOJ 1019 自然数的秘密
    XidianOJ 1109 Too Naive
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/12176030.html
Copyright © 2011-2022 走看看