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

时间:2016-05-08 07:54:28

标签: java hibernate spring-data-jpa multi-tenant

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

在运行时到服务器

现在,这是一个很好的起点,让我了解春天和冬眠的多租户是如何工作的,但我想进一步推动这一点,我想做到让租户完全充满活力,即我不要假设申请可以有多少租户。

这就是我的想法:

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

之后,基本的多租户结构已在上述链接中描述:每当最终用户向浏览器发出请求时,服务器将详细说明租户并返回正确的数据源以查找要使用的数据库

任何人都可以指点我一些资源,如果之前已经做过(我搜索了很多但没有让我开始),或者提供一些关于使用哪个Spring类/配置来实现这个目标的建议?

提前致谢

2 个答案:

答案 0 :(得分:2)

这就是我最终做的事情,如果有人有此需要的话。 任何进一步的扩展,或者关于最佳做法侵权的评论都将受到欢迎。

扩展DataSourceProvider的{​​{1}}必须覆盖两种方法

  • AbstractDataSourceBasedMultiTenantConnectionProviderImpl返回一个selectAnyDataSource,它由Spring实例化,通常用于实例化应用程序的数据源。
  • @Autowired DataSource执行以下操作:
    • 获取租户配置文件夹的路径
    • 实例化selectDataSource(String tenant),其属性取自租户配置文件夹中的application.properties文件
    • 通过DataSourceProperties创建并返回一个新的DataSource,使用以前实例化的DataSourceProperties中的字段作为属性(因为Spring会从数据库URL动态地为您提供驱动程序类名称)。

此处提供的代码,随时可以使用它:

DataSourceBuilder

答案 1 :(得分:0)

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

  @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;
    }
}