Ответ 1
Предположения
Поскольку у меня нет репутации, чтобы опубликовать комментарий ниже вашего вопроса, мой ответ основан на следующих предположениях:
-
Текущее имя схемы, которое будет использоваться для текущего пользователя, доступно через поставщика Spring JSR-330, например
private javax.inject.Provider<User> user; String schema = user.get().getSchema();
private javax.inject.Provider<User> user; String schema = user.get().getSchema();
, Это идеальный прокси-сервер на основе ThreadLocal. -
Чтобы создать
DataSource
который полностью настроен так, как вам нужно, он требует одинаковых свойств. Каждый раз. Единственное, что отличается, это имя схемы. (Легко было бы получить и другие различные параметры, но это было бы слишком большим для этого ответа) -
Каждая схема уже настроена с необходимым DDL, поэтому нет необходимости в спящем режиме создавать таблицы или что-то еще
-
Каждая схема базы данных выглядит полностью одинаковой, за исключением ее имени
-
Вам нужно повторно использовать DataSource каждый раз, когда соответствующий пользователь делает запрос к вашему приложению. Но вы не хотите, чтобы каждый DataSource каждого пользователя постоянно находился в памяти.
Моя идея решения
Используйте комбинацию прокси-сервера ThreadLocal, чтобы получить имя схемы и Singleton-DataSource, которые ведут себя по-разному при каждом запросе пользователя. Это решение вдохновлено вашим намеком на AbstractRoutingDataSource
, комментарии Мехерзада и собственный опыт.
Динамический DataSource
Я предлагаю облегчить AbstractDataSource
Spring и реализовать его как AbstractRoutingDataSource
. Вместо статического Map
-like подход мы используем Guava Cache, чтобы получить простой в использовании кэш.
public class UserSchemaAwareRoutingDataSource extends AbstractDataSource {
private @Inject javax.inject.Provider<User> user;
private @Inject Environment env;
private LoadingCache<String, DataSource> dataSources = createCache();
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
private DataSource determineTargetDataSource() {
String schema = user.get().getSchema();
return dataSources.get(schema);
}
private LoadingCache<String, DataSource> createCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, DataSource>() {
public DataSource load(String key) throws AnyException {
return buildDataSourceForSchema(key);
}
});
}
private DataSource buildDataSourceForSchema(String schema) {
// e.g. of property: "jdbc:postgresql://localhost:5432/mydatabase?currentSchema="
String url = env.getRequiredProperty("spring.datasource.url") + schema;
return DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("spring.datasource.driverClassName"))
[...]
.url(url)
.build();
}
}
Теперь у вас есть "DataSource", который действует по-разному для каждого пользователя. После создания DataSource он будет кэшироваться в течение 10 минут. Это.
Внесите приложение в известность о нашем динамическом DataSource
Место для интеграции нашего недавно созданного DataSource - это синглтон DataSource, известный весеннему контексту и используемый во всех компонентах, например EntityManagerFactory
Поэтому нам нужен эквивалент:
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
но он должен быть более динамичным, чем простой DataSourceBuilder на основе простого свойства:
@Primary
@Bean(name = "dataSource")
public UserSchemaAwareRoutingDataSource dataSource() {
return new UserSchemaAwareRoutingDataSource();
}
Вывод
У нас есть прозрачный динамический DataSource, который использует правильный DataSource каждый раз.
Открытые вопросы
- Что делать, когда ни один пользователь не вошел в систему? Доступ запрещен для базы данных?
- Кто создает схемы?
отказ
Я не тестировал этот код!
EDIT: для реализации Provider<CustomUserDetails>
с Spring вам необходимо определить это как прототип. Вы можете использовать поддержку Springs для JSR-330 и Spring Securitys SecurityContextHolder:
@Bean @Scope("prototype")
public CustomUserDetails customUserDetails() {
return return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
Вам больше не нужен RequestInterceptor
, UserProvider
или код контроллера, чтобы обновить пользователя.
Помогает ли это?
EDIT2 Только для записи: НЕ CustomUserDetails
непосредственно на компонент CustomUserDetails
. Поскольку это прототип, Spring попытается создать прокси для класса CustomUserDetails
, что в нашем случае не является хорошей идеей. Поэтому просто используйте Provider
для доступа к этому компоненту. Или сделайте его интерфейсом.