在做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的特性,所以用完数据源之后必须要释放数据源,不然存在内存泄露的隐患)。
- 若我们只想使用默认的数据源,则代码中不需要任何的切源操作。
- 由于业务的特殊性,为了减少代码的侵入性,目前我使用的是利用拦截器判断前端的标记值进行动态的切源。若有兴趣,也可以实现用注解进行切源。