簡體   English   中英

具有動態數據源的 Spring Boot jpa 多租戶

[英]Spring boot jpa multitenancy with dynamic datasources

我正在嘗試創建一個多租戶 Web 應用程序,並在這里找到了一個很好的教程。 這向我解釋了如何配置 MVC 以查找新租戶(使用CurrentTenantIdentifierResolver和擴展HandlerInterceptorAdapter的 MultiTenancyInterceptor ),如何為三個不同的租戶配置三個不同的數據源,以及如何通過擴展AbstractDataSourceBasedMultiTenantConnectionProviderImpl在運行時為服務器提供正確的數據源

現在,這是一個很好的起點,讓我了解 spring 和 hibernate 中的多租戶是如何工作的,但我想進一步推動這一點,我想讓租戶完全動態,即我不假設一個應用程序可以有多少租戶。

這就是我的想法:

  • 應用程序配置為在啟動時掃描路徑(不在類路徑中,例如 /usr/data/config),並在各種目錄(每個租戶一個目錄)下查找各種 application.properties 文件,例如租戶A、租戶B、租戶C.. .
  • 對於每個 application.properties,Spring boot 將基於該文件創建一個數據源(該文件將只有 boot 屬性 spring.datasource.url)。 請注意,使用 Spring Boot 的屬性會很棒,因為它為我提供了來自單個 URL 所需的所有信息,例如 JDBC 類等。
  • 我將在 HashMap 中注冊每個數據源(如上一個鏈接所示)

之后,基本的多租戶結構已經在上述鏈接中描述:每次最終用戶向瀏覽器發出請求時,服務器都會詳細說明租戶並返回正確的數據源以查找要使用的數據庫。

任何人都可以向我指出一些資源,如果這是以前做過的(我搜索了很多但沒有讓我開始),或者就使用哪些彈簧類/配置來實現這一點提供一些建議?

提前致謝

如果有人有這種需要,這就是我最終要做的。 任何對此的進一步擴展,或對最佳實踐侵權的評論都將受到歡迎。

擴展AbstractDataSourceBasedMultiTenantConnectionProviderImplDataSourceProvider必須覆蓋兩個方法

  • selectAnyDataSource返回一個@Autowired DataSource ,該@Autowired DataSource selectAnyDataSource Spring 使用為應用程序實例化數據源的常用方法進行實例化。
  • selectDataSource(String tenant)執行以下操作:
    • 獲取租戶的配置文件夾的路徑
    • 使用從租戶配置文件夾中的 application.properties 文件中獲取的屬性實例化DataSourceProperties
    • 通過DataSourceBuilder創建並返回一個新的DataSourceBuilder ,使用先前實例化的 DataSourceProperties 中的字段作為屬性(很有用,因為 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;
    }
}

看看這個如何實現以及如何自動化這個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM