我正在尝试创建一个多租户 Web 应用程序,并在这里找到了一个很好的教程。这解释了如何配置 MVC 以查找新租户(使用 CurrentTenantIdentifierResolver
和扩展 HandlerInterceptorAdapter 的 MultiTenancyInterceptor),
如何为三个不同的租户配置三个不同的数据源,以及如何通过扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImpl
在运行时为服务器提供正确的数据源
现在,这是让我了解spring和hibernate中的多租户如何工作的一个很好的起点,但我想进一步推动这一点,我希望租户是完全动态的,即我不会假设一个应用程序可以有多少个租户。
我是这么想的:
之后,前面的链接已经描述了基本的多租户结构:每次最终用户向浏览器发出请求时,服务器都会详细说明租户,并返回正确的数据源以查找要使用的数据库。
任何人都可以给我一些参考资料,如果以前有人做过的话(我谷歌了很多,但没有任何东西可以让我开始),或者给我一些建议,关于使用哪些spring类/配置来实现这一点?
提前感谢
如果有人有这种需求,这就是我最终做的。对此的任何进一步扩展,或关于最佳实践侵权的评论都将是最受欢迎的。
扩展 AbstractDataSourceBasedMultiTenantConnectionProviderImpl
的 DataSourceProvider
必须覆盖两个方法
选择anyDataSource
,返回一个@Autow的DataSource
,该由Spring使用为应用程序实例化数据源的常用方法实例化。
选择DataSource(字符串租户)
执行以下操作:
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;
}
}