​​​​​​​Spring多租户数据源管理 AbstractRoutingDataSource

下面我就来详细讲解一下“Spring多租户数据源管理 AbstractRoutingDataSource”的完整攻略。

什么是多租户数据源管理

在多租户系统中,一份应用程序服务多个用户,每个用户有自己独立的数据。多租户数据源解决了这个问题,通过配置多个数据源,根据不同的用户请求来动态选取对应的数据源,从而实现对多租户数据的支持。

AbstractRoutingDataSource

Spring提供了一个实现多租户数据源管理的类AbstractRoutingDataSource,它是一个抽象类,通过继承该类并实现determineCurrentLookupKey方法,就能实现自己的动态数据源。

determineCurrentLookupKey()方法作用是获取当前的数据源key,然后根据key去获取对应的数据源,每个key对应一个对应的数据源。

具体来说,动态数据源管理需要以下两步:

  1. 在项目中,自定义一个类继承AbstractRoutingDataSource;

  2. 实现determineCurrentLookupKey()方法,该方法根据请求参数、session或者其他规则来确定对应的数据源key。

简单来说,AbstractRoutingDataSource 负责管理一组数据源,并提供了一种机制,使得在访问数据源时,能够动态选择其中的一个来使用。

AbstractRoutingDataSource代码示例

下面是一个使用 AbstractRoutingDataSource 的示例代码。

  1. 自定义类继承 AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}
  1. 配置 AbstractRoutingDataSource
@Configuration
public class DynamicDataSourceConfig {

    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 配置 targetDataSources,key 为数据源 key,value 为对应的数据源 DataSource
        targetDataSources.put("data1", dataSource1());
        targetDataSources.put("data2", dataSource2());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        // 设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource1());
        return dynamicDataSource;
    }

    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource dataSource2() {
        return DataSourceBuilder.create().build();
    }

}

在上面的示例中,我们定义了一个 DynamicDataSource 类,它继承了 AbstractRoutingDataSource 类,然后实现了该类的 determineCurrentLookupKey() 抽象方法。在该方法中,我们使用了一个名为"DataSourceContextHolder"的类来获取数据源 key。

DataSourceContextHolder是一个自定义的类,它使用 ThreadLocal 来存储当前线程使用的数据源 key。当需要切换数据源时,只需要调用 DataSourceContextHolder.setCurrentDataSource(key) 方法即可。

另外,我们在 DynamicDataSourceConfig 配置类中,定义了 dataSource1 和 dataSource2 两个数据源,并将它们注册到了动态数据源 DynamicDataSource 中。

AbstractRoutingDataSource示例2

下面是一个更加详细的示例,它演示了如何使用 Annotation 实现动态数据源的在不同方法中进行切换。

1. 添加注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "data1";
}

2. 自定义类继承 AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

3. 自定义ContextHolder类

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

4. 切面

@Aspect
@Component
public class DynamicDataSourceAspect {

    @Around("@annotation(ds)")
    public Object around(ProceedingJoinPoint point, DataSource ds) throws Throwable {
        String datasourceKey = ds.value();
        if (!DynamicDataSourceContextHolder.containsDataSource(datasourceKey)) {
            System.err.println("数据源[{}]不存在,使用默认数据源 > {}" + ds.value() + point.getSignature());
        } else {
            System.out.println("Use DataSource : {} > {}"+ ds.value() + point.getSignature());
            DynamicDataSourceContextHolder.setDataSourceType(datasourceKey);
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
            System.out.println("清空数据源信息 > {}");
        }
    }
}

5. 工具类

public class DataSourceEnum {
    public enum DataSourceType {
        DataSource1("data1"),
        DataSource2("data2");

        private String type;

        DataSourceType(String type) {
            this.type = type;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }
    }
}
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    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 containsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }

    public static List<String> getDataSourceIds() {
        return dataSourceIds;
    }

    public static void addDataSourceIds(String dataSourceId) {
        LOG.info("Register datasource : {}", dataSourceId);
        dataSourceIds.add(dataSourceId);
    }

    public static void addDataSourceIds(Collection<String> dataSourceIds) {
        DynamicDataSourceContextHolder.dataSourceIds.addAll(dataSourceIds);
    }

    public static void removeDataSourceIds(String dataSourceId) {
        LOG.info("Remove datasource : {}", dataSourceId);
        dataSourceIds.remove(dataSourceId);
    }

    public static void clearDataSourceIds() {
        dataSourceIds.clear();
    }

    public static void setDataSourceIds(Collection<String> dataSourceIds) {
        clearDataSourceIds();
        addDataSourceIds(dataSourceIds);
    }
}

6. 测试代码

@SpringBootTest(classes = Springboot2MybatisplusApplication.class)
@RunWith(SpringRunner.class)
public class DynamicDataSourceTest {

    @Autowired
    private DynamicDataSourceMapper mapper;

    @Test
    public void testInsert() {
        User user = new User();
        user.setName("name");
        userMapper.insert(user);
    }

    @Test
    @DataSource(DataSourceEnum.DataSourceType.DataSource1)
    public void testInsertWithDataSource1() {
        User user = new User();
        user.setName("name");
        userMapper.insert(user);
    }

    @Test
    @DataSource(DataSourceEnum.DataSourceType.DataSource2)
    public void testInsertWithDataSource2() {
        User user = new User();
        user.setName("name");
        userMapper.insert(user);
    }
}

在上面的示例代码中,我们使用了两个数据库:DataSource1 和 DataSource2。在使用 DynamicDataSourceContextHolder 类的 addDataSourceIds 方法注册需要使用的 DataSource 的 key。

在测试代码中,我们通过使用已经定义好的 DataSource 注解,告诉 Spring 当前需要使用哪个数据源。比如,在 testInsertWithDataSource1 方法上添加注解@DataSource(DataSourceEnum.DataSourceType.DataSource1),那么在该方法执行时,就会使用 DataSource1 数据源。

总结

这就是使用 AbstractRoutingDataSource 实现动态数据源的完整攻略。通过使用此方法,可以支持在一个应用程序中使用多个数据库,并且允许在应用程序运行时切换不同的数据源,从而实现多租户数据源管理的功能。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:​​​​​​​Spring多租户数据源管理 AbstractRoutingDataSource - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java线程安全与非线程安全解析

    Java线程安全与非线程安全解析 Java的线程安全问题是非常重要的一个主题,尤其是在多线程程序的开发中。本文将从线程安全和非线程安全的概念入手,深入探讨Java线程安全与非线程安全的区别,并以代码示例详细说明。 线程安全与非线程安全 Java中的线程安全问题可以简单理解为多线程同时访问同一块内存时所出现的问题。如果多个线程并发地访问同一块内存时,程序仍然能…

    Java 2023年5月19日
    00
  • 线上诊断神器-arthas基本应用

    Arthas基本应用 一、Arthas作用 什么是Arthas呢? ​ Arthas 是一款阿里推出的线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。 那我们为什么要使用Arthas? …

    Java 2023年4月22日
    00
  • Java网络编程之入门篇

    Java网络编程之入门篇 简介 网络编程是Java编程中不可或缺的一部分。Java提供了许多类和接口,支持Socket编程和URL编程,使得Java开发者可以轻松地构建并运行基于网络的应用程序。 本文将介绍Java网络编程的入门知识,包括Socket编程和URL编程的基本概念和示例。 Socket编程 Socket编程提供了与远程主机通信的机制。Java提供…

    Java 2023年5月19日
    00
  • 实例详解Java中如何对方法进行调用

    下面我将为您详细讲解“实例详解Java中如何对方法进行调用”的完整攻略。 什么是Java方法? 在Java中,方法指的是一段可重复使用的代码块,它可以接收零个、一个或多个参数,并在执行完毕后返回一个值。Java中的方法如同其他编程语言中的函数或子程序一样,它们担任着封装和抽象的重要角色。 方法的调用 在Java中调用方法需要两个要素:方法名和参数。方法名是方…

    Java 2023年5月26日
    00
  • JDBC程序更新数据库中记录的方法

    下面是JDBC程序更新数据库中记录的方法的完整攻略。 更新数据 在JDBC程序中,更新数据使用UPDATE语句,具体步骤如下: 加载JDBC驱动程序 建立数据库连接 创建Statement对象或PreparedStatement对象 准备SQL语句 执行SQL语句 关闭数据库连接 下面是代码示例: // 加载JDBC驱动程序 Class.forName(&q…

    Java 2023年5月19日
    00
  • SpringBoot整合SQLite数据库全过程

    下面我将为您详细讲解SpringBoot整合SQLite数据库的全过程,包括以下几个步骤: 导入SQLite依赖 配置SQLite数据源 创建实体类 创建DAO接口 创建Service层 创建Controller层 示例演示 1.导入SQLite依赖 在pom.xml文件中添加以下依赖: <dependency> <groupId>o…

    Java 2023年5月20日
    00
  • Java实现文件上传和下载的方法详解

    Java实现文件上传和下载的方法详解 文件上传 文件上传是通过HTTP协议中的POST方法进行实现的。在Java中,常见的实现方式有两种: 1. 使用Servlet API Servlet API 提供了实现文件上传的类 javax.servlet.http.Part。我们可以通过 request.getParts() 方法来获取所有上传的文件数据,然后进行…

    Java 2023年5月19日
    00
  • MySQL常用判断函数小结

    MySQL是一种关系型数据库管理系统,常用于网站后台开发中。而判断函数则是MySQL中的重要函数之一,用于对数据进行逻辑判断。下面是MySQL常用判断函数的小结: IF函数 IF函数的作用是,当第一个参数是真(非0或不空)时返回第二个参数,否则返回第三个参数。IF函数的格式如下: IF(condition, true_value, false_value) …

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部