spring-data-jpa Sping Boot 多租户-休眠-在实体结构更改时使用ddl-auto更新所有模式

brjng4g3  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(210)

我是Sping Boot 的新手,正在尝试使用Spring Boot 2、Hibernate和Flyway来实现多租户架构。我参考了教程https://reflectoring.io/flyway-spring-boot-multitenancy/来理解概念,并能够实现所提到的架构。
但是,如果我添加了一个新的字段实体类,那么一切都会中断,因为Hibernate不会在租户数据库中创建新字段。从阅读理论和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 {

    }
}

还有其他类,如Web配置,控制器等,但我不需要他们张贴在这里。

8oomwypt

8oomwypt1#

经过大量的研究,我了解到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();
        }

    }

}

相关问题