springboot 2.1.1动态切换多个数据源 - 日理万妓 2022-04-22 16:12 186阅读 0赞 ### 一.前言 ### springboot1.x与springboot2.x的不同版本还是有不少区别的,本文主要介绍在springboot2.1.1动态切换数据源的案例. ### 二.配置 ### ##### 1.引入依赖 ##### <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>sqljdbc4</artifactId> <version>4.0</version> </dependency> ##### 2.配置文件application.yml ##### 默认数据库 spring: profiles: active: pro datasource: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://127.0.0.1;DatabaseName=lss0555 username: root password: 888888 配置其他数据库,在application-pro.yml中: custom: datasource: names: date2 date2: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://127.0.0.1;DatabaseName=lss0666;sendStringParametersAsUnicode=false username: root password: 888888 validation-query: SELECT 1 time-between-eviction-runs-millis: 600000 min-evictable-idle-time-millis: 1800000 maximum-pool-size: 80 ### 三、切换数据源工具类 ### ##### 1.动态切换数据源 ##### public class DynamicDataSource extends AbstractRoutingDataSource { /* * 代码中的determineCurrentLookupKey方法取得一个字符串, * 该字符串将与配置文件中的相应字符串进行匹配以定位数据源,配置文件,即applicationContext.xml文件中需要要如下代码:(non-Javadoc) * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { /* * DynamicDataSourceContextHolder代码中使用setDataSourceType * 设置当前的数据源,在路由类中使用getDataSourceType进行获取, * 交给AbstractRoutingDataSource进行注入使用。 */ return DynamicDataSourceContextHolder.getDataSourceType(); } } ##### 2.动态数据源上下文管理 ##### public class DynamicDataSourceContextHolder { //存放当前线程使用的数据源类型信息 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); //存放数据源id public static List<String> dataSourceIds = new ArrayList<String>(); //设置数据源 public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } //获取数据源 public static String getDataSourceType() { return contextHolder.get(); } //清除数据源 public static void clearDataSourceType() { contextHolder.remove(); } //判断当前数据源是否存在 public static boolean isContainsDataSource(String dataSourceId) { return dataSourceIds.contains(dataSourceId); } } ##### 3.初始化数据源和提供了执行动态切换数据源的工具类 ##### 在上面已经配置了二个数据源,但是这是我们自定义的配置,springboot是无法给我们自动配置,所以需要我们自己注册数据源. 那么就要实现 EnvironmentAware用于读取上下文环境变量用于构建数据源,同时也需要实现 ImportBeanDefinitionRegistrar接口注册我们构建的数据源。DynamicDataSourceRegister具体代码如下 /** * @Description 注册动态数据源 * 初始化数据源和提供了执行动态切换数据源的工具类 * EnvironmentAware(获取配置文件配置的属性值) */ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); //指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置) private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource"; //默认数据源 private DataSource defaultDataSource; //用户自定义数据源 private Map<String, DataSource> customDataSources = new HashMap<>(); @Override public void setEnvironment(Environment environment) { initDefaultDataSource(environment); initcustomDataSources(environment); } private void initDefaultDataSource(Environment env) { // 读取主数据源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver-class-name", env.getProperty("spring.datasource.driver-class-name")); dsMap.put("url", env.getProperty("spring.datasource.url")); dsMap.put("username", env.getProperty("spring.datasource.username")); dsMap.put("password", env.getProperty("spring.datasource.password")); defaultDataSource = buildDataSource(dsMap); } private void initcustomDataSources(Environment env) { // 读取配置文件获取更多数据源 String dsPrefixs = env.getProperty("custom.datasource.names"); for (String dsPrefix : dsPrefixs.split(",")) { // 多个数据源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver-class-name", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name")); dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url")); dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username")); dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password")); DataSource ds = buildDataSource(dsMap); customDataSources.put(dsPrefix, ds); } } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); //添加默认数据源 targetDataSources.put("dataSource", this.defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); //添加其他数据源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } //创建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); //注册 - BeanDefinitionRegistry beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry"); } public DataSource buildDataSource(Map<String, Object> dataSourceMap) { try { Object type = dataSourceMap.get("type"); if (type == null) { type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource } Class<? extends DataSource> dataSourceType; dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); String driverClassName = dataSourceMap.get("driver-class-name").toString(); String url = dataSourceMap.get("url").toString(); String username = dataSourceMap.get("username").toString(); String password = dataSourceMap.get("password").toString(); // 自定义DataSource配置 DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url) .username(username).password(password).type(dataSourceType); return factory.build(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } } 注册完数据源之后,需要通过@import注解把我们的数据源注册器导入到spring中 在启动类DemoApplication.java加上如下注解 > @Import(DynamicDataSourceRegister.class)。 @Import({ DynamicDataSourceRegister.class}) //注册多源数据库 @SpringBootApplication public class DemoApplication{ public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ##### 4.自定义注解 ##### /** * @Description: 在方法上使用,用于指定使用哪个数据源 */ @Target({ ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); } ##### 5.动态数据源通知 ##### @Aspect @Order(-10)//保证该AOP在@Transactional之前执行 @Component public class DynamicDataSourceAspect { private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); //改变数据源 @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { String dbid = targetDataSource.name(); if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) { //joinPoint.getSignature() :获取连接点的方法签名对象 logger.error("数据源 " + dbid + " 不存在使用默认的数据源 -> " + joinPoint.getSignature()); } else { logger.debug("使用数据源:" + dbid); DynamicDataSourceContextHolder.setDataSourceType(dbid); } } @After("@annotation(targetDataSource)") public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { logger.debug("清除数据源 " + targetDataSource.name() + " !"); DynamicDataSourceContextHolder.clearDataSourceType(); } } ##### 6.使用 ##### 本例使用mybatis,在dao层的数据接口上写上切换数据源注解即可,如: @Repository public interface UserDaoMapper{ @TargetDataSource(name = "date2") User getUserList(); }
还没有评论,来说两句吧...