这篇文章迭代了两次,代码可能有些偏差,核心代码全部在这里了。
以下是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();
}
}