[转帖]spring 多数据源的使用

spring,数据源,使用 · 浏览次数 : 0

小编点评

## 作者:dwtfukgv 本文介绍了如何在同一个项目中使用多个数据源的方法,以及使用 AbstractRoutingDataSource 进行切换数据源。 **AbstractRoutingDataSource** 是一个抽象类,它提供了一种灵活的方式来管理多个数据源。它定义了一个 `targetDataSources` 属性来存放所有数据源的名称,以及一个 `setDefaultTargetDataSource` 方法来设置默认数据源。当需要的时候,可以通过设置 `targetDataSources` 或 `setDefaultTargetDataSource` 方法来动态切换数据源。 **使用 AbstractRoutingDataSource** 的步骤: 1. 创建一个 `MultipleDataSource` 类,并实现 `getTargetDataSources` 和 `setDefaultTargetDataSource` 方法。 2. 在 Spring 容器中创建多个数据源,并使用 `@Qualifier` 注解指定每个数据源的名称。 3. 使用 `@Autowired` 注解将多个数据源注入到你的类中。 **示例**: ```java @Component public class MultipleDataSource { private Map targetDataSources; public void setTargetDataSources(Map targetDataSources) { this.targetDataSources = targetDataSources; } public DataSource getDataSource(String name) { return targetDataSources.get(name); } public void setDefaultTargetDataSource(DataSource defaultDataSource) { this.targetDataSources.put("default", defaultDataSource); } } ``` **多数据源的注解**: 可以使用 `@DataSource` 注解标记类或方法,指定数据源名称。例如: ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default \"\"; } ``` **总结**: AbstractRoutingDataSource 是一个非常灵活的解决方案,可以帮助你轻松管理多个数据源。它提供了多种方法来控制数据源的创建、配置和使用。

正文


目录
作者:@dwtfukgv
本文为作者原创,转载请注明出处:https://www.cnblogs.com/dwtfukgv/p/14848407.html
spring 多数据源的使用
在同一个项目中需要使用多个数据源,这就需要根据不同的场景进行切换数据源,spring给我们提供一种很方便的方式,那就是使用 AbstractRoutingDataSource 进行切换数据源。

首先来看 AbstractRoutingDataSource 这个类,下面是这个类源码。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
// 需要存储的所有数据源,需要在 bean 生成后进行注入,key 为获得数据源名称,value 为数据源
private Map<Object, Object> targetDataSources;

private Object defaultTargetDataSource; // 初始设计的默认数据源

// 如果在查找数据源时,找不到相应的数据源时是否使用默认数据源
private boolean lenientFallback = true;

// 通过 JNDI 方式的来获取一个数据源
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

// 将 targetDataSources 通过转化得到的数据源的集合。(如果不使用 JNDI 方式,它和 targetDataSources 是一样的)
private Map<Object, DataSource> resolvedDataSources;

// 转化后的默认数据源,如果在数据源的 key 为 null 时会使用,默认数据源,当然还有其他情况,下面会介绍
private DataSource resolvedDefaultDataSource;

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}

public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}

public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}

public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
}


/**
* 因为该类实现了 InitializingBean,所以会在 bean 生成之后(@Bean 注解执行之后,或者 xml 解析完之后)执行该方法
*/
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) { // 必须在 Bean 生成后注入数据源
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); // 对数据源名称进行重定义
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); // 获取数据源
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) { // 设置默认数据源
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}

/**
* 变换 lookupKey,由子类实现, 默认是 targetDataSources 的原始 key
*/
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}

/**
* 获取数据,直接获取,或者是通过 JNDI 的方法进行获取
*/
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) { // targetDataSources 中直接存储的就是数据源
return (DataSource) dataSource;
}
else if (dataSource instanceof String) { // 通过 JNDI 的方法获取数据源
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}


/**
* 获取数据源的数据库连接
*/
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

/**
* 通过用户名和密码的方式获取数据库连接
*/
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
}

/**
* 通过 lookupKey 来获取数据源
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey(); // 获取需要数据源的 lookupKey
// 从已经转化过的数据源查找相应的数据源
DataSource dataSource = this.resolvedDataSources.get(lookupKey);

// 如果没有找到数据源,或者可以使用默认数据源,或者 lookupKey 为 null,则使用默认数据源
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;
}

/**
* 由子类实现,返回需要获取的数据源的 lookupKey
*/
protected abstract Object determineCurrentLookupKey();

}

如果要实现多数据源的方式就必须要继承类,然后实现其抽象方法,下面给出一种我的实现方式:

多数据源的的实现:

public class MultipleDataSource extends AbstractRoutingDataSource {

// 通过 ThreadLocal 的方式来为每个线程一存储个不同的数据源名称
private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>();

/**
* 设置数据源,可以考虑使用注解 aop 的方式,在每个方法执行进行拦截,然后将数据源名称(lookupKey) 设置进行去
*/
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}

/**
* 实现父类的获取数据名称的方法
*/
@Override
protected Object determineCurrentLookupKey() {
String dsName = dataSourceKey.get();
dataSourceKey.remove(); // 注意要删除,否则可能内存泄漏
return dsName;
}
}

多数据源的注入 spring 容器:

@Bean("master")
public DataSource masterDataSource(){
return new C3p0SingleDataSource();
}

@Bean("slaver")
public DataSource slaverDataSource(){
return new BladeDataSource();
}

@Bean
public DataSource dataSource(@Qualifier("master") DataSource master, @Qualifier("slaver") DataSource slaver){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slaver", slaver);

MultipleDataSource multipleDataSource = new MultipleDataSource();
multipleDataSource.setTargetDataSources(targetDataSources); // 注入
multipleDataSource.setDefaultTargetDataSource(master); // 设置默认数据源
return multipleDataSource;
}
注意上面的 master 和 slaver 数据源都是随便 new 的,具体使用时还需要自己设置数据库的相应属性。

再考虑写一个多数据源的注解和 aop,可以加在方法和类上面,可以进行多数据源的使用:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "";
}
@Aspect
@Component
public class DataSourceAspect {
public DataSourceAspect() {
}

@Around("@within(dataSource) || @annotation(dataSource)")
public Object around(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
if (null == dataSource) {
dataSource = (DataSource)extractClassLevelAnnotation(joinPoint, DataSource.class);
}

MultipleDataSource.setDataSourceKey(dataSource.value());
return joinPoint.proceed();
}

public static Annotation extractClassLevelAnnotation(JoinPoint joinPoint, Class clazz) {
for(Annotation annotation : joinPoint.getTarget().getClass().getAnnotations()) {
if (annotation.annotationType() == clazz) {
return annotation;
}
}
throw new RuntimeException("类:" + joinPoint.getTarget().getClass().getSimpleName() + "没有找到" + clazz.getName() + "注解");
}
}

DataSource 注解可以类上面,表示类中的所有方法都使用该数据源,也可以加在方法上面,如果都加了,则会使用方法上面的那个。

@DataSource("slave")
public interface UserDao {

@DataSource("master")
int insert(UserPO userPO);
}

与[转帖]spring 多数据源的使用相似的内容:

[转帖]spring 多数据源的使用

目录作者:@dwtfukgv本文为作者原创,转载请注明出处:https://www.cnblogs.com/dwtfukgv/p/14848407.htmlspring 多数据源的使用在同一个项目中需要使用多个数据源,这就需要根据不同的场景进行切换数据源,spring给我们提供一种很方便的方式,那就

[转帖]RedisTemplate写入Redis数据出现无意义乱码前缀\xac\xed\x00\x05

背景 项目使用Spring的RedisTemplate进行Redis数据存取操作,实际应用中发现Redis中key和value会出现“无意义”乱码前缀\xac\xed\x00\x05t\x00-(样例\xac\xed\x00\x05t\x00-abcd:abc:xxxxxx:passport:ass

[转帖]Redis学习五(Spring Cache For Redis).

https://www.cnblogs.com/jmcui/p/8410560.html 一、概述 缓存(Caching)可以存储经常会用到的信息,这样每次需要的时候,这些信息都是立即可用的。 常用的缓存数据库: Redis 使用内存存储(in-memory)的非关系数据库,字符串、列表、集合、散列

[转帖]记录一次spring-boot程序内存泄露排查

现象 spring boot项目jvm启动配置-Xms4g -Xmx4g,然而很不幸的是程序所占的内存越来越高,都达到了12个多G,只能临时重启服务 常用命令 jstat -class PIDjstat -compiler PIDjstat -gc PIDjstat -gccapacity PIDj

[转帖]Full GC (Ergonomics) 产生的原因

发生Full GC,有很多种原因,不仅仅是只有Allocation Failure。 还有以下这么多: #include "precompiled.hpp" #include "gc/shared/gcCause.hpp" const char* GCCause::to_string(GCCause

[转帖]Spring Boot 3 Ships November 2022, Delays Java Module Support

Spring Boot 3 Ships November 2022, Delays Java Module Supporthttps://www.infoq.com/news/2022/10/spring-boot-3-jax-london/ Join a community of experts.

[转帖]Spring体系结构:七大核心模块详解

https://www.toutiao.com/article/7088616970362487329/ spring是一个非常优秀的java框架,99%的公司都在使用,spring算是必备技能,所以一定要掌握好@mikechen Spring简介 Spring是一个基于控制反转IOC和面向切面编程

[转帖]Spring Boot中Tomcat是怎么启动的

https://zhuanlan.zhihu.com/p/208318177 Spring Boot一个非常突出的优点就是不需要我们额外再部署Servlet容器,它内置了多种容器的支持。我们可以通过配置来指定我们需要的容器。 本文以我们平时最常使用的容器Tomcat为列来介绍以下两个知识点: Spr

[转帖]Spring Boot 依赖包及作用

目录 作者:@dwtfukgv本文为作者原创,转载请注明出处:https://www.cnblogs.com/dwtfukgv/articles/10179922.html Spring Boot 之Spring Boot Starter依赖包及作用 spring-boot-starter这是Spr

[转帖]Spring-data-redis操作redis知识总结

https://www.yisu.com/zixun/218120.html 什么是spring-data-redis spring-data-redis是spring-data模块的一部分,专门用来支持在spring管理项目对redis的操作,使用java操作redis最常用的是使用jedis,但