使用Hibernate编程验证模式架构

7
在大多数项目中,使用Spring时运行具有模式验证的Java应用程序的方法是使用以下配置:
spring.jpa.hibernate.ddl-auto=validate

我遇到了一个问题,需要在运行过程中特定的时间验证我的模式,有没有办法实现?
我看到 Hibernate 使用 AbstractSchemaValidator 进行管理, 我正在使用 Spring 和 Hibernate,但我没有找到任何处理它的信息, 我唯一找到的是如何使用注释在 Hibernate 中以编程方式验证数据库模式?,但它已经在旧版本的 spring-boot 中被删除了。
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>

任何想法?

1
这似乎是一个XY问题。你的使用场景是什么? - Turing85
1
我认为Turing85想知道为什么您需要每10分钟验证一次模式。 - Oreste Viron
哦,这是我们架构中的一些东西... @OresteViron - Daniel Taub
我已经更新了我的问题@SimonMartinelli。 - Daniel Taub
显示剩余3条评论
3个回答

5
这是一种解决方案,适用于以下情况:
  • 需要对模式的哪些部分进行细粒度和明确的控制
  • 需要验证多个模式
  • 需要验证服务未使用但计划验证器正在运行的模式
  • 应用程序使用的数据库连接不应受到验证的任何影响(意味着您不想从主连接池中借用连接)

如果上述适用于您的需求,则这是执行定期模式验证的示例:

  1. 来源
@SpringBootApplication
@EnableScheduling
@EnableConfigurationProperties(ScheamValidatorProperties.class)
public class SchemaValidatorApplication {
     public static void main(String[] args) {
       SpringApplication.run(SchemaValidatorApplication.class, args);
    }
}

@ConfigurationProperties("schema-validator")
class ScheamValidatorProperties {
    public Map<String, String> settings = new HashMap<>();

    public ScheamValidatorProperties() {
    }

    public Map<String, String> getSettings() { 
        return this.settings;
    }

    public void setSome(Map<String, String> settings) { 
        this.settings = settings;
    }
}

@Component
class ScheduledSchemaValidator {

    private ScheamValidatorProperties props;

    public ScheduledSchemaValidator(ScheamValidatorProperties props) {
        this.props = props;
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public void validateSchema() {
        StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
            .applySettings(props.getSettings())
            .build();

        Metadata metadata = new MetadataSources(serviceRegistry)
            .addAnnotatedClass(Entity1.class)
            .addAnnotatedClass(Entity2.class)
            .buildMetadata();

        try {
            new SchemaValidator().validate(metadata, serviceRegistry);
        } catch (Exception e) {
            System.out.println("Validation failed: " + e.getMessage());
        } finally {
            StandardServiceRegistryBuilder.destroy(serviceRegistry);
        }
    }
}

@Entity
@Table(name = "table1")
class Entity1 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    Entity1() {}

    public Long getId() {
        return id;
    }

}

@Entity
@Table(name = "table2")
class Entity2 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    Entity2() {}

    public Long getId() {
        return id;
    }
}
  1. schema.sql
CREATE DATABASE IF NOT EXISTS testdb;

CREATE TABLE IF NOT EXISTS `table1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `table2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
);

  1. application.yml
spring:
  cache:
    type: none
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3309/testdb?useSSL=false&nullNamePatternMatchesAll=true&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: test_user
    password: test_password
    testWhileIdle: true
    validationQuery: SELECT 1
  jpa:
    show-sql: false
    database-platform: org.hibernate.dialect.MySQL8Dialect
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
    properties:
      hibernate.dialect: org.hibernate.dialect.MySQL8Dialect
      hibernate.cache.use_second_level_cache: false
      hibernate.cache.use_query_cache: false
      hibernate.generate_statistics: false
      hibernate.hbm2ddl.auto: validate

schema-validator:
    settings:
        connection.driver_class: com.mysql.cj.jdbc.Driver
        hibernate.dialect: org.hibernate.dialect.MySQL8Dialect
        hibernate.connection.url: jdbc:mysql://localhost:3309/testdb?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
        hibernate.connection.username: test_user
        hibernate.connection.password: test_password
        hibernate.default_schema: testdb

  1. docker-compose.yml
version: '3.0'

services:
  db:
    image: mysql:8.0.14
    restart: always
    ports:
     - 3309:3306
    environment:
      MYSQL_ROOT_PASSWORD: test_password
      MYSQL_DATABASE: testdb
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_password

做得好!可以直接从spring.datasource和spring.jpa.database-platform属性构建schema-validator属性,以避免重复。 - robynico

3
如果您想让SchemaValidator重复使用项目中已配置的连接配置和映射信息,而不是再次定义它们以进行模式验证,则应考虑我的解决方案,这样您就可以DRY并且不需要在两个不同的位置维护这些配置。
实际上,SchemaValidator需要的是仅在启动Hibernate期间才可用的Metadata实例。但是,我们可以使用Hibernate Integrator API(如此处所述)来捕获它,以便稍后进行验证。
(1) 创建实现Hibernate Integrator API以捕获MetadataSchemaValidateService。还要设置一个@Scheduled方法,在所需时间验证模式。
@Component
public class SchemaValidateService implements Integrator {

    private Metadata metadata;

    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
        this.metadata = metadata;
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    }

    //Adjust the scheduled time here
    @Scheduled(cron = "0 0/1 * * * ?")
    public void validate() {
        try {
            System.out.println("Start validating schema");
            new SchemaValidator().validate(metadata);
        } catch (Exception e) {
            //log the validation error here.
        }
        System.out.println("Finish validating schema....");
    }
}

(2) 注册SchemaValidateService到Hibernate中

@SpringBootApplication
@EnableScheduling
public class App {

    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(SchemaValidateService schemaValidateService) {
        return (prop -> {
            List<Integrator> integrators = new ArrayList<>();
            integrators.add(schemaValidateService);
            prop.put("hibernate.integrator_provider", (IntegratorProvider) () -> integrators);
        });
    }
}

此解决方案应该具有更好的性能,因为它不需要每次都创建新的数据库连接来验证模式,而只需从现有的连接池中获取连接即可。

我想用自己的配置获取元数据,但是当我创建自己的配置时,出现了“在命名空间中找到多个表”的错误。 - Daniel Taub
你的意思是说,你想自己再次创建“元数据”,而不是重用已经由Hibernate构建的现有元数据? - Ken Chan
你是否担心无法配置自己想要验证的包,就像通常的Hibernate实体包那样,Hibernate已经为您构建了元数据?如果您想自己构建元数据,您应该参考其他人提出的解决方案。这个解决方案的精神是提出与已经提出的不同的东西,所以并不打算自己操纵元数据。 - Ken Chan

3
当我需要在测试用例中通过Hibernate验证模式时,我偶然发现了这篇帖子。之所以这样做是因为模式是通过数据库脚本在测试中创建的。当我设置了以下属性时,
hibernate.hbm2ddl.auto=validate

Hibernate在执行创建脚本之前,会立即报告表不存在的情况。
因此,我需要一种在创建模式后验证模式的方法。我发现在Hibernate 6.2中引入了SchemaManager接口,您可以通过SessionFactory获取该接口,非常适合这个任务。
使用以下代码,您可以轻松地在测试用例中验证当前模式:
@Autowired
SessionFactory sessionFactory;
   
@Test
void validateSchema() {
  sessionFactory.getSchemaManager().validateMappedObjects();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接