#spring #spring-boot #spring-mvc #spring-data-jpa #spring-data
#spring #spring-boot #spring-mvc #spring-data-jpa #многопользовательский
Вопрос:
Я новичок в Spring Boot и пытаюсь реализовать многопользовательскую архитектуру, используя Spring boot 2, hibernate и flyway. Я имел в виду учебник https://reflectoring.io/flyway-spring-boot-multitenancy / чтобы понять концепции и смог реализовать архитектуру, как уже упоминалось.
Однако, если я добавлю новые классы объектов поля, все сломается, потому что спящий режим не создает новые поля в базах данных клиентов. Из вопросов теории чтения и stackoverflow я понимаю, что flyway должен решить эту проблему. Однако я не могу заставить его работать.
Может кто-нибудь сказать мне, где я ошибаюсь. Мое требование — когда я добавляю новое поле в класс объектов, все таблицы во всех базах данных клиентов должны обновляться этим полем. Ниже приведен код
Свойства приложения
spring:
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
flyway:
enabled: false
tenants:
datasources:
vw:
jdbcUrl: jdbc:mysql://localhost:3306/vw
driverClassName: com.mysql.jdbc.Driver
username: vikky
password: Test@123
bmw:
jdbcUrl: jdbc:mysql://localhost:3306/bmw
driverClassName: com.mysql.jdbc.Driver
username: vikky
password: Test@123
Конфигурация источника данных
@Configuration
public class DataSourceConfiguration {
private final DataSourceProperties dataSourceProperties;
public DataSourceConfiguration(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
@Bean
public DataSource dataSource() {
TenantRoutingDataSource customDataSource = new TenantRoutingDataSource();
customDataSource.setTargetDataSources(dataSourceProperties.getDatasources());
return customDataSource;
}
@PostConstruct
public void migrate() {
dataSourceProperties
.getDatasources()
.values()
.stream()
.map(dataSource -> (DataSource) dataSource)
.forEach(this::migrate);
}
private void migrate(DataSource dataSource) {
Flyway flyway = Flyway.configure().dataSource(dataSource).load();
flyway.migrate();
}
}
Свойства источника данных
@Component
@ConfigurationProperties(prefix = "tenants")
public class DataSourceProperties {
private Map<Object, Object> datasources = new LinkedHashMap<>();
public Map<Object, Object> getDatasources() {
return datasources;
}
public void setDatasources(Map<String, Map<String, String>> datasources) {
datasources
.forEach((key, value) -> this.datasources.put(key, convert(value)));
}
public DataSource convert(Map<String, String> source) {
return DataSourceBuilder.create()
.url(source.get("jdbcUrl"))
.driverClassName(source.get("driverClassName"))
.username(source.get("username"))
.password(source.get("password"))
.build();
}
}
Источник данных маршрутизации арендатора
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return ThreadTenantStorage.getTenantId();
}
}
Перехватчик заголовков
@Component
public class HeaderTenantInterceptor implements WebRequestInterceptor {
public static final String TENANT_HEADER = "X-tenant";
@Override
public void preHandle(WebRequest request) throws Exception {
ThreadTenantStorage.setTenantId(request.getHeader(TENANT_HEADER));
}
@Override
public void postHandle(WebRequest request, ModelMap model) throws Exception {
ThreadTenantStorage.clear();
}
@Override
public void afterCompletion(WebRequest request, Exception ex) throws Exception {
}
}
Существуют и другие классы, такие как веб-конфигурация, контроллеры и т.д., Но я не считаю, что они должны быть размещены здесь.
Комментарии:
1. Вы правы. Для этого вы должны использовать Flyway, а не спящий режим. Кроме того, у вас отключен flyway прямо в ваших свойствах. И, возможно, совместное использование полученной ошибки значительно упрощает определение того, что пошло не так.
2. Итак, просто чтобы было понятно: вы добавили сценарий миграции Flyway, который добавляет недостающие столбцы, выполняется
flyway.migrate()
программно изнутри@PostConstruct
, и сценарий не запускался внутри клиентских баз данных, это правильно? Вы получили какие-либо ошибки? Есть ли какие-либо записи внутриschema_version
таблицы Flyway в схемах арендаторов? Доступен ли сценарий миграции в расположении по умолчанию (т.Е.classpath:db/migration
)?
Ответ №1:
После долгих исследований я понял, что flyway требуется только в случае производства, где мы не хотим обновлять определение таблицы с помощью ddl-auto=true . Поскольку со мной это было не так, я добавил приведенную ниже конфигурацию для обновления всех схем в соответствии со структурой объекта
@Configuration
public class AutoDDLConfig
{
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${schemas.list}")
private String schemasList;
@Bean
public void bb()
{
if (StringUtils.isBlank(schemasList))
{
return;
}
String[] tenants = schemasList.split(",");
for (String tenant : tenants)
{
tenant = tenant.trim();
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver"); // Change here to MySql Driver
dataSource.setSchema(tenant);
dataSource.setUrl("jdbc:mysql://localhost/" tenant
"?autoReconnect=trueamp;characterEncoding=utf8amp;useSSL=falseamp;useTimezone=trueamp;serverTimezone=Asia/Kolkataamp;useLegacyDatetimeCode=falseamp;allowPublicKeyRetrieval=true");
dataSource.setUsername(username);
dataSource.setPassword(password);
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan("com"); // Here mention JPA entity path / u can leave it scans all packages
emfBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emfBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.default_schema", tenant);
emfBean.setJpaPropertyMap(properties);
emfBean.setPersistenceUnitName(dataSource.toString());
emfBean.afterPropertiesSet();
}
}
}