当前位置: 首页 > news >正文

石大远程在线考试 《网页设计与网站建设》站长工具之家

石大远程在线考试 《网页设计与网站建设》,站长工具之家,商标设计怎么收费,深圳航空公司订票电话实现效果 先说效果,要实现方法级别注解切换当前数据源,不设置注解时走默认数据源,同时支持JNDI源。 总体思路 Spring框架中存在一个抽象类AbstractRoutingDataSource,他是一个可以动态选择当前DataSource的路由类,我…

实现效果

先说效果,要实现方法级别注解切换当前数据源,不设置注解时走默认数据源,同时支持JNDI源。

总体思路

Spring框架中存在一个抽象类AbstractRoutingDataSource,他是一个可以动态选择当前DataSource的路由类,我们就是要从这里入手,重新实现数据源的切换选择逻辑。然后借助注解和切面,将当前需要的数据源名称放在ThreadLocal中,需要时从当前线程取得即可完成数据源的切换。
注解部分比较简单不再详说,看AbstractRoutingDataSource。该类文档写的非常全面,自行翻译一下就可以看懂。主要看其中的几个关键方法。

setTargetDataSources

类中存在一个成员变量targetDataSources,结合之后的setTargetDataSources方法可知,这里用来保存目标数据源。
根据注释我们可以知道,targetDataSources的key可以是数据源的名字,value是相应数据源的实例。
当然这里也可是使用其他的保存方式,然后自行改写用来查找数据源的determineCurrentLookupKey方法,默认场景就足够我们使用了。所以我们要构建一个Map出来,其中key用来区分数据源的名字,value放入对应数据源的实例,有几个数据源就放几个进去。

	@Nullableprivate Map<Object, Object> targetDataSources;/*** Specify the map of target DataSources, with the lookup key as key.* The mapped value can either be a corresponding {@link javax.sql.DataSource}* instance or a data source name String (to be resolved via a* {@link #setDataSourceLookup DataSourceLookup}).* <p>The key can be of arbitrary type; this class implements the* generic lookup process only. The concrete key representation will* be handled by {@link #resolveSpecifiedLookupKey(Object)} and* {@link #determineCurrentLookupKey()}.*/public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;}

setDefaultTargetDataSource

上面说了如何设置当前数据源,那如果在开发的时候每一个方法都要声明一下使用哪个源就太麻烦了,所以Spring提供了一个方法用来设置默认的数据源,没啥可说的,传入DataSource实例就好了。

	@Nullableprivate Object defaultTargetDataSource;/*** Specify the default target DataSource, if any.* <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}* instance or a data source name String (to be resolved via a* {@link #setDataSourceLookup DataSourceLookup}).* <p>This DataSource will be used as target if none of the keyed* {@link #setTargetDataSources targetDataSources} match the* {@link #determineCurrentLookupKey()} current lookup key.*/public void setDefaultTargetDataSource(Object defaultTargetDataSource) {this.defaultTargetDataSource = defaultTargetDataSource;}

determineCurrentLookupKey

在设置好数据源之后,接下来这几个寻路方法则是能实现动态数据源切换的重点。afterPropertiesSet方法对我们以配置的数据源进行校验;如果我们在第一步配置数据源map的时候对key有特殊处理则要自己实现抽象方法resolveSpecifiedLookupKey,告诉Spring应该怎么解析这个key值;determineTargetDataSource则最终确定要使用哪一个数据源,其中有一个方法determineCurrentLookupKey需要关注,这个方法会返回当前要使用的数据源名字,但他是个抽象方法,所以我们需要给他重写一下,改为从当前线程获取数据源名称

	@Overridepublic void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");}this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = resolveSpecifiedLookupKey(key);DataSource dataSource = resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);}}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password);}/*** Retrieve the current target DataSource. Determines the* {@link #determineCurrentLookupKey() current lookup key}, performs* a lookup in the {@link #setTargetDataSources targetDataSources} map,* falls back to the specified* {@link #setDefaultTargetDataSource default target DataSource} if necessary.* @see #determineCurrentLookupKey()*/protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}/*** Determine the current lookup key. This will typically be* implemented to check a thread-bound transaction context.* <p>Allows for arbitrary keys. The returned key needs* to match the stored lookup key type, as resolved by the* {@link #resolveSpecifiedLookupKey} method.*/@Nullableprotected abstract Object determineCurrentLookupKey();

代码实现

思路理顺了,代码写起来就比较快,直接贴最后代码,部分地方保留了注释。

数据源切换注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 数据源切换注解,默认为primary*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {DataSourceEnum value() default DataSourceEnum.PRIMARY;
}

数据源切换切面

这里需要特别提醒一下,事务注解@Transactional默认处于切面代理的最后一个,所以我们需要保证数据源切换注解优先级要高于事务注解

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 数据源切换切面*/
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {private final static Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);@Before(value = "@annotation(targetDataSource)")public void beforePointCut(TargetDataSource targetDataSource) {log.debug("数据源切换为 " + targetDataSource.value().getDataSourceName());DynamicDataSourceContextHolder.setDataSource(targetDataSource.value().getDataSourceName());}@After(value = "@annotation(targetDataSource)")public void afterPointCut(TargetDataSource targetDataSource) {log.debug("数据源恢复为 " + DataSourceEnum.PRIMARY.getDataSourceName());DynamicDataSourceContextHolder.clearDataSource();}
}

数据源枚举类

/*** 数据源枚举类*/
public enum DataSourceEnum {PRIMARY("primary"), SECONDARY("secondary");private final String dataSourceName;public String getDataSourceName() {return dataSourceName;}DataSourceEnum(String dataSourceName) {this.dataSourceName = dataSourceName;}
}

数据源上下文保持类

/*** 数据源上下文线程持有类*/
public class DynamicDataSourceContextHolder {/*** 存放当前线程使用的数据源类型信息*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static void setDataSource(String dataSourceType) {CONTEXT_HOLDER.set(dataSourceType);}public static String getDataSource() {return CONTEXT_HOLDER.get();}public static void clearDataSource() {CONTEXT_HOLDER.remove();}
}

AbstractRoutingDataSource自定义实现

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;/*** 动态数据源切换类** @author liuenqi*/
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSource();}public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {// 默认数据源super.setDefaultTargetDataSource(defaultTargetDataSource);// 所有目标数据源super.setTargetDataSources(targetDataSources);// 后处理super.afterPropertiesSet();}
}

数据源注册

注意使用jndi源的时候需要加一个特定前缀。

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;/*** 多数据源注册类*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {private DataSource primaryDataSource;private DataSource secondaryDataSource;@Overridepublic void setEnvironment(Environment environment) {initPrimaryDataSource(environment);initSecondaryDataSource(environment);}/*** 组装主数据源参数,兼容jdbc-url与jndi** @param env Environment*/private void initPrimaryDataSource(Environment env) {Map<String, String> paramMap = new HashMap<>(4);if (StringUtils.isNotBlank(env.getProperty("spring.datasource.primary.url"))) {paramMap.put("url", env.getProperty("spring.datasource.primary.url"));paramMap.put("userName", env.getProperty("spring.datasource.primary.username"));paramMap.put("password", env.getProperty("spring.datasource.primary.password"));paramMap.put("driverClassName", env.getProperty("spring.datasource.primary.driver-class-name"));} else {paramMap.put("jndi", env.getProperty("spring.datasource.primary.jndi-name"));}primaryDataSource = buildDataSource(paramMap);}/*** 组装辅数据源参数,兼容jdbc-url与jndi** @param env Environment*/private void initSecondaryDataSource(Environment env) {if (StringUtils.isNotBlank(env.getProperty("spring.datasource.secondary.url"))) {Map<String, String> paramMap = new HashMap<>(4);paramMap.put("url", env.getProperty("spring.datasource.secondary.url"));paramMap.put("userName", env.getProperty("spring.datasource.secondary.username"));paramMap.put("password", env.getProperty("spring.datasource.secondary.password"));paramMap.put("driverClassName", env.getProperty("spring.datasource.secondary.driver-class-name"));secondaryDataSource = buildDataSource(paramMap);} else if (StringUtils.isNotBlank(env.getProperty("spring.datasource.secondary.jndi-name"))) {Map<String, String> paramMap = new HashMap<>(2);paramMap.put("jndi", env.getProperty("spring.datasource.secondary.jndi-name"));secondaryDataSource = buildDataSource(paramMap);}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Map<Object, Object> targetDataSource = new HashMap<>(2);targetDataSource.put("primary", primaryDataSource);if (Objects.nonNull(secondaryDataSource)) {targetDataSource.put("secondary", secondaryDataSource);}// 为DynamicDataSource构造参数,注意参数顺序ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();constructorArgumentValues.addGenericArgumentValue(primaryDataSource);constructorArgumentValues.addGenericArgumentValue(targetDataSource);// 构造bean放入IOCGenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(DynamicDataSource.class);beanDefinition.setConstructorArgumentValues(constructorArgumentValues);beanDefinition.setSynthetic(true);registry.registerBeanDefinition("dataSource", beanDefinition);}/*** 使用HikariDataSource** @param paramMap {"url":"JDBC-URL","userName":"数据库用户名","password":"密码","driverClassName":"驱动名","jndi":"jndi源"}* @return HikariDataSource*/private DataSource buildDataSource(Map<String, String> paramMap) {HikariConfig hikariConfig = new HikariConfig();if (paramMap.containsKey("url")) {hikariConfig.setJdbcUrl(paramMap.get("url"));hikariConfig.setUsername(paramMap.get("userName"));hikariConfig.setPassword(paramMap.get("password"));hikariConfig.setDriverClassName(paramMap.get("driverClassName"));} else {hikariConfig.setDataSourceJNDI("java:comp/env/" + paramMap.get("jndi"));}return new HikariDataSource(hikariConfig);}
}

application.yml配置

spring:datasource:primary:url: jdbc:mysql://xxxxxusername: xxxxpassword: xxxxxdriver-class-name: com.mysql.cj.jdbc.Driversecondary: jndi-name: jdbc/db
http://www.yayakq.cn/news/765014/

相关文章:

  • 门户网站建设资质wordpress post.php
  • 刘涛现在哪个网站做直播做网站最简单
  • 基金会网站建设方案wordpress divi 2.5
  • 汕头哪里学网站建设最好软件外包开发
  • 广州建设银行网站宁波企业网站优化报价
  • 手机网站 优化做防水的网站有哪些
  • php 网站换空间兰州微商城搭建
  • 中国建设银行网站宁波网点如何选择编程培训机构
  • 什么网站可以做设计赚钱吴江网站制作
  • 电商网站建设实验原理建设网站松岗
  • 怎么样制作个网站做任务的网站源码
  • 牛商网网站的优化通过什么做上去
  • 长沙网站 微信建设电商网站设计思维导图
  • 外国广告公司网站做购物网站 推广
  • 做网站改字号代码营销型网站制作建设
  • 家电维修怎么自己做网站亿动广告公司
  • flash做网站拒绝做网站的理由
  • 做网站公众号多少钱wordpress怎么修改导航栏
  • wap网站制作软件宁夏网站建站
  • 长春哪家网络公司做网站专业获取网站访问qq
  • 征信网站开发数据中台建设
  • 检测ai写作的网站医院网站源码下载
  • 提高网站排名怎么做公司简介模板表格
  • 建设网站策划方案网站如何301
  • 化妆品网站设计欣赏室内设计公司简介
  • 门户网站html网站的程序怎么做的
  • 在线购物网站设计网站开发技术 北京
  • 广告传媒公司网站襄阳网站建设价格
  • 上海企业网站建设制作百度seo标题优化软件
  • 学校内部网站开发价格网页设计培训学校多少