动态数据源配置,通过数据库存储数据源连接信息,动态使用不同的数据库进行数据处理。使用自定义注解标识使用默认数据库。
配置文件:
/** * @author kuangsha * @date 2020/6/29 9:25 */ @Configuration public class DatasourceConfiguration { /** * 注册bean 拿到动态数据源 * 每次数据库操作都会执行DynamicDatasource 中重写的getConnection()方法 * @return */ @Bean public DataSource dataSource() { DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create(); dataSourceBuilder.type(DynamicDatasource.class); return dataSourceBuilder.build(); } }使用数据源之前先getConnection连接数据源,首先获取当前使用的数据源id,DatasourceHolder中通过数据源id获取数据源信息,如果没有数据源就初始化数据源连接
/** * @author kuangsha * @date 2020/6/29 9:27 */ @Slf4j public class DynamicDatasource extends HikariDataSource { @Override public Connection getConnection() throws SQLException { // 获取当前数据源 id Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig(); // 根据当前id获取数据源 HikariDataSource dataSource = DatasourceHolder.INSTANCE.getDatasource(id); if (null == dataSource) { // 初始化数据源 dataSource = initDatasource(id); } return dataSource.getConnection(); } /** * 初始化数据源 * * @param id * @return */ private HikariDataSource initDatasource(Long id) { HikariDataSource dataSource = new HikariDataSource(); // 判断是否是默认数据源 if (DatasourceHolder.DEFAULT_ID.equals(id)) { // 默认数据源根据 application.yml 配置的生成 DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class); dataSource.setJdbcUrl(properties.getUrl()); dataSource.setUsername(properties.getUsername()); dataSource.setPassword(properties.getPassword()); dataSource.setDriverClassName(properties.getDriverClassName()); } else { DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id); if (null == datasourceConfig) { throw new RuntimeException("没有此数据源"); } dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl()); dataSource.setUsername(datasourceConfig.getUsername()); dataSource.setPassword(datasourceConfig.getPassword()); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); } // 将创建的数据源添加数据源管理中 绑定当前线程 DatasourceHolder.INSTANCE.addDatasource(id, dataSource); return dataSource; } }管理数据源id 当前使用数据源的id记录和修改
/** * 数据源管理 * * @author kuangsha * @date 2020/6/29 9:38 */ public class DatasourceConfigContextHolder { // 线程之间可见数据源id private static final ThreadLocal<Long> DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID); /** * 获取当前数据源id * * @return */ public static Long getCurrentDatasourceConfig() { return DATASOURCE_HOLDER.get(); } /** * 设置默认数据源 */ public static void setDefaultDatasource() { DATASOURCE_HOLDER.remove(); setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID); } /** * 设置当前数据源配置 * * @param id */ public static void setCurrentDatasourceConfig(Long id) { DATASOURCE_HOLDER.set(id); } }数据源id和数据源对应管理,可以获取数据源id对应的数据源 DataSource,可以添加和移除已有的数据源对应
/** * @author kuangsha * @date 2020/6/29 9:39 */ public enum DatasourceHolder { /** * 当前实例 */ INSTANCE; /** * 默认数据源的id */ public static final Long DEFAULT_ID = -1L; /** * 管理动态数据源列表。 */ private static final Map<Long, DatasourceManager> DATASOURCE_CACHE = new ConcurrentHashMap<>(); /** * 根据数据源id获取数据源 * * @param id * @return */ public synchronized HikariDataSource getDatasource(Long id) { if (DATASOURCE_CACHE.containsKey(id)) { DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id); datasourceManager.refreshTime(); return datasourceManager.getDataSource(); } return null; } /** * 数据源添加到数据源管理器中,绑定当前线程 * * @param id * @param dataSource */ public void addDatasource(Long id, HikariDataSource dataSource) { DatasourceManager datasourceManager = new DatasourceManager(dataSource); DATASOURCE_CACHE.put(id, datasourceManager); } }数据源类 保存datasource和刷新时间,每使用一次就刷新最近使用时间
/** * 数据源管理类 * * @author kuangsha * @date 2020/6/29 9:44 */ public class DatasourceManager { /** * 上一次使用时间 */ private LocalDateTime lastUseTime; /** * 数据源 */ @Getter private HikariDataSource dataSource; public DatasourceManager(HikariDataSource dataSource) { this.dataSource = dataSource; this.lastUseTime = LocalDateTime.now(); } /** * 刷新上次使用时间 */ public void refreshTime() { this.lastUseTime = LocalDateTime.now(); } }数据库中配置的数据源读取存放,可以新增数据源配置和删除数据源配置,数据源id做key
/** * 数据源实体管理 * 数据库中配置的数据源,通过数据源id和数据源配置对应 * @author kuangsha * @date 2020/6/29 9:57 */ public enum DatasourceConfigCache { /** * 当前实例 */ INSTANCE; /** * 管理动态数据源列表。 */ private static final Map<Long, DatasourceConfig> CONFIG_CACHE = new ConcurrentHashMap<>(); /** * 获取数据源配置 * * @param id * @return */ public DatasourceConfig getConfig(Long id) { if (CONFIG_CACHE.containsKey(id)) { return CONFIG_CACHE.get(id); } return null; } /** * 添加数据源列表 * * @param id * @param datasourceConfig */ public void addConfig(Long id, DatasourceConfig datasourceConfig) { CONFIG_CACHE.put(id, datasourceConfig); } }获取bean,这儿使用在获取DataSourceProperties的yml配置文件生成
/** * @author kuangsha * @date 2020/6/29 9:51 */ @Service @Slf4j @Lazy(false) public class SpringUtil implements ApplicationContextAware, DisposableBean { private static ApplicationContext applicationContext = null; /** * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. * * @param requiredType * @return */ public static <T> T getBean(Class<T> requiredType) { return applicationContext.getBean(requiredType); } /** * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. * * @param beanName * @return */ public static <T> T getBean(String beanName) { return (T) applicationContext.getBean(beanName); } /** * 实现DisposableBean接口, 在Context关闭时清理静态变量. * * @throws Exception */ @Override public void destroy() throws Exception { SpringUtil.clearHolder(); } /** * 清空SpringContextHolder中的ApplicationContext为Null. */ private static void clearHolder() { if (log.isDebugEnabled()) { log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); } applicationContext = null; } /** * 实现ApplicationContextAware接口, 注入Context到静态变量中. * * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtil.applicationContext = applicationContext; } }MybatisConfiguration类:
指定mapper位置,定义sqlSessionFactory,通过数据源创建SqlSessionFactory
/** * * @author kuangsha * @date 2020/6/29 10:46 */ @Configuration // sqlSessionFactoryRef 指定sqlSessionFactory对象名 @MapperScan(basePackages = "cn.shuhan.dynamic.datasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory") public class MybatisConfiguration { /** * 创建会话工厂。 * * @param dataSource 数据源 * @return 会话工厂 */ @Bean(name = "sqlSessionFactory") @SneakyThrows // lombok public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean.getObject(); } }Mapper
/** * 定义MyMapper * @author kuangsha * @date 2020/6/29 10:28 */ @RegisterMapper public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> { } /** * @author kuangsha * @date 2020/6/29 10:15 */ @Mapper public interface DatasourceConfigMapper extends MyMapper<DatasourceConfig> { } /** * @author kuangsha * @date 2020/6/29 10:15 */ @Mapper public interface UserMapper extends MyMapper<User> { }DefaultDatasourceAspect类:
自定义注解功能实现 ;
Before:如果有注解就使用默认数据源。没有注解就拿到请求头中的Datasource-Config-Id属性,如果不为空,就将数据源通过拿到的Datasource-Config-Id 去设置对应数据源,如果为空就使用默认数据源;
After: 方法之后将数据源切换回默认数据源
1.@Pointcut(“execution(public * cn.shuhan.dynamic.datasource.controller..(…))”) 注解切面位置
2.@Before(“datasourcePointcut()”) 方法执行前操作
3.@After(“datasourcePointcut()”) 方法执行后操作
/** * @author kuangsha * @date 2020/6/29 14:48 */ @Aspect @Component @RequiredArgsConstructor(onConstructor_ = @Autowired) public class DefaultDatasourceAspect { private final Logger logger = LoggerFactory.getLogger(DefaultDatasourceAspect.class); // @Pointcut("@annotation(cn.shuhan.dynamic.datasource.annotation.DefaultDatasource)") // 切注解 // public void datasourcePointcut() { // } @Pointcut("execution(public * cn.shuhan.dynamic.datasource.controller.*.*(..))") // 切位置 public void datasourcePointcut() { } @Before("datasourcePointcut()") public void doBefore(JoinPoint joinPoint) { logger.info("**************************************1111111111111"); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); // 去除有注解的方法 有注解只能 用默认数据源 DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class); if (null != annotation) { // 设置为默认数据源 DatasourceConfigContextHolder.setDefaultDatasource(); } else { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = attributes.getRequest(); String header = request.getHeader("Datasource-Config-Id"); if (StringUtil.isNotEmpty(header)) { long id = Long.parseLong(header); DatasourceConfigContextHolder.setCurrentDatasourceConfig(id); } else { DatasourceConfigContextHolder.setDefaultDatasource(); } } } /** * 设置回默认数据源 */ @After("datasourcePointcut()") public void doAfter() { logger.info("******************************22222222222"); DatasourceConfigContextHolder.setDefaultDatasource(); } }在调用有@DefaultDatasource 注解方法 使用默认数据库
调用没有@DefaultDatasource注解方法,获取请求头中Datasource-Config-Id 属性,通过Datasource-Config-Id值去获取动态数据源