赞
踩
目录
DynamicDataSourceContextHolder
在学校或者自己练习的demo,基本都是配置一个数据源即可,基本都是使用MySQL,可是在工作中经常会出现很多不一样的场景和需求。
这里说一下我的需求:我需要从mysql数据库中通过一个字段去sqlserver数据库中读取数据,然后封装数据再录入到mysql中库中对应的表
尝试了很多方法,发现都不行,各种报错,在文章的后面会把我部署过程中的报错全部展现一下,方便大家排错,最后用的若依框架中的aop动态切换数据源的方式
- <!-- SQL Server -->
- <dependency>
- <groupId>com.microsoft.sqlserver</groupId>
- <artifactId>mssql-jdbc</artifactId>
- <version>9.2.1.jre15</version>
- </dependency>
这里的数据源分为master 和 slave 如果你有三个四个五个数据源在这里都可以配置,只要在后面的config类配置中进行配置即可
- #dev环境 mysql 7.0
- spring:
- application:
- name: data-spider
- datasource:
- type: com.alibaba.druid.pool.DruidDataSource
- # 配置多数据源
- master:
- Url: jdbc:mysql://localhost:3306/库名?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
- username: root
- password: root
- #driver-class-name: com.mysql.cj.jdbc.Driver
- slave:
- enabled: true
- Url: jdbc:sqlserver://localhost:1433;DatabaseName=test
- username: sa
- password: 123456
- driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
-
- druid:
- inital-size: 10
- #最大连接数
- max-active: 50
- #最小连接数
- min-idle: 10
- #获取链接等待超时时间
- max-wait: 5000
- pool-prepared-statements: true #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
- max-pool-prepared-statement-per-connection-size: 20
- validation-query: SELECT 1
- validation-query-timeout: 20000
- test-on-borrow: false #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
- test-on-return: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
- test-while-idle: true #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
- time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
- min-evictable-idle-time-millis: 300000 #一个连接在池中最小生存的时间,单位是毫秒
- max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒
- #StatViewServlet配置。(因为暴露的监控信息比较敏感,支持密码加密和访问ip限定)
- web-stat-filter:
- enableed: true
- stat-view-servlet:
- enabled: true
- url-pattern: /druid/*
- #可以增加访问账号密码【去掉注释就可以】
- #login-username: admin
- #login-password: admin
- filter:
- stat:
- enabled: true
- # 慢SQL 记录
- log-slow-sql: true
- slow-sql-millis: 1000
- merge-sql: true
- wall:
- config:
- multi-statement-allow: true
通过注解的方式动态切换数据源,很灵活
- package com.hhubrain.common.annotation;
-
- import com.hhubrain.model.enums.DataSourceType;
-
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Inherited;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
-
- /**
- * 自定义多数据源切换注解
- *
- * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
- *
- * @author
- */
- @Target({ ElementType.METHOD, ElementType.TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- public @interface DataSource
- {
- /**
- * 切换数据源名称
- */
- public DataSourceType value() default DataSourceType.MASTER;
- }
aop切面思想
- package com.hhubrain.common.aop;
-
- import java.util.Objects;
-
- import com.hhubrain.common.annotation.DataSource;
- import com.hhubrain.datasource.DynamicDataSourceContextHolder;
- import com.hhubrain.utils.StringUtils;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.core.annotation.AnnotationUtils;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
-
- /**
- *
- * 多数据源处理
- *
- * @author
- */
- @Aspect
- @Order(1)
- @Component
- public class DataSourceAspect
- {
- protected Logger logger = LoggerFactory.getLogger(getClass());
-
- //这里的地址就是上面的DataSource注解的位置
- @Pointcut("@annotation(com.hhubrain.common.annotation.DataSource)"
- + "|| @within(com.hhubrain.common.annotation.DataSource)")
- public void dsPointCut()
- {
-
- }
-
- @Around("dsPointCut()")
- public Object around(ProceedingJoinPoint point) throws Throwable
- {
- DataSource dataSource = getDataSource(point);
-
- if (StringUtils.isNotNull(dataSource))
- {
- DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
- }
-
- try
- {
- return point.proceed();
- }
- finally
- {
- // 销毁数据源 在执行方法之后
- DynamicDataSourceContextHolder.clearDataSourceType();
- }
- }
-
- /**
- * 获取需要切换的数据源
- */
- public DataSource getDataSource(ProceedingJoinPoint point)
- {
- MethodSignature signature = (MethodSignature) point.getSignature();
- DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
- if (Objects.nonNull(dataSource))
- {
- return dataSource;
- }
-
- return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
- }
- }
驱动自定义配置类,去获取不同的数据源做不同的事情
- package com.hhubrain.common.config;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.sql.DataSource;
-
- import com.hhubrain.datasource.DynamicDataSource;
- import com.hhubrain.model.enums.DataSourceType;
- import com.hhubrain.utils.SpringUtils;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
- import com.alibaba.druid.pool.DruidDataSource;
- import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
- import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
- import com.alibaba.druid.util.Utils;
-
-
- /**
- * druid 配置多数据源
- *
- * @author
- */
- @Configuration
- public class DruidConfig
- {
- //ConfigurationProperties 配置master数据源的前缀 看你的yml文件中的配置是否一致
- @Bean
- @ConfigurationProperties("spring.datasource.master")
- public DataSource masterDataSource(DruidProperties druidProperties)
- {
- DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
- return druidProperties.dataSource(dataSource);
- }
-
- //ConditionalOnProperty这个注解的意思是 当配置文件中enabled这个值为true时才执行这个方法
- @Bean
- @ConfigurationProperties("spring.datasource.slave")
- @ConditionalOnProperty(prefix = "spring.datasource.slave", name = "enabled", havingValue = "true")
- public DataSource slaveDataSource(DruidProperties druidProperties)
- {
- DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
- return druidProperties.dataSource(dataSource);
- }
-
- @Bean(name = "dynamicDataSource")
- @Primary
- public DynamicDataSource dataSource(DataSource masterDataSource)
- {
- Map<Object, Object> targetDataSources = new HashMap<>();
- targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
- setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
- return new DynamicDataSource(masterDataSource, targetDataSources);
- }
-
- /**
- * 设置数据源
- *
- * @param targetDataSources 备选数据源集合
- * @param sourceName 数据源名称
- * @param beanName bean名称
- */
- public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
- {
- try
- {
- DataSource dataSource = SpringUtils.getBean(beanName);
- targetDataSources.put(sourceName, dataSource);
- }
- catch (Exception e)
- {
- }
- }
-
- /**
- * 去除监控页面底部的广告
- */
- @SuppressWarnings({ "rawtypes", "unchecked" })
- @Bean
- @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
- public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
- {
- // 获取web监控页面的参数
- DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
- // 提取common.js的配置路径
- String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
- String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
- final String filePath = "support/http/resources/js/common.js";
- // 创建filter进行过滤
- Filter filter = new Filter()
- {
- @Override
- public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
- {
- }
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException
- {
- chain.doFilter(request, response);
- // 重置缓冲区,响应头不会被重置
- response.resetBuffer();
- // 获取common.js
- String text = Utils.readFromResource(filePath);
- // 正则替换banner, 除去底部的广告信息
- text = text.replaceAll("<a.*?banner\"></a><br/>", "");
- text = text.replaceAll("powered.*?shrek.wang</a>", "");
- response.getWriter().write(text);
- }
- @Override
- public void destroy()
- {
- }
- };
- FilterRegistrationBean registrationBean = new FilterRegistrationBean();
- registrationBean.setFilter(filter);
- registrationBean.addUrlPatterns(commonJsPattern);
- return registrationBean;
- }
- }
属性配置类,针对yml文件中的配置获取
- package com.hhubrain.common.config;
-
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.Configuration;
- import com.alibaba.druid.pool.DruidDataSource;
-
- /**
- * druid 配置属性
- *
- * @author
- */
- @Configuration
- public class DruidProperties
- {
- @Value("${spring.datasource.druid.inital-size}")
- private int initialSize;
-
- @Value("${spring.datasource.druid.min-idle}")
- private int minIdle;
-
- @Value("${spring.datasource.druid.max-active}")
- private int maxActive;
-
- @Value("${spring.datasource.druid.max-wait}")
- private int maxWait;
-
- @Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
- private int timeBetweenEvictionRunsMillis;
-
- @Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
- private int minEvictableIdleTimeMillis;
-
- @Value("${spring.datasource.druid.max-evictable-idle-time-millis}")
- private int maxEvictableIdleTimeMillis;
-
- @Value("${spring.datasource.druid.validation-query}")
- private String validationQuery;
-
- @Value("${spring.datasource.druid.test-while-idle}")
- private boolean testWhileIdle;
-
- @Value("${spring.datasource.druid.test-on-borrow}")
- private boolean testOnBorrow;
-
- @Value("${spring.datasource.druid.test-on-return}")
- private boolean testOnReturn;
-
- public DruidDataSource dataSource(DruidDataSource datasource)
- {
- /** 配置初始化大小、最小、最大 */
- datasource.setInitialSize(initialSize);
- datasource.setMaxActive(maxActive);
- datasource.setMinIdle(minIdle);
-
- /** 配置获取连接等待超时的时间 */
- datasource.setMaxWait(maxWait);
-
- /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
- datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
-
- /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
- datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
- datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
-
- /**
- * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
- */
- datasource.setValidationQuery(validationQuery);
- /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
- datasource.setTestWhileIdle(testWhileIdle);
- /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
- datasource.setTestOnBorrow(testOnBorrow);
- /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
- datasource.setTestOnReturn(testOnReturn);
- return datasource;
- }
- }
动态数据源
- package com.hhubrain.datasource;
-
- import java.util.Map;
- import javax.sql.DataSource;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
-
- /**
- * 动态数据源
- *
- * @author
- */
- public class DynamicDataSource extends AbstractRoutingDataSource
- {
- public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
- {
- super.setDefaultTargetDataSource(defaultTargetDataSource);
- super.setTargetDataSources(targetDataSources);
- super.afterPropertiesSet();
- }
-
- @Override
- protected Object determineCurrentLookupKey()
- {
- return DynamicDataSourceContextHolder.getDataSourceType();
- }
- }
数据源切换处理
- package com.hhubrain.datasource;
-
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- /**
- * 数据源切换处理
- *
- * @author
- */
- public class DynamicDataSourceContextHolder
- {
- public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
-
- /**
- * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
- * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
- */
- private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
-
- /**
- * 设置数据源的变量
- */
- public static void setDataSourceType(String dsType)
- {
- log.info("切换到{}数据源", dsType);
- CONTEXT_HOLDER.set(dsType);
- }
-
- /**
- * 获得数据源的变量
- */
- public static String getDataSourceType()
- {
- return CONTEXT_HOLDER.get();
- }
-
- /**
- * 清空数据源变量
- */
- public static void clearDataSourceType()
- {
- CONTEXT_HOLDER.remove();
- }
- }
方便在非spring管理环境中获取bean
- package com.hhubrain.utils;
-
- import org.springframework.aop.framework.AopContext;
- import org.springframework.beans.BeansException;
- import org.springframework.beans.factory.NoSuchBeanDefinitionException;
- import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
- import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
- import org.springframework.stereotype.Component;
-
-
- /**
- * spring工具类 方便在非spring管理环境中获取bean
- *
- * @author
- */
- @Component
- public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
- {
- /** Spring应用上下文环境 */
- private static ConfigurableListableBeanFactory beanFactory;
-
- private static ApplicationContext applicationContext;
-
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
- {
- SpringUtils.beanFactory = beanFactory;
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
- {
- SpringUtils.applicationContext = applicationContext;
- }
-
- /**
- * 获取对象
- *
- * @param name
- * @return Object 一个以所给名字注册的bean的实例
- * @throws BeansException
- *
- */
- @SuppressWarnings("unchecked")
- public static <T> T getBean(String name) throws BeansException
- {
- return (T) beanFactory.getBean(name);
- }
-
- /**
- * 获取类型为requiredType的对象
- *
- * @param clz
- * @return
- * @throws BeansException
- *
- */
- public static <T> T getBean(Class<T> clz) throws BeansException
- {
- T result = (T) beanFactory.getBean(clz);
- return result;
- }
-
- /**
- * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
- *
- * @param name
- * @return boolean
- */
- public static boolean containsBean(String name)
- {
- return beanFactory.containsBean(name);
- }
-
- /**
- * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
- *
- * @param name
- * @return boolean
- * @throws NoSuchBeanDefinitionException
- *
- */
- public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
- {
- return beanFactory.isSingleton(name);
- }
-
- /**
- * @param name
- * @return Class 注册对象的类型
- * @throws NoSuchBeanDefinitionException
- *
- */
- public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
- {
- return beanFactory.getType(name);
- }
-
- /**
- * 如果给定的bean名字在bean定义中有别名,则返回这些别名
- *
- * @param name
- * @return
- * @throws NoSuchBeanDefinitionException
- *
- */
- public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
- {
- return beanFactory.getAliases(name);
- }
-
- /**
- * 获取aop代理对象
- *
- * @param invoker
- * @return
- */
- @SuppressWarnings("unchecked")
- public static <T> T getAopProxy(T invoker)
- {
- return (T) AopContext.currentProxy();
- }
-
- /**
- * 获取当前的环境配置,无配置返回null
- *
- * @return 当前的环境配置
- */
- public static String[] getActiveProfiles()
- {
- return applicationContext.getEnvironment().getActiveProfiles();
- }
-
- /**
- * 获取当前的环境配置,当有多个环境配置时,只获取第一个
- *
- * @return 当前的环境配置
- */
- public static String getActiveProfile()
- {
- final String[] activeProfiles = getActiveProfiles();
- return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
- }
- }
type的枚举类,差点忘了
- package com.hhubrain.model.enums;
-
- /**
- * 数据源
- *
- * @author
- */
- public enum DataSourceType
- {
- /**
- * 主库
- */
- MASTER,
-
- /**
- * 从库
- */
- SLAVE,
- /**
- * 从库
- */
- TDENGINE
- }
在对应的service层或者mapper层类上面或者方法上添加@DataSource(value=type)注解即可
我这里是在定时器里写的,就直接在mapper的接口方法上加的
sqlserver 对应yml文件中salve中的数据源
mysql 对应yml文件中master中的数据源
Failed to configure a Datasource: 'url' attribute is not specified and no embedded datasource could be configured.
解决方法:
把jdbcUrl 改成url 不管是什么 都改成url即可
Failed to bind properties under 'spring.datasource.slave' to javax.sql.Datasource:
解决方法:
Cavse: org.springframework.jdbc.CannotbetJdbcConnectionException: Failed to obtain JDBcConnection; nested exception is com.microsoft,sglserverjdbcSQLServerException: 通过端口 1433 连接到主机 Localhost的TCP/IP连接失败。
解决方法:
右键点击"计算机"-->选择"管理"-->选择sqlserver配置管理-->重启服务
Failed to obtain JDBC Connection; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 对象名 'DUAL' 无效。
解决方法:
造成这个原因是配置mysql的时候需要validation-query:SELECT 1 FROM DUAL
但是sqlserver没有DUAL 造成报错,改成以下即可
问题千千万,唯有花时间
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。