Я новичок в Spring Boot и пытаюсь реализовать многопользовательскую архитектуру, используя Spring boot 2, hibernate и flyway. Я имел в виду учебник https://reflectoring.io/flyway-spring-boot-multitenancy/, чтобы понять концепции и смог реализовать упомянутую архитектуру.
Однако, если я добавлю новые классы сущностей поля, все сломается, потому что спящий режим не создает новые поля в базах данных арендаторов. Из теории чтения и вопросов о стеке переполнения я понимаю, что пролетный путь должен решить эту проблему. Однако я не могу заставить его работать.
Может кто-нибудь сказать мне, где я ошибаюсь. Мое требование: когда я добавляю новое поле в класс сущностей, все таблицы во всех клиентских базах данных должны обновляться с помощью этого поля. Ниже приведен код
Свойства приложения
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 {
}
}
Существуют и другие классы, такие как веб-конфигурация, контроллеры и т. д., но я не считаю их обязательными для размещения здесь.
Итак, просто для ясности: вы добавили скрипт миграции Flyway, который добавляет недостающие столбцы, выполняется flyway.migrate()
программно изнутри @PostConstruct
, и скрипт не запускался внутри клиентских БД, это правильно? Вы получили какие-либо ошибки? Есть ли какие-либо записи в таблице schema_version
Flyway в схемах арендаторов? Доступен ли сценарий переноса в расположении по умолчанию (например, classpath:db/migration
)?
После долгих исследований я понял, что 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=true&characterEncoding=utf8&useSSL=false&useTimezone=true&serverTimezone=Asia/Kolkata&useLegacyDatetimeCode=false&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();
}
}
}
Ты прав. Вы должны использовать для этого Flyway, а не Hibernate. Кроме того, у вас отключен пролетный путь прямо в ваших объектах. И, возможно, сообщение об ошибке, которую вы получите, значительно облегчит определение того, что пошло не так.