提问者:小点点

带有动态数据源的Spring boot jpa多租户


我正在尝试创建一个多租户 Web 应用程序,并在这里找到了一个很好的教程。这解释了如何配置 MVC 以查找新租户(使用 CurrentTenantIdentifierResolver 和扩展 HandlerInterceptorAdapter 的 MultiTenancyInterceptor),如何为三个不同的租户配置三个不同的数据源,以及如何通过扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImpl 在运行时为服务器提供正确的数据源

现在,这是让我了解spring和hibernate中的多租户如何工作的一个很好的起点,但我想进一步推动这一点,我希望租户是完全动态的,即我不会假设一个应用程序可以有多少个租户。

我是这么想的:

  • 应用程序配置为在启动时扫描路径(不在类路径中,例如 /usr/data/config),并在各种目录下查找各种 application.properties 文件(每个租户一个目录),例如租户 A、租户 B、租户 C...
  • 对于每个应用程序属性,Spring 引导将基于该文件创建一个数据源(该文件将仅具有引导属性 spring.datasource.url)。请注意,使用 spring boot 的属性会很棒,因为它为我提供了来自单个 URL 所需的所有信息,例如 JDBC 类等。
  • 我将在 HashMap 中注册这些数据源中的每一个(如上一个链接所示)

之后,前面的链接已经描述了基本的多租户结构:每次最终用户向浏览器发出请求时,服务器都会详细说明租户,并返回正确的数据源以查找要使用的数据库。

任何人都可以给我一些参考资料,如果以前有人做过的话(我谷歌了很多,但没有任何东西可以让我开始),或者给我一些建议,关于使用哪些spring类/配置来实现这一点?

提前感谢


共2个答案

匿名用户

如果有人有这种需求,这就是我最终做的。对此的任何进一步扩展,或关于最佳实践侵权的评论都将是最受欢迎的。

扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImplDataSourceProvider 必须覆盖两个方法

  • 选择anyDataSource,返回一个@Autow的DataSource,该由Spring使用为应用程序实例化数据源的常用方法实例化。
  • 选择DataSource(字符串租户)执行以下操作:
    • 获取租户配置文件夹的路径
    • 使用从租户的配置文件夹中找到的application.properties文件中获取的属性实例化DataSource属性
    • 通过DataSourceBuilder创建并返回一个新的DataSource,使用之前实例化的DataSource属性中的字段作为属性(很有用,因为Spring从数据库URL动态地为您提供了驱动程序类名)

    此处提供的代码,请随意使用:

    String configPath = [...]; // Instantiate your configuration path
    File file = new File(realPath);
    DataSourceProperties dsProp = new DataSourceProperties();
    Properties properties = new Properties();
    try {
        properties.load(new FileInputStream(file));
    } catch (IOException e) {
        throw new TenantNotConfiguredException(tenant); // Custom exception
    }
    
    PropertiesConfigurationFactory<DataSourceProperties> pcf = new PropertiesConfigurationFactory<>(dsProp);
    pcf.setTargetName(DataSourceProperties.PREFIX);
    pcf.setProperties(properties);
    
    try {
        dsProp = pcf.getObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    return DataSourceBuilder.create()
                .url(dsProp.getUrl())
                .driverClassName(dsProp.getDriverClassName())
                .username(dsProp.getUsername())
                .password(dsProp.getPassword())
                .build();
    

匿名用户

这是完整的代码。我希望它能有所帮助,因为在我到达之前我也遭受了痛苦。

  @RestController
  @RequestMapping(value = "/accounts", headers = "Accept=application/json")
  public class AppController {

    @Autowired
    UserService service;
    @Autowired
    AppService appService;
    ////////working on dynamic loading of datasource

    @Autowired
    Map<String, DataSource> dataSourcesMtApp;

    public void updateDataSource(String url, String username, String password, String tenant) {
        try {
            DataSourceBuilder factory1 = DataSourceBuilder.create(MultiTenancyJpaConfiguration.class.getClassLoader()).url(url)
                    .username(username).password(password)
                    .driverClassName("com.mysql.jdbc.Driver");
            dataSourcesMtApp.put(tenant, factory1.build());
            System.out.println("Size:......................................................" + dataSourcesMtApp.size());
        } catch (Exception ex) {
            Logger.getLogger(AppController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @PostMapping("/create-account")
    public Response createAccount(@RequestBody ConnectionParams request) {
        String tenant = ConnectionUtils.initializeDatabase(request.getDatabase(), request.getDbusername(), request.getDbpassword());
        updateDataSource("jdbc:mysql://localhost:3306/" + request.getDatabase() + "?useSSL=false", request.getDbusername(), request.getDbpassword(), tenant);
        TenantContextHolder.setTenantId(tenant);
        Users user = new Users();
        user.setPassword(request.getLoginpassword());
        user.setUsername(request.getLoginusername());
        user.setTenant(tenant);
        user = service.save(user);
        String response = "Account Setup Completed TenantId: " + tenant + " Username: " + user.getUsername();
        Response rp = new Response();
        rp.setResult(response);
        return rp;
    }
}