Невозможно динамически создать источник данных SpringBoot — 3.3.0JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Невозможно динамически создать источник данных SpringBoot — 3.3.0

Сообщение Anonymous »

Я пытаюсь разработать REST-приложение с весенней загрузкой, которое предоставляет конечные точки Rest для динамического добавления источника данных без повторного развертывания.
По сути, я хочу запустить приложение с основным источником данных tenant1 с таблицей person, то после развертывания я хочу динамически добавить еще один источник данных tenant2, который также содержит таблицу person.
Я создал 1 база данных по умолчанию выглядит следующим образом:

Код: Выделить всё

 Tenant 1:  url: jdbc:mysql://localhost:3307/primarydb
Конфигурация application.yml следующая:
spring:
application:
имя: Dynamic-demo-db
main:
allow-bean-definition-overriding: true
jpa:
open-in-view: false
Для загрузки свойств используется следующий класс:

Код: Выделить всё

@Component
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceProperties {

private Map datasources;

public Map getDatasources() {
return datasources;
}

public void setDatasources(Map datasources) {
this.datasources = datasources;
}

}
Основываясь на онлайн-ресурсах, похоже, что AbstractRoutingDataSource следует использовать для реализации динамического источника данных следующим образом:

Код: Выделить всё

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
TenantContext показан ниже:

Код: Выделить всё

public class TenantContext {

private static final ThreadLocal CONTEXT = new ThreadLocal();

public static void setCurrentTenant(String tenantId) {
CONTEXT.set(tenantId);
}

public static String getCurrentTenant() {
return CONTEXT.get();
}

public static void clear() {
CONTEXT.remove();
}
}
Конфигурация источника данных и менеджера транзакций определяется ниже:

Код: Выделить всё

@Configuration
public class DynamicDataSourceConfig {

@Autowired
private DynamicDataSourceProperties properties;

private Map dataSourceMap = new ConcurrentHashMap();

@Bean
public DataSource dataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

properties.getDatasources().forEach((key, dataSourceProperties) -> {
HikariDataSource dataSource = createDataSource(dataSourceProperties);
dataSourceMap.put(key, dataSource);
});

dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSourceMap.values().iterator().next());
return dynamicRoutingDataSource;
}

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}

public void addDataSource(String key, DataSourceProperties dataSourceProperties) {
HikariDataSource dataSource = createDataSource(dataSourceProperties);
dataSourceMap.put(key, dataSource);
((DynamicRoutingDataSource) dataSource()).setTargetDataSources(dataSourceMap);

((DynamicRoutingDataSource) dataSource()).afterPropertiesSet();  // Refresh the DataSource routing
}

private HikariDataSource createDataSource(DataSourceProperties properties) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(properties.getDriverClassName());
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
return dataSource;
}
}
Методы добавления источника данных и сохранения человека показаны ниже:

Код: Выделить всё

@Service
public class DataSourceService {

@Autowired
private DynamicDataSourceConfig dynamicDataSourceConfig;
@Autowired
PersonRepository personRepository;

@Autowired
DataSource dataSource;

public void addDataSource(String key, DataSourceProperties properties) {
dynamicDataSourceConfig.addDataSource(key, properties);
}

@Transactional
public Person savePerson(PersonRequest personRequest) {
return  personRepository.save(personRequest.getPerson());
}
}
Я добавил фильтр для добавления арендатора в контекст до начала транзакции, как показано ниже:

Код: Выделить всё

  @Component
public class RequestFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void destroy() {

DataSourceContextHolder.clearDataSourceKey();
Filter.super.destroy();
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletResponse res = (HttpServletResponse) servletResponse;

HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
Enumeration headerNames = httpRequest.getHeaderNames();

String tenant = httpRequest.getHeader("datasource");

if (tenant !=null) {
TenantContext.setCurrentTenant(tenant);
DataSourceContextHolder.setDataSourceKey(tenant);
}

filterChain.doFilter(servletRequest, servletResponse);

}
}
Конечная точка Rest отображается следующим образом:

Код: Выделить всё

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.sql.DataSource;
import java.sql.SQLException;

@RestController
@RequestMapping("/datasource")
public class DataSourceController {

@Autowired
private DataSourceService dataSourceService;

@Autowired
DataSource dataSource;

@PostMapping("/add")
public void addDataSource(@RequestParam String key,
@RequestParam String url,
@RequestParam String username,
@RequestParam String password,
@RequestParam String driverClassName) throws SQLException {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl(url);
properties.setUsername(username);
properties.setPassword(password);
properties.setDriverClassName(driverClassName);
dataSourceService.addDataSource(key, properties);

dataSource.getConnection();
}

@PostMapping("/addPerson")
public Person savePerson(@RequestBody PersonRequest personRequest) {
return dataSourceService.savePerson(personRequest);
}
}
}
На следующем снимке экрана показаны полезные данные для добавления источника данных:
Изображение

Вышеуказанный источник данных успешно добавлен, и результат addPerson для ново созданной базы данных показан ниже:
Изображение

Однако запись вставлена ​​в tenant1(primarydb ) вместо tenant2(вторичный db).
Похоже, даже если я добавил tenant2 с новой базой данных, он не принимается во внимание.
Записи сохраняются в источнике данных по умолчанию — tenant1.
Примечание. Когда jpa:open-in-view: true, тогда он сохраняется в первичной базе данных, а если false, данные вообще не сохраняются.
Пожалуйста, посоветуйте, что я делаю не так?

Подробнее здесь: https://stackoverflow.com/questions/786 ... boot-3-3-0
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»