Spring 使用动态数据源启动 jpa 多租户
Spring boot jpa multitenancy with dynamic datasources
我正在尝试创建一个多租户 Web 应用程序,并找到了一个很好的教程 here。这解释了我如何配置 MVC 以找到新租户(使用 CurrentTenantIdentifierResolver
和一个扩展 HandlerInterceptorAdapter
的 MultiTenancyInterceptor),如何为三个不同的租户配置三个不同的数据源,以及如何提供正确的数据源在运行时通过扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImpl
到服务器
现在,这是让我了解 spring 和休眠中的多租户如何工作的一个很好的起点,但我想进一步推动这一点,我想让租户是完全动态的,即我不假设一个应用程序可以有多少租户。
这是我的想法:
- 应用程序配置为在启动时扫描路径(不在class路径中,例如/usr/data/config),并在各个目录下找到各种application.properties文件(一个目录用于每个租户)例如租户A、租户B、租户C...
- 对于每个 application.properties Spring 引导将基于该文件创建一个数据源(该文件将只有引导 属性 spring.datasource.url)。请注意,使用 spring 引导程序的 属性 会很棒,因为它从单个 URL 中为我提供了我需要的所有信息,例如 JDBC class 等等.
- 我将在 HashMap 中注册每个数据源(如前面 link 所示)
之后,前面link中已经描述了基本的多租户结构:每次最终用户向浏览器发出请求,服务器都会详细说明租户并返回正确的数据源以找到要使用的数据库。
任何人都可以向我指出一些资源,如果这是以前制作的(我在谷歌上搜索了很多但没有让我开始),或者给出一些建议 spring classes/configurations 用于实现这个?
提前致谢
这就是我最终所做的,如果有人有这种需要的话。
欢迎对此进行任何进一步扩展,或对最佳实践侵权发表评论。
扩展AbstractDataSourceBasedMultiTenantConnectionProviderImpl
的DataSourceProvider
必须覆盖两个方法
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;
}
}
我正在尝试创建一个多租户 Web 应用程序,并找到了一个很好的教程 here。这解释了我如何配置 MVC 以找到新租户(使用 CurrentTenantIdentifierResolver
和一个扩展 HandlerInterceptorAdapter
的 MultiTenancyInterceptor),如何为三个不同的租户配置三个不同的数据源,以及如何提供正确的数据源在运行时通过扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImpl
现在,这是让我了解 spring 和休眠中的多租户如何工作的一个很好的起点,但我想进一步推动这一点,我想让租户是完全动态的,即我不假设一个应用程序可以有多少租户。
这是我的想法:
- 应用程序配置为在启动时扫描路径(不在class路径中,例如/usr/data/config),并在各个目录下找到各种application.properties文件(一个目录用于每个租户)例如租户A、租户B、租户C...
- 对于每个 application.properties Spring 引导将基于该文件创建一个数据源(该文件将只有引导 属性 spring.datasource.url)。请注意,使用 spring 引导程序的 属性 会很棒,因为它从单个 URL 中为我提供了我需要的所有信息,例如 JDBC class 等等.
- 我将在 HashMap 中注册每个数据源(如前面 link 所示)
之后,前面link中已经描述了基本的多租户结构:每次最终用户向浏览器发出请求,服务器都会详细说明租户并返回正确的数据源以找到要使用的数据库。
任何人都可以向我指出一些资源,如果这是以前制作的(我在谷歌上搜索了很多但没有让我开始),或者给出一些建议 spring classes/configurations 用于实现这个?
提前致谢
这就是我最终所做的,如果有人有这种需要的话。 欢迎对此进行任何进一步扩展,或对最佳实践侵权发表评论。
扩展AbstractDataSourceBasedMultiTenantConnectionProviderImpl
的DataSourceProvider
必须覆盖两个方法
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;
}
}