-
-
Save jelies/8951881 to your computer and use it in GitHub Desktop.
package com.jelies.hibernate.validation; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.TreeMap; | |
import org.apache.log4j.Logger; | |
import org.hibernate.HibernateException; | |
import org.hibernate.MappingException; | |
import org.hibernate.cfg.AnnotationConfiguration; | |
import org.hibernate.cfg.Configuration; | |
import org.hibernate.cfg.Environment; | |
import org.hibernate.dialect.Dialect; | |
import org.hibernate.engine.Mapping; | |
import org.hibernate.id.IdentifierGenerator; | |
import org.hibernate.id.IdentifierGeneratorAggregator; | |
import org.hibernate.id.PersistentIdentifierGenerator; | |
import org.hibernate.id.factory.IdentifierGeneratorFactory; | |
import org.hibernate.mapping.Collection; | |
import org.hibernate.mapping.Column; | |
import org.hibernate.mapping.IdentifierCollection; | |
import org.hibernate.mapping.PersistentClass; | |
import org.hibernate.mapping.Property; | |
import org.hibernate.mapping.RootClass; | |
import org.hibernate.mapping.Table; | |
import org.hibernate.tool.hbm2ddl.ColumnMetadata; | |
import org.hibernate.tool.hbm2ddl.DatabaseMetadata; | |
import org.hibernate.tool.hbm2ddl.TableMetadata; | |
import org.hibernate.type.Type; | |
/** | |
* This class overrides the default implementation of | |
* {@link #validateSchema(dialect, databaseMetadata) validateSchema} method. | |
* Basically, instead of validating the schema and throwing an exception when | |
* first error is found, this class gathers all the errors and prints them into | |
* the log in WARN level. This implementation allows the application to run | |
* anyway if any violation is found (the same way if 'hibernate.hbm2ddl.auto' | |
* validation is disabled). This is very useful to know how many violations | |
* existed at first glance, if we are facing just a few or a lot of them. <br> | |
* <br> | |
* Note: All code is borrowed from original Configuration class. We had to | |
* extend from deprecated AnnotationConfiguration class due to legacy reasons | |
* (we are using AnnotationSessionFactoryBean as a session factory and only | |
* accepts an AnnotationConfiguration as a configurationClass). Also remember | |
* that this validation is just triggered when hibernate.hbm2ddl.auto is set to | |
* 'validate'. | |
*/ | |
public class CustomSchemaValidationConfiguration extends | |
Configuration { | |
private static final long serialVersionUID = 3678466532340192490L; | |
private static final Logger LOGGER = Logger | |
.getLogger(CustomSchemaValidationConfiguration.class); | |
private final Mapping mapping = buildMapping(); | |
private final List<Exception> schemaViolations = | |
new ArrayList<Exception>(); | |
@Override | |
public void validateSchema(final Dialect dialect, | |
final DatabaseMetadata databaseMetadata) | |
throws HibernateException { | |
secondPassCompile(); | |
String defaultCatalog = | |
getProperties().getProperty(Environment.DEFAULT_CATALOG); | |
String defaultSchema = | |
getProperties().getProperty(Environment.DEFAULT_SCHEMA); | |
Iterator<Table> iterTables = getTableMappings(); | |
while (iterTables.hasNext()) { | |
Table table = iterTables.next(); | |
if (table.isPhysicalTable()) { | |
TableMetadata tableInfo = | |
databaseMetadata.getTableMetadata(table.getName(), | |
(table.getSchema() == null) ? defaultSchema | |
: table.getSchema(), | |
(table.getCatalog() == null) ? defaultCatalog | |
: table.getCatalog(), table.isQuoted()); | |
if (tableInfo == null) { | |
Exception e = | |
new HibernateException("Missing table: " | |
+ table.getName()); | |
schemaViolations.add(e); | |
} else { | |
try { | |
validateColumns(table, dialect, tableInfo); | |
} catch (HibernateException e) { | |
schemaViolations.add(e); | |
} | |
} | |
} | |
} | |
Iterator<IdentifierGenerator> iterGenerators = | |
iterateGenerators(dialect); | |
while (iterGenerators.hasNext()) { | |
PersistentIdentifierGenerator generator = | |
(PersistentIdentifierGenerator) iterGenerators.next(); | |
Object key = generator.generatorKey(); | |
if (!databaseMetadata.isSequence(key) | |
&& !databaseMetadata.isTable(key)) { | |
Exception e = | |
new HibernateException("Missing sequence or table: " | |
+ key); | |
schemaViolations.add(e); | |
} | |
} | |
if (!schemaViolations.isEmpty()) { | |
logViolations(); | |
} | |
} | |
private void logViolations() { | |
LOGGER.warn("There were discrepancies (" + schemaViolations.size() | |
+ ") between database and entity mappings."); | |
for (Exception e : schemaViolations) { | |
LOGGER.warn(e); | |
} | |
} | |
private void validateColumns(final Table table, final Dialect dialect, | |
final TableMetadata tableInfo) { | |
Iterator<?> iter = table.getColumnIterator(); | |
while (iter.hasNext()) { | |
Column col = (Column) iter.next(); | |
ColumnMetadata columnInfo = | |
tableInfo.getColumnMetadata(col.getName()); | |
if (columnInfo == null) { | |
Exception e = | |
new HibernateException("Missing column: " | |
+ col.getName() | |
+ " in " | |
+ Table.qualify(tableInfo.getCatalog(), | |
tableInfo.getSchema(), tableInfo.getName())); | |
schemaViolations.add(e); | |
} else { | |
final boolean typesMatch = | |
col.getSqlType(dialect, mapping) | |
.toLowerCase() | |
.startsWith(columnInfo.getTypeName().toLowerCase()) | |
|| columnInfo.getTypeCode() == col | |
.getSqlTypeCode(mapping); | |
if (!typesMatch) { | |
Exception e = | |
new HibernateException( | |
"Wrong column type in " | |
+ Table.qualify(tableInfo.getCatalog(), | |
tableInfo.getSchema(), | |
tableInfo.getName()) + " for column " | |
+ col.getName() + ". Found: " | |
+ columnInfo.getTypeName().toLowerCase() | |
+ ", expected: " | |
+ col.getSqlType(dialect, mapping)); | |
schemaViolations.add(e); | |
} | |
} | |
} | |
} | |
@Override | |
public Mapping buildMapping() { | |
return new Mapping() { | |
public IdentifierGeneratorFactory getIdentifierGeneratorFactory() { | |
return getIdentifierGeneratorFactory(); | |
} | |
/** | |
* Returns the identifier type of a mapped class | |
*/ | |
public Type getIdentifierType(final String entityName) | |
throws MappingException { | |
PersistentClass pc = classes.get(entityName); | |
if (pc == null) { | |
throw new MappingException( | |
"persistent class not known: " + entityName); | |
} | |
return pc.getIdentifier().getType(); | |
} | |
public String getIdentifierPropertyName(final String entityName) | |
throws MappingException { | |
final PersistentClass pc = classes.get(entityName); | |
if (pc == null) { | |
throw new MappingException( | |
"persistent class not known: " + entityName); | |
} | |
if (!pc.hasIdentifierProperty()) { | |
return null; | |
} | |
return pc.getIdentifierProperty().getName(); | |
} | |
public Type getReferencedPropertyType(final String entityName, | |
final String propertyName) throws MappingException { | |
final PersistentClass pc = classes.get(entityName); | |
if (pc == null) { | |
throw new MappingException( | |
"persistent class not known: " + entityName); | |
} | |
Property prop = pc.getReferencedProperty(propertyName); | |
if (prop == null) { | |
throw new MappingException("property not known: " | |
+ entityName + '.' + propertyName); | |
} | |
return prop.getType(); | |
} | |
}; | |
} | |
private Iterator<IdentifierGenerator> iterateGenerators( | |
final Dialect dialect) throws MappingException { | |
TreeMap<Object, IdentifierGenerator> generators = | |
new TreeMap<Object, IdentifierGenerator>(); | |
String defaultCatalog = | |
getProperties().getProperty(Environment.DEFAULT_CATALOG); | |
String defaultSchema = | |
getProperties().getProperty(Environment.DEFAULT_SCHEMA); | |
for (PersistentClass pc : classes.values()) { | |
if (!pc.isInherited()) { | |
IdentifierGenerator ig = | |
pc.getIdentifier().createIdentifierGenerator( | |
getIdentifierGeneratorFactory(), dialect, | |
defaultCatalog, defaultSchema, (RootClass) pc); | |
if (ig instanceof PersistentIdentifierGenerator) { | |
generators.put(((PersistentIdentifierGenerator) ig) | |
.generatorKey(), ig); | |
} else if (ig instanceof IdentifierGeneratorAggregator) { | |
((IdentifierGeneratorAggregator) ig) | |
.registerPersistentGenerators(generators); | |
} | |
} | |
} | |
for (Collection collection : collections.values()) { | |
if (collection.isIdentified()) { | |
IdentifierGenerator ig = | |
((IdentifierCollection) collection).getIdentifier() | |
.createIdentifierGenerator( | |
getIdentifierGeneratorFactory(), dialect, | |
defaultCatalog, defaultSchema, null); | |
if (ig instanceof PersistentIdentifierGenerator) { | |
generators.put(((PersistentIdentifierGenerator) ig) | |
.generatorKey(), ig); | |
} | |
} | |
} | |
return generators.values().iterator(); | |
} | |
public List<Exception> getSchemaViolations() { | |
return schemaViolations; | |
} | |
} |
How can you use this?
Hi Jelies
I have updated a project that came from hibernate and spring 3 to 5, they were using your script to validate the Schema, but it obviously does not work over hibernate and spring 5, as it uses for instance:
org.hibernate.engine.Mapping
This library seems to be part of the old hibernate, I changed by this one:
import org.hibernate.engine.spi.Mapping;
But then the method this.buildMapping() that initializes the mapping does not exist and the one that exists called .buildMappings() does nothing.
Is there a way you can provide this same behavior for hibernate 5 with Spring 5 ? thanks !!
Here's a similar solution that works with Hibernate 5 and Spring: https://gist.github.com/shawnz/8f7308741e2ff6270cc8878ac69b4f6b
Thanks Jelies for the idea, this was very helpful for us.
Happy that it helped you guys to come up with the updated example! :)
Hi Jelies.
I don't know how to make it work with jboss ear deployment