Search code examples
javaspringhibernatejpa

Validate schema programmatically using hibernate


In mose projects the way to run your java app with schema validation is with that configuration (when using spring):

spring.jpa.hibernate.ddl-auto=validate

I ran into a problem that I need to validate my schema at a specific times during running, is there any way to implement that?

I saw that hibernate managed it with the AbstractSchemaValidator, I'm using spring with hibernate, and I didn't found any information how to deal with it,
the only thing I found is How to validate database schema programmatically in hibernate with annotations? , but it was removed in the older versions of spring-boot

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>

any ideas?


Solution

  • This is solution, if your use case requires:

    • granular & explicit control of which part of the schema should be validated
    • the need is to validate multiple schemas
    • the need is to validate schema that is not used by the service, on which scheduled validator is running
    • db connections used by application should not be influenced by validation in any way (meaning, you don't want to borrow connection from main connections pool)

    If above applies for your needs, than this is example of how to do scheduled schema validation:

    1. Sources
    @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