Spring/SpringBoot实现配置多数据源和常见问题

   在做SpringBoot项目开发时,有时需要配置多个数据源。今天记录一下多数据源的配置。

1. 设置数据源枚举,通过枚举作为数据源Map的key获取数据源,实现动态切换数据源

实现代码:

/**
 * 描述:数据源枚举
 */
public enum DataSourceEnum {

    /**默认数据源**/
    DB_DEFAULT,
	
    /**另外的数据源**/
    DB_REPLICATION
}

2. 配置properties文件:配置多个数据源(备注:前缀可以自己定义)

#默认的数据源
spring.datasource.default.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.default.url=jdbc:mysql://数据源地址
spring.datasource.default.username=******
spring.datasource.default.password=******
spring.datasource.default.test-on-borrow=false
spring.datasource.default.test-while-idle=true
spring.datasource.default.time-between-eviction-runs-millis=3600000

#额外的数据源
spring.datasource.replication.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.replication.url=jdbc:mysql://数据源地址
spring.datasource.replication.username=******
spring.datasource.replication.password=******
spring.datasource.replication.test-on-borrow=false
spring.datasource.replication.test-while-idle=true
spring.datasource.replication.time-between-eviction-runs-millis=3600000

3. 数据源的存放、设置,获取

/**
 * 描述: 数据源的存放获取及设置
 */
public class DataSourceContextHolder {

    /**
     * 私有化
     */
    private DataSourceContextHolder() {
    }

    /**
     * 描述:数据源ThreadLocal实例-内部,使用枚举实现最为安全的单例模式,用于存放数据源
     */
    private enum InternalSourceEnum {

        /** 数据源 **/
        SINGTON_DATA_SOURCE;

        /** 数据源实例 */
        private final ThreadLocal<DataSourceEnum> currentDataSource;

        /**
         * 延迟实例化
         */
        InternalSourceEnum() {
            currentDataSource = new ThreadLocal<>();
        }

        /**
         * 获取数据源
         * 
         * @return 数据源
         */
        public ThreadLocal<DataSourceEnum> getInstance() {
            return currentDataSource;
        }
    }

    /**
     * 清除当前数据源,可防治内存泄露
     */
    public static void clear() {
        InternalSourceEnum.SINGTON_DATA_SOURCE.getInstance().remove();
    }

    /**
     * 获取当前使用的数据源枚举,在Controller进行切换
     * 
     * @return 当前使用数据源的
     */
    public static DataSourceEnum get() {
        return InternalSourceEnum.SINGTON_DATA_SOURCE.getInstance().get();
    }

    /**
     * 设置当前使用的数据源
     *
     * @param dataSourceEnum
     *            需要设置的数据源枚举
     */
    public static void set(DataSourceEnum dataSourceEnum) {
        InternalSourceEnum.SINGTON_DATA_SOURCE.getInstance().set(dataSourceEnum);
    }
}

5. 路由数据源

  说明:数据源设置好后,需要提供给框架获取该数据源的方法,此处通过继承Spring的AbstractRoutingDataSource(包:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource)类并实现该类中的determineCurrentLookupKey()方法来实现本功能。

/**
 * 描述:路由数据源  AbstractRoutingDataSource:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
 */
public class RoutingDataSource extends AbstractRoutingDataSource {

    /**
     * 路由
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();
    }
}

6. 配置多数据源

   本步骤是多数据源配置的最为关键的一步。当我们使用自己配置的数据源时,Spring默认的数据源就会失效,同时也需要自己写实现事务的bean。若我们在一个事务中进行多次的数据源切换操作,事务只会针对该事务中的第一个数据库操作对应的数据源生效。其次,使用自己实现的数据源时,一些MyBatis的插件也会失效,比如PageHelper插件,解决方法为:Spring/SpringBoot使用多数据源时,导致Mybatis插件PagerHelper失效问题解决方案

/**
 * 描述:数据源配置
 */
@MapperScan(basePackages = "com.xxxxxxx.dao")
@Configuration
public class DynamicDataSourceConfigure {

    /** LOG日志 */
    private static final Logger LOG = LoggerFactory.getLogger(DynamicDataSourceConfigure.class);

    /**
     * @ConfigurationProperties会帮忙配置好以prefix 值为前缀的属性
     * 
     * @return bean
     */
    @Bean(name = "defaultSource")
    @RefreshScope
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSource defaultSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * @return bean
     */
    @Bean(name = "replicateSource")
    @RefreshScope
    @ConfigurationProperties(prefix = "spring.datasource.replication")
    public DataSource replicateSource() {
        // org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder
        return DataSourceBuilder.create().build();
    }

    /**
     * 核心动态数据源
     *
     * @param defaultSource
     *            默认的
     * @param replicateSource
     *            其他的
     * @return 数据源bean
     * @throws Exception
     *             异常
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("defaultSource") DataSource defaultSource, @Qualifier("replicateSource") DataSource replicateSource) {

        RoutingDataSource dataSource = new RoutingDataSource();
		
		//设置默认的数据源
        dataSource.setDefaultTargetDataSource(defaultSource);

        Map<Object, Object> dataSourceMap = new HashMap<>(2);

        dataSourceMap.put(DataSourceEnum.DB_DEFAULT, defaultSource);
        dataSourceMap.put(DataSourceEnum.DB_REPLICATION, replicateSource);

		//将数据源放进RoutingDataSource ,最后利用get获取当前数据源
        dataSource.setTargetDataSources(dataSourceMap);

        return dataSource;
    }

    /**
     * sql工厂(通过名称来注入),形参注解为:org.springframework.beans.factory.annotation.Qualifier
     * 
     * @param defaultSource
     *            默认
     * @param replicateSource
     *            其他的数据源
     * @return bean
     * @throws Exception
     *             异常
     */
    @Bean(name = "sessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("defaultSource") DataSource defaultSource, @Qualifier("replicateSource") DataSource replicateSource)
            throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dynamicDataSource(defaultSource, replicateSource));

        // 配置xml文件的路径org.springframework.core.io.support.PathMatchingResourcePatternResolver
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*/*.xml"));
        // sqlSessionFactoryBean.setPlugins(plugins());
        org.apache.ibatis.session.Configuration configuration = sqlSessionFactoryBean.getObject().getConfiguration();

        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCallSettersOnNulls(true);

        return sqlSessionFactoryBean.getObject();
    }

    /* *//**
          * PageHelper插件失效的解决方案
          *//*
            @Bean(name = "plugins")
            public Interceptor[] plugins() {
            
             // org.apache.ibatis.plugin.Interceptor
             Interceptor interceptor = new PageInterceptor();
            
             // java.util.Properties
             Properties properties = new Properties();
            
             properties.setProperty("offsetAsPageNum", "true");
            
             // 数据库类型
             properties.setProperty("helperDialect", "mysql");
            
             // 查询行数,相当于MySQL中的count(*)
             properties.setProperty("rowBoundsWithCount", "true");
            
             // 是否分页合理化:即翻页小于0时,显示第一页数据,翻页数较大查不到数据时,显示最后一页的数据
             properties.setProperty("reasonable", "false");
            
             interceptor.setProperties(properties);
             // org.apache.ibatis.plugin.Interceptor
             Interceptor[] plugins = new Interceptor[Constant.NUMBER_1];
            
             plugins[Constant.NUMBER_0] = interceptor;
            
             return plugins;
            }
            */

    /**
     * sql模板
     * 
     * @param defaultSource
     *            默认的
     * @param replicateSource
     *            额外的数据源
     * @return bean
     * @throws Exception
     *             异常
     */
    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("defaultSource") DataSource defaultSource, @Qualifier("replicateSource") DataSource replicateSource)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory(defaultSource, replicateSource));
    }

    /**
     * 事务管理
     * 
     * @param defaultSource
     *           默认数据源
     * @param replicateSource
     *            第二个数据源
     * @return bean
     * @throws Exception
     *             异常
     */
    @Bean(name = "platformTransactionManager")
    public PlatformTransactionManager platformTransactionManager(@Qualifier("defaultSource") DataSource defaultSource,
            @Qualifier("replicateSource") DataSource replicateSource) {
        return new DataSourceTransactionManager(dynamicDataSource(defaultSource, replicateSource));
    }
}

7. 使用多数据源

  以上就是实现多数据源配置的所有步骤,下面就讲一下多数据源配置好后如何使用。

  • 如标题3:调用DataSourceContextHolder类的静态get方法,就可以获取当前的数据源;调用set方法就可以设置当前的数据源,调用clear方法就可以移除当前的数据源(备注:因为使用了ThreadLocal作为数据源的载体,鉴于ThreadLocal的特性,所以用完数据源之后必须要释放数据源,不然存在内存泄露的隐患)。
  • 若我们只想使用默认的数据源,则代码中不需要任何的切源操作。
  • 由于业务的特殊性,为了减少代码的侵入性,目前我使用的是利用拦截器判断前端的标记值进行动态的切源。若有兴趣,也可以实现用注解进行切源。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值