Spring 使用动态数据源启动 jpa 多租户

Spring boot jpa multitenancy with dynamic datasources

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

到服务器

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

这是我的想法:

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

任何人都可以向我指出一些资源,如果这是以前制作的(我在谷歌上搜索了很多但没有让我开始),或者给出一些建议 spring classes/configurations 用于实现这个?

提前致谢

这就是我最终所做的,如果有人有这种需要的话。 欢迎对此进行任何进一步扩展,或对最佳实践侵权发表评论。

扩展AbstractDataSourceBasedMultiTenantConnectionProviderImplDataSourceProvider必须覆盖两个方法

  • selectAnyDataSource returns 返回一个 @Autowired DataSource,它由 Spring 使用为应用程序实例化数据源的常用方法实例化。
  • selectDataSource(String tenant) 执行以下操作:
    • 获取租户配置文件夹的路径
    • 实例化一个 DataSourceProperties,其属性取自租户配置文件夹中的 application.properties 文件
    • 通过 DataSourceBuilder 创建并 returns 一个新的数据源,使用先前实例化的 DataSourceProperties 中的字段作为属性(很有用,因为 Spring 为您提供了驱动程序 Class从数据库中动态命名 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;
    }
}