Reputation: 5389
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?
Upvotes: 7
Views: 12784
Reputation: 205
I came across this post when I needed to find a way to validate the schema by Hibernate in a testcase. Reason for this is that the schema is created in the test using database scripts. When I set the following property
hibernate.hbm2ddl.auto=validate
Hibernate will immediately report the tables are not there, before the create scripts are executed.
So I needed a way to validate the schema after it has been created. I found that in Hibernate 6.2 the SchemaManager
interface was introduced, which you can obtain via the SessionFactory
, which is exactly suited for this task.
Using the following code you can easily validate the current schema in a testcase:
@Autowired
SessionFactory sessionFactory;
@Test
void validateSchema() {
sessionFactory.getSchemaManager().validateMappedObjects();
}
Upvotes: 5
Reputation: 4365
This is solution, if your use case requires:
If above applies for your needs, than this is example of how to do scheduled schema validation:
@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;
}
}
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`)
);
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
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
Upvotes: 5
Reputation: 90517
If you want to let the SchemaValidator
to reuse the connection configuration and the mapping information that are already configured in the project rather then defining them once again for schema validation, you should consider my solution such that you are DRY and don't need to maintain these configurations in two separate places.
Actually , what SchemaValidator
requires is the Metadata
instance which is only available during bootstrapping Hibernate . But we can use Hibernate Integrator API (as described in here) to capture it such that we can validate them later.
(1) Create SchemaValidateService
which implements Hibernate Integrator API to capture Metadata
. Also setup a @Scheduled
method to validate the schema at desired time.
@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) Register SchemaValidateService
to 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);
});
}
}
Also, this solution should has better performance as it does not need to create a new database connection for validating schema each time as it can just grab the connection from the existing connection pool.
Upvotes: 3