Incremental schema upgrades using Hibernate
August 28th, 2006I have been inspired by recent discussions on upgrade frameworks to show how hibernate can be used to provide simple incremental database schema maintenance. Database schema maintenance is one of the more difficult aspects of upgrading applications, particularly when the application supports multiple databases, so I am very happy that hibernate helps out during upgrades.
SchemaUpdate
Hibernate provides a class called SchemaUpdate that is able to synchronise a set of hibernate mappings with a database schema. The following code snippet shows how easy it is:
Configuration config = new Configuration();
Properties props = new Properties();
props.put(“hibernate.dialect”, “org.hibernate.dialect.HSQLDialect”);
props.put(“hibernate.connection.provider_class”,
“com.zutubi.pulse.upgrade.tasks.UpgradeTaskConnectionProvider”);
// slight hack to provide hibernate with access to
// the configured datasource via a static variable
// on our ConnectionProvider implementation.
UpgradeTaskConnectionProvider.dataSource = dataSource;
// use spring to help load the classpath resources.
for (String mapping : mappings)
{
ClassPathResource resource =
new ClassPathResource(mapping);
config.addInputStream(resource.getInputStream());
}
// run the schema update.
new SchemaUpdate(config, props).execute(true, true);
This example uses the spring ClassPathResource to load the mappings file from the classpath, and the UpgradeTaskConnectionProvider to inject a datasource into the process.
.hbm.xml fragments
This by itself is not overly interesting. What people usually do not realise is that the mappings files do not need to hold your entire schema. When making incremental changes to your schema, all you need in the mappings are those incremental changes. This comes in very handy when you have lots of mappings to manage.
For example. You have the following mapping of a user:
<id name=“id” type=“java.lang.Long” column=“ID”>
<generator class=“hilo”/>
</id>
<property name=“login” column=“LOGIN” type=“string”/>
<property name=“name” column=“NAME” type=“string”/>
</class>
Some time later, you want to store a password field with this user. By passing the following mapping to the SchemaUpdate, it will add that column to your existing table, leaving the existing schema as it is.
<id name=“id” type=“java.lang.Long” column=“ID”>
<generator class=“hilo”/>
</id>
<property name=“pass” column=“PASS” type=“string”/>
</class>
You still need to ensure that the mapping file is valid, hence the inclusion of the ID field in the second mapping.
Versioning
So, to support incremental schema upgrades within your application, you will need to keep two sets of hibernate mapping files. The first will be the latest version of your mappings. This is what is used for new installations. The second will be a set of versioned mapping fragments as described above.
You will need to version them so that you can track which fragments you need to apply and in which order, based on the version of the schema you are upgrading from. I use directory names like build_010101 to store my schema fragments and a properties file to store the current schema version. Other people use a special table in the database to hold the current schema version. Use which ever is most appropriate to your situation.
Generating upgrade SQL
For those of you that do not want or can not allow Hibernate to run the schema update, you can use the following code to generate the SQL that Hibernate would otherwise execute:
Connection connection = dataSource.getConnection();
DatabaseMetadata meta =
new DatabaseMetadata(connection, dialect);
String[] createSQL =
config.generateSchemaUpdateScript(dialect, meta);
This code would replace the last line in the first example.
Things to remember about SchemaUpdate
Okay, so just a couple of final things to be aware of with hibernates schema update.
The hibernate schema update will:
- create a new table
- add a new column
The hibernate schema update will not:
- drop a table
- drop a column
- change a constraint on a column
- add a column with a not-null constraint to an existing table
Final tip
Oh, and the class name that you provide in the update mapping can be anything you want. It is not checked, which is great, otherwise you would need to handle versioning of your class files as well.
Happy upgrading!