Как подключиться к нескольким базам данных MySQL в соответствии с заголовком в запросе REST API

Я создаю многопользовательскую весеннюю загрузку - приложение JPA.

В этом приложении я хочу подключиться к базам данных MySQL, используя имя БД, которое отправляется через запрос API в качестве заголовка.

Я проверил множество примеров мультитенантных проектов в Интернете, но все еще не могу найти решения.

Может ли кто-нибудь предложить мне способ сделать это?

Для меня это звучит как потенциальная дыра в безопасности. В чем причина этого? Возможно, это тоже недостаток дизайна: клиент API не должен вообще ничего знать или заботиться о базах данных. Конечно, личность пользователя, выполняющего запрос (или какой-либо аналогичный фактор или комбинация факторов), будет определять базу данных, которую API решит использовать.

ADyson 18.12.2018 13:10

Спасибо за ответ. Я знаю, что похоже на реализацию приложения без защиты. Но этот дизайн предназначен только для запуска приложения. Мы планируем устанавливать соединения с БД в соответствии с запросом, но не для получения имени БД и кредитов из запроса.

Sajeer Babu 18.12.2018 13:53

Не уверен, что следую твоей логике, но ладно. В любом случае, что именно доставляет вам проблемы? Отправить запрос с подходящим заголовком? Получение значения заголовка из запроса? Открытие соединения с БД с использованием переменной для имени БД? Непонятно, в чем именно проблема.

ADyson 18.12.2018 14:53

Я просто понял, как это сделать, и просто опубликовал ответ.

Sajeer Babu 18.12.2018 17:25
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
4
282
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Для этого можно использовать 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;
}

Спасибо за ваш ответ. Кажется, ваше решение работает правильно, но я нашел способ лучше. Выкладываю сейчас.

Sajeer Babu 18.12.2018 17:11
Ответ принят как подходящий

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

Не уверен, насколько оно отличается от моего решения, за исключением того факта, что я не предоставил точное решение для создания различных источников данных.

Sukhpal Singh 18.12.2018 18:11

Это очень грубо. Сначала получите идею от другого человека, примите его ответ, затем ответьте на свой собственный и примите его. Отлично сработано.

Sukhpal Singh 21.12.2018 10:07

Другие вопросы по теме