【springboot】实现动态数据源(在线切换、新增、删除、编辑)的方案,拒绝多数据源硬编码方案
想要实现灵活的动态数据源,我们肯定不能如传统动态数据源一样将数据源配置文件中,更加灵活的方式是基于数据库中数据源配置的持久化。
·
1. 前言
思路:想要实现灵活的动态数据源,我们肯定不能如传统动态数据源一样将数据源配置文件中,更加灵活的方式是基于数据库中数据源配置的持久化。
需改进点:在 2.7. DBChangeService
中获取所有数据源列表,并从数据源列表中过滤出目标数据源源的过程存在性能损耗,性能损耗点在于 获取数据源列表
的过程,我本地实际是从数据源查询的,更合理的方式应该是直接存储到内存中,避免网关开销。
说明:
1、我在写本文章时,并没有做过多的文字说,但是代码中有非常详细的代码注解
。
2、如下代码省略了对 os_datasource
的 entity映射
和 mapper查询
,但是核心代码没有少一点。原项目中是做了缓存,链接如下: 查看缓存逻辑。
3、本文中并没有给出前端示例,完整项目请查看如下链接:查看完整项目。
2. 源代码
2.1. 表结构
CREATE TABLE `os_datasource` (
`id` varchar(20) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '数据源名称',
`drive` varchar(100) DEFAULT NULL COMMENT '驱动',
`url` varchar(500) DEFAULT NULL COMMENT '连接信息',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`database_type` varchar(255) DEFAULT NULL COMMENT '数据库类型',
`code` varchar(255) DEFAULT NULL COMMENT '暂留字段',
`del_flag` int DEFAULT '0' COMMENT '是否删除(1是 0否)',
PRIMARY KEY (`id`)
) COMMENT='数据源';
2.2. Entity
@Data
public class DataSource {
略....
}
2.3. Mapper
@Repository
public interface DataSourceMapper {
略....
}
@Service
public class DataSourceServiceImpl implements DataSourceService {
略....
}
2.4. DruidDBConfig
该类用于配置主数据源
package com.os.core.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.os.core.datasource.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
/* adi数据库连接信息 */
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
/* 连接池连接信息 */
@Value("${spring.datasource.initial-size}")
private int initialSize;
@Value("${spring.datasource.min-idle}")
private int minIdle;
@Value("${spring.datasource.max-active}")
private int maxActive;
@Value("${spring.datasource.max-wait}")
private int maxWait;
@Value("${spring.datasource.time-between-eviction-runs-millis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.min-evictable-idle-time-millis}")
private long minEvictableIdleTimeMillis;
@Value("${spring.datasource.max-evictable-idle-time-millis}")
private long maxEvictableIdleTimeMillis;
@Value("${spring.datasource.validation-query}")
private String validationQuery;
@Value("${spring.datasource.test-while-idle}")
private boolean testWhileIdle;
@Value("${spring.datasource.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.filters}")
private String filters;
private String mybatisConfig = "classpath:mybatis/mybatis-config.xml";
private String mapperXmlPath = "classpath*:mapper/**/*Mapper.xml";
/**
* 个性化配置数据源
*/
public DataSource dataSource() throws SQLException {
DruidDataSource datasource = new DruidDataSource();
/* 基础连接信息 */
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
/* 连接池连接信息 */
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
/* 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 */
datasource.setPoolPreparedStatements(true);
datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
/* 对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒 */
datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=15000");
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
/* 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,
执行validationQuery检测连接是否有效。
*/
datasource.setTestOnReturn(testOnReturn);
/* 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、
testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/* 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall */
datasource.setFilters(filters);
/* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/* 配置一个连接在池中最小生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/* 打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,
则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,
只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
*/
datasource.setKeepAlive(true);
/* 是否移除泄露的连接/超过时间限制是否回收。 */
datasource.setRemoveAbandoned(true);
/* 泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时 */
datasource.setRemoveAbandonedTimeout(3600);
/* 移除泄露连接发生是是否记录日志 */
datasource.setLogAbandoned(true);
return datasource;
}
/**
* 配置动态数据源。提示:DynamicDataSource 是 DataSource 的子类。
*/
@Bean("dynamicDataSource")
public DynamicDataSource dynamicDataSource() throws SQLException {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
/* 设置默认数据源配置 */
dynamicDataSource.setDefaultTargetDataSource(dataSource());
/* 必须设置targetDataSource,因为在AbstractRoutingDataSource的afterPropertiesSet()中会判断targetDataSource是否为空*/
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put("mainDataSource", dataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
/**
* 配置SqlSessionFactory
* @param dynamicDataSource 动态数据源bean
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 这里一定要注意使用的是我们自定义个的DynamicDataSource,如果不是,则切换数据源会失效
sqlSessionFactoryBean.setDataSource(dynamicDataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
/* 设置mybatis的主配置文件 */
sqlSessionFactoryBean.setConfigLocation(resolver.getResource(mybatisConfig));
/* 手动配置mybatis的mapper.xml资源路径 */
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperXmlPath));
return sqlSessionFactoryBean.getObject();
}
}
2.5. DynamicDataSource
该类用于实现动态数据源的创建、检查、删除、替换等操作。
package com.os.core.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.os.common.entity.datasource.DataSource;
import com.os.common.exception.DataSourceConfigException;
import com.os.common.exception.DataSourceCreateException;
import com.os.common.exception.DataSourceNotFoundException;
import com.os.common.utils.MyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class DynamicDataSource extends AbstractRoutingDataSource {
private final Logger log = LoggerFactory.getLogger(getClass());
private Map<Object, Object> dynamicTargetDataSources;
private Object dynamicDefaultTargetDataSource;
/* 连接池连接信息 */
@Value("${spring.datasource.initial-size}")
private int initialSize;
@Value("${spring.datasource.min-idle}")
private int minIdle;
@Value("${spring.datasource.max-active}")
private int maxActive;
@Value("${spring.datasource.max-wait}")
private int maxWait;
@Value("${spring.datasource.time-between-eviction-runs-millis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.min-evictable-idle-time-millis}")
private long minEvictableIdleTimeMillis;
@Value("${spring.datasource.max-evictable-idle-time-millis}")
private long maxEvictableIdleTimeMillis;
@Value("${spring.datasource.validation-query}")
private String validationQuery;
@Value("${spring.datasource.test-while-idle}")
private boolean testWhileIdle;
@Value("${spring.datasource.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.filters}")
private String filters;
public void clearDSCache() {
this.dynamicTargetDataSources = new HashMap<>();
}
/**
* 检测并创建动态数据源:数据源不存在则创建,存在则创建
* @param dataSource
* @throws Exception
*/
@Transactional
public boolean createDataSourceWithCheck(DataSource dataSource) throws Exception {
String datasourceId = String.valueOf(dataSource.getId());
String datasourceName = dataSource.getName();
log.info("正在检查数据源:[{}]", datasourceName);
/* 备份目标数据源 */
Map<Object, Object> dynamicTargetDataSourcesBak = this.dynamicTargetDataSources;
/* 检查目标数据源是否存在 */
if (dynamicTargetDataSourcesBak.containsKey(datasourceId)) {
log.info("数据源[{}]之前已经创建,准备测试数据源是否正常", datasourceName);
/* 获取数据源*/
DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSourcesBak.get(datasourceId);
/* 数据源连接测试 */
boolean rightFlag = true;
Connection connection = null;
try {
log.info("[{}]数据源的概况:{}个闲置连接, {}个活动连接", datasourceName, druidDataSource.getPoolingCount(),druidDataSource.getActiveCount());
connection = druidDataSource.getConnection();
log.info("数据源[{}]正常", datasourceName);
} catch (Exception e) {
rightFlag = false;
log.error(e.getMessage());
log.info("缓存数据源[{}]已失效,准备删除",datasourceName);
if(deleteDataSources(datasourceId)) {
log.info("缓存数据源删除成功");
} else {
log.error("缓存数据源删除失败");
}
} finally {
if(null != connection) {
connection.close();
}
}
if(rightFlag) {
return true;
} else {
boolean res = createDataSource(dataSource);
if (res) {
return true;
} else {
return false;
}
}
} else {
boolean res = createDataSource(dataSource);
if (res) {
return true;
} else {
return false;
}
}
}
/**
* 删除数据源
* @param datasourceId
* @return
*/
private boolean deleteDataSources(String datasourceId) {
Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
if (dynamicTargetDataSources2.containsKey(datasourceId)) {
Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
for (DruidDataSource l : druidDataSourceInstances) {
if (datasourceId.equals(l.getName())) {
dynamicTargetDataSources2.remove(datasourceId);
DruidDataSourceStatManager.removeDataSource(l);
/* 将map赋值给父类的TargetDataSources */
setTargetDataSources(dynamicTargetDataSources2);
/* 将TargetDataSources中的连接信息放入resolvedDataSources管理 */
super.afterPropertiesSet();
return true;
}
}
}
return false;
}
/**
* 创建数据源
* @param dataSource
* @throws Exception
*/
private boolean createDataSource(DataSource dataSource) throws Exception {
String datasourceId = String.valueOf(dataSource.getId());
String datasourceName = dataSource.getName();
log.info("准备创建数据源:[{}]",datasourceName);
String databaseType = dataSource.getDatabaseType();
String userName = dataSource.getUserName();
String password = dataSource.getPassword();
String url = dataSource.getUrl();
String driveClass = dataSource.getDrive();
if(testDatasource(driveClass,url,userName,password)) {
boolean result = this.createDataSource(datasourceId, driveClass, url, userName, password, databaseType,datasourceName);
if(!result) {
throw new DataSourceCreateException();
}
log.info("数据源[{}]创建成功",datasourceName);
return true;
} else {
throw new DataSourceConfigException();
}
}
/**
* 测试数据源连接是否有效
* @param driveClass
* @param url
* @param username
* @param password
* @return
*/
private boolean testDatasource(String driveClass, String url, String username, String password) {
try {
Class.forName(driveClass);
DriverManager.getConnection(url, username, password);
return true;
} catch (Exception e) {
e.printStackTrace();
throw new DataSourceConfigException();
}
}
/**
* 创建数据源
* @param key 数据源key
* @param driveClass 驱动
* @param url url
* @param username 用户名
* @param password 密码
* @param databasetype 数据库类型
* @param datasourceName 数据源名称
*/
private boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype,String datasourceName) {
try {
try { /* 排除连接不上的错误*/
Class.forName(driveClass);
DriverManager.getConnection(url, username, password);// 相当于连接数据库
} catch (Exception e) {
return false;
}
/* 创建数据源 */
@SuppressWarnings("resource")
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(key);
druidDataSource.setDriverClassName(driveClass);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
/* 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时*/
druidDataSource.setInitialSize(initialSize);
/* 最大连接池数量 */
druidDataSource.setMaxActive(maxActive);
/* 获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象 */
druidDataSource.setMaxWait(maxWait);
/* 最小连接池数量 */
druidDataSource.setMinIdle(minIdle);
/* 申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用 */
druidDataSource.setTestOnBorrow(testOnBorrow);
/* 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
druidDataSource.setTestWhileIdle(testWhileIdle);
druidDataSource.setTestOnReturn(testOnReturn);
/* 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */
druidDataSource.setValidationQuery(validationQuery);
/* 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall */
druidDataSource.setFilters(filters);
/* 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/* 配置一个连接在池中最小生存的时间,单位是毫秒 */
druidDataSource.setMinEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/* 打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断 */
druidDataSource.setKeepAlive(true);
/* 是否移除泄露的连接/超过时间限制是否回收。 */
druidDataSource.setRemoveAbandoned(true);
/* 泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时 */
druidDataSource.setRemoveAbandonedTimeout(3600);
/* 移除泄露连接发生是是否记录日志 */
druidDataSource.setLogAbandoned(true);
druidDataSource.init();
this.dynamicTargetDataSources.put(key, druidDataSource);
/* 将map赋值给父类的TargetDataSources */
setTargetDataSources(this.dynamicTargetDataSources);
log.info("数据源[{}]初始化成功",datasourceName);
return true;
} catch (Exception e) {
log.error(e.getMessage());
throw new DataSourceCreateException();
}
}
/**
* 设置当前要使用的数据源,key是数据源的唯一表示,value是数据源实体.
* 通过 super.setTargetDataSources() 将当前已经存在的数据源map集,传递给 AbstractRoutingDataSource 的 targetDataSources
*
* 该方法是在动态数据源被初始化后被调用(在当前类中),不能说是在数据源被切换时被调用,以为数据源切换有两种情况:
* 1. 数据源没有被初始化,那么会初始化数据源即createDataSource(),然后修改DBContextHolder
* 2. 数据源已经被初始化,那么会直接修改DBContextHolder
*/
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet(); // 利用afterPropertiesSet()将 targetDataSources 赋值给 resolvedDataSources
this.dynamicTargetDataSources = targetDataSources;
}
/**
* 设置默认数据源,默认数据源不需要key
* 通过 super.setDefaultTargetDataSource() 将默认数据源,传递给 AbstractRoutingDataSource 的 defaultTargetDataSource
*
* 该方法是在DynamicDataSource被实例化过程中被调用(com.os.core.config.DruidDBConfig => dynamicDataSource())
*/
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
}
/**
* 该方法是实现动态数据源切换的核心方法。
* 由上可知,我们已经创建数据源都是存储在map中的,determineCurrentLookupKey()的作用就是拿到目标数据源
* 的key,从而获取到数据源。
*
* 该方法是在AbstractRoutingDataSource中被调用的。
*/
@Override
protected Object determineCurrentLookupKey() {
/* 获取缓存中的key */
String datasourceKey = DBContextHolder.getDataSource();
/* 判断数据源 */
if (!MyUtil.isEmpty(datasourceKey)) {
Map<Object, Object> dynamicTargetDataSourcesBak = this.dynamicTargetDataSources;
/* 判断缓存中的数据源与设置的目标数据源是否一致 */
if (!dynamicTargetDataSourcesBak.containsKey(datasourceKey)) {
throw new DataSourceNotFoundException();
}
} else {
log.info("当前数据源:[默认数据源]");
}
return datasourceKey;
}
}
2.6. DBContextHolder
该类用于记录动态数据源上下文信息。
package com.os.core.datasource;
import com.os.common.utils.MyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class DBContextHolder {
private static final Logger log = LoggerFactory.getLogger(DBContextHolder.class);
// 对当前线程的操作-线程安全的
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 调用此方法,切换数据源
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
log.info("已完成数据源的切换");
}
// 获取数据源
public static String getDataSource() {
return contextHolder.get();
}
// 删除数据源
public static void clearDataSource() {
String dataSource = getDataSource();
if (!MyUtil.isEmpty(dataSource)) {
contextHolder.remove();
log.info("已切换到主数据源");
}
}
}
2.7. DBChangeService
该类用于实现动态数据源的切换。
package com.os.core.datasource;
import com.os.common.entity.datasource.DataSource;
import com.os.common.exception.DataSourceNotFoundException;
import com.os.common.exception.ErrorException;
import com.os.common.utils.MyUtil;
import com.os.system.service.DataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.SQLException;
import java.util.List;
@Service
public class DBChangeService {
@Autowired
DataSourceService dataSourceService;
@Autowired
DynamicDataSource dynamicDataSource;
public List<DataSource> get() throws SQLException {
return dataSourceService.selectList();
}
public void clearDSCache() {
dynamicDataSource.clearDSCache();
}
/**
* 切换到默认数据源
*/
public void changeDefaultBD() {
/* 默认切换到主数据源,进行整体资源的查找*/
DBContextHolder.clearDataSource();
}
public boolean changeBD(String datasourceId) throws Exception {
/* 默认切换到主数据源,进行整体资源的查找*/
DBContextHolder.clearDataSource();
/* 获取所有数据源 */
List<DataSource> dataSourcesList;
try {
dataSourcesList = dataSourceService.selectList();
if (MyUtil.isEmpty(dataSourcesList)) {
throw new DataSourceNotFoundException();
}
} catch (Exception e) {
e.printStackTrace();
throw new ErrorException();
}
/* 寻找目标数据源*/
for (DataSource dataSource : dataSourcesList) {
if (String.valueOf(dataSource.getId()).equals(datasourceId)) {
/* 创建数据源 */
boolean res = dynamicDataSource.createDataSourceWithCheck(dataSource);
if (res) {
/* 设置缓存 */
DBContextHolder.setDataSource(String.valueOf(dataSource.getId()));
return true;
} else {
return false;
}
}
}
throw new DataSourceNotFoundException();
}
}
2.8. @DynamicDatasource
该注解用于标记数据源的切换入口,以及待切换的目标数据源。
package com.os.common.annotation.ds;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDatasource {
String value() default "datasourceId";
}
2.9. DatasourceAspect
该类为动态数据源注解切面,其作用为完成数据源切换的实际入口(切换到目标数据源)和出口(切回到主数据源)位置。
package com.os.core.interceptor.ds;
import com.os.common.annotation.ds.DynamicDatasource;
import com.os.common.utils.MyUtil;
import com.os.core.datasource.DBChangeService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Order(-1)
@Component
public class DatasourceAspect {
@Autowired
DBChangeService dbChangeService ;
@Pointcut("@annotation(com.os.common.annotation.ds.DynamicDatasource)")
public void DsPointcut() {
}
@Before("DsPointcut()")
public void beforeMethod(JoinPoint joinPoint) throws Exception {
Map<String, Object> map = parameterMap(joinPoint);
DynamicDatasource changeDs = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(DynamicDatasource.class);
Object o = map.get(changeDs.value());
if (MyUtil.isEmpty(o)) {
return;
}
dbChangeService.changeBD(o.toString());
}
@After("DsPointcut()")
public void afterMethod(JoinPoint joinPoint) {
dbChangeService.changeDefaultBD();
}
/**
* 获取参数map
* @param joinPoint 切点
* @return 参数名-参数值的map
*/
private Map<String, Object> parameterMap(JoinPoint joinPoint) {
Object[] parameterValues = joinPoint.getArgs();
String[] parameterNames = ((CodeSignature) (joinPoint.getSignature())).getParameterNames();
HashMap<String, Object> map = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
map.put(parameterNames[i],parameterValues[i]);
}
return map;
}
}
2.10. Demo
package com.os.core.service.base.impl;
import com.os.common.annotation.ds.DynamicDatasource;
import com.os.common.entity.table.ColumnDetails;
import com.os.common.entity.table.TableCreateSQL;
import com.os.common.entity.table.TableDesc;
import com.os.core.service.base.BaseInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BaseInfoServiceImpl implements BaseInfoService {
@Autowired
XxxMapper xxxMapper;
/**
* 基于动态数据源获取表基础信息列表列表
* @param datasourceId 数据源ID // 核心参数
*/
@Override
@DynamicDatasource // 核心注解
public List<TableDesc> selectList(String datasourceId) {
return xxxMapper.selectList();
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献4条内容
所有评论(0)