Я создаю многопользовательскую весеннюю загрузку - приложение JPA.
В этом приложении я хочу подключиться к базам данных MySQL, используя имя БД, которое отправляется через запрос API в качестве заголовка.
Я проверил множество примеров мультитенантных проектов в Интернете, но все еще не могу найти решения.
Может ли кто-нибудь предложить мне способ сделать это?
Спасибо за ответ. Я знаю, что похоже на реализацию приложения без защиты. Но этот дизайн предназначен только для запуска приложения. Мы планируем устанавливать соединения с БД в соответствии с запросом, но не для получения имени БД и кредитов из запроса.
Не уверен, что следую твоей логике, но ладно. В любом случае, что именно доставляет вам проблемы? Отправить запрос с подходящим заголовком? Получение значения заголовка из запроса? Открытие соединения с БД с использованием переменной для имени БД? Непонятно, в чем именно проблема.
Я просто понял, как это сделать, и просто опубликовал ответ.
Для этого можно использовать AbstractRoutingDataSource
. AbstractRoutingDataSource
требует информации, чтобы знать, к какому фактическому DataSource
маршрутизировать (обозначается как Контекст), которая предоставляется методом determineCurrentLookupKey()
. На примере из здесь.
Определите Контекст как:
public enum ClientDatabase {
CLIENT_A, CLIENT_B
}
Затем вам нужно определить держатель контекста, который будет использоваться в determineCurrentLookupKey()
.
public class ClientDatabaseContextHolder {
private static ThreadLocal<ClientDatabase> CONTEXT = new ThreadLocal<>();
public static void set(ClientDatabase clientDatabase) {
Assert.notNull(clientDatabase, "clientDatabase cannot be null");
CONTEXT.set(clientDatabase);
}
public static ClientDatabase getClientDatabase() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
Затем вы можете расширить AbstractRoutingDataSource
, как показано ниже:
public class ClientDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return ClientDatabaseContextHolder.getClientDatabase();
}
}
Наконец, конфигурация bean-компонента DataSource
:
@Bean
public DataSource clientDatasource() {
Map<Object, Object> targetDataSources = new HashMap<>();
DataSource clientADatasource = clientADatasource();
DataSource clientBDatasource = clientBDatasource();
targetDataSources.put(ClientDatabase.CLIENT_A,
clientADatasource);
targetDataSources.put(ClientDatabase.CLIENT_B,
clientBDatasource);
ClientDataSourceRouter clientRoutingDatasource
= new ClientDataSourceRouter();
clientRoutingDatasource.setTargetDataSources(targetDataSources);
clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);
return clientRoutingDatasource;
}
Спасибо за ваш ответ. Кажется, ваше решение работает правильно, но я нашел способ лучше. Выкладываю сейчас.
https://github.com/wmeints/spring-multi-tenant-demo
Следуя этой логике, я могу решить ее сейчас. Некоторые версии нуждаются в обновлении, а также коды.
Версия Spring Boot изменилась.
org.springframework.boot весна-загрузка-стартер-родитель 2.1.0.РЕЛИЗ
Версия MySQL удалена.
И немного изменилось в MultitenantConfiguration.java
@Configuration
public class MultitenantConfiguration {
@Autowired
private DataSourceProperties properties;
/**
* Defines the data source for the application
* @return
*/
@Bean
@ConfigurationProperties(
prefix = "spring.datasource"
)
public DataSource dataSource() {
File[] files = Paths.get("tenants").toFile().listFiles();
Map<Object,Object> resolvedDataSources = new HashMap<>();
if (files != null) {
for (File propertyFile : files) {
Properties tenantProperties = new Properties();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader());
try {
tenantProperties.load(new FileInputStream(propertyFile));
String tenantId = tenantProperties.getProperty("name");
dataSourceBuilder.driverClassName(properties.getDriverClassName())
.url(tenantProperties.getProperty("datasource.url"))
.username(tenantProperties.getProperty("datasource.username"))
.password(tenantProperties.getProperty("datasource.password"));
if (properties.getType() != null) {
dataSourceBuilder.type(properties.getType());
}
resolvedDataSources.put(tenantId, dataSourceBuilder.build());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
// Create the final multi-tenant source.
// It needs a default database to connect to.
// Make sure that the default database is actually an empty tenant database.
// Don't use that for a regular tenant if you want things to be safe!
MultitenantDataSource dataSource = new MultitenantDataSource();
dataSource.setDefaultTargetDataSource(defaultDataSource());
dataSource.setTargetDataSources(resolvedDataSources);
// Call this to finalize the initialization of the data source.
dataSource.afterPropertiesSet();
return dataSource;
}
/**
* Creates the default data source for the application
* @return
*/
private DataSource defaultDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader())
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword());
if (properties.getType() != null) {
dataSourceBuilder.type(properties.getType());
}
return dataSourceBuilder.build();
}
}
Это изменение связано с тем, что DataSourceBuilder
был перемещен на другой путь и его конструктор был изменен.
Также изменилось имя класса драйвера MySQL в application.properties
следующим образом
spring.datasource.driver-имя-класса = com.mysql.jdbc.Driver
Не уверен, насколько оно отличается от моего решения, за исключением того факта, что я не предоставил точное решение для создания различных источников данных.
Это очень грубо. Сначала получите идею от другого человека, примите его ответ, затем ответьте на свой собственный и примите его. Отлично сработано.
Для меня это звучит как потенциальная дыра в безопасности. В чем причина этого? Возможно, это тоже недостаток дизайна: клиент API не должен вообще ничего знать или заботиться о базах данных. Конечно, личность пользователя, выполняющего запрос (или какой-либо аналогичный фактор или комбинация факторов), будет определять базу данных, которую API решит использовать.