Recently I had the not altogether unfamiliar requirement to have two completely different database configurations for my development environment and the live/production environment. It wasn‘t just JDBC connection details either. Locally, my Hibernate configuration was using a direct JDBC connection. The production environment requires the use of a container-managed data source.
What I could do, of course, is manually change the Hibernate configuration each time I deploy. However, I‘m lazy (a quality, which if applied correctly is good in a software developer) and I hate performing tasks that expensive electronics I‘ve bought should be able to do quicker and better than me. So I drew on my experience with Rails and had a look what was available.
The Rails way
One thing Rails does very well is the separation of production and development environments (as well as test). It allows you to configure different database connections and parameters, configure different behavior for your software (e.g. put ActiveMerchant into :production mode) and also has sensible defaults for logging levels etc. It does this by having different bootstrapping files each intuitively called development.rb, production.rb and test.rb. All in a folder conveniently called environments.
Though I‘ve never had cause to myself, you can add new environments of your own and reconfigure Rails to use it. One useful additional environment would be staging perhaps for running on the test server, we certainly wouldn‘t want ActiveMerchant creating real transactions for our end-user testing.
Maven
Maven 2 is fairly ubiquitous in Javaland now. With significant improvements over Ant, it's quickly becoming the Maker of choice. Since the project in question employs Maven for building anyway it is the obvious place to start looking for a solution.
Profiles
Maven profiles allow a wide variety of the project configuration to be overridden given the activation of a profile. This allows almost wholesale changes to the way a project is built, it‘s dependencies and even where it references it‘s sources.
My Solution
To recap, in my development environment, Hibernate uses a direct JDBC connection, configured like this:
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/my_db</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.username">dev</property>
<property name="connection.password">dev</property>
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- all the mappings -->
</session-factory>
</hibernate-configuration>
But my live environment uses a container-managed data source, configured like this:
<session-factory>
<property name="connection.datasource">java:comp/env/jdbc/live_datasource</property>
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- all the mappings -->
</session-factory>
</hibernate-configuration>
Filtering resources
If it was a simple matter of changing connection details I could filter the resources by using Maven filtering replacing connection details with defined properties from pom.xml. Using something like the following in pom.xml ...
<!-- ... -->
<build>
<!-- ... -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>hibernate.cfg.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>hibernate.cfg.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
</resources>
<!-- ... -->
</build>
<!-- ... -->
<profiles>
<profile>
<id>env-production</id>
<activation>
<property>
<name>env</name>
<value>production</value>
</property>
</activation>
<properties>
<jdbc.url><![CDATA[jdbc:mysql://localhost/live_db]]></jdbc.url>
<jdbc.username>live_user</jdbc.username>
<jdbc.password>live_pass</jdbc.password>
</properties>
</profile>
</profiles>
<properties>
<!-- ... -->
<hibernate.dialect>org.hibernate.dialect.MySQL5InnoDBDialect</hibernate.dialect>
<jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
<jdbc.url><![CDATA[jdbc:mysql://localhost/my_db]]></jdbc.url>
<jdbc.username>dev</jdbc.username>
<jdbc.password>dev</jdbc.password>
</properties>
</project>
... and the following in hibernate.cfg.xml ...
<session-factory>
<property name="connection.url">${jdbc.url}</property>
<property name="connection.driver_class">${jdbc.driverClassName}</property>
<property name="connection.username">${jdbc.username}</property>
<property name="connection.password">${jdbc.password}</property>
<property name="dialect">${hibernate.dialect}</property>
<!-- DB schema will be updated if needed -->
<property name="hbm2ddl.auto">update</property>
<!-- all the mappings -->
</session-factory>
</hibernate-configuration>
Using this method I could run mvn package -Denv=production and a WAR would be generated with the live database configuration populated into my hibernate.cfg.xml. However, in my case the Hibernate configuration doesn‘t have the same properties.
Defining different production resources
The answer I came up with is to define some completely separate resources for live packaging. Because I didn‘t want all the common configuration – like the mappings – duplicated I‘ve moved some of the database configuration into hibernate.properties. So now I have the following in my pom.xml ...
<!-- ... -->
<build>
<!-- ... -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>hibernate.cfg.xml</include>
<include>hibernate.properties</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>hibernate.cfg.xml</exclude>
<exclude>hibernate.properties</exclude>
</excludes>
<filtering>false</filtering>
</resource>
</resources>
<!-- ... -->
</build>
<!-- ... -->
<profiles>
<profile>
<id>env-production</id>
<activation>
<property>
<name>env</name>
<value>production</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/production/resources</directory>
<includes>
<include>hibernate.properties</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<hibernate.datasource>java:comp/env/jdbc/live_datasource</hibernate.datasource>
</properties>
</profile>
</profiles>
<properties>
<!-- ... -->
<hibernate.dialect>org.hibernate.dialect.MySQL5InnoDBDialect</hibernate.dialect>
<jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
<jdbc.url><![CDATA[jdbc:mysql://localhost/my_db]]></jdbc.url>
<jdbc.username>dev</jdbc.username>
<jdbc.password>dev</jdbc.password>
</properties>
</project>
... and the following files in src/main/resources and src/production/resources respectively:
hibernate.connection.url=${jdbc.url}
hibernate.connection.driver_class=${jdbc.driverClassName}
hibernate.connection.username=${jdbc.username}
hibernate.connection.password=${jdbc.password}
hibernate.connection.datasource=${hibernate.datasource}
You may notice an additional/replacement dependency in the production profile. This is because for container managed data sources the JDBC driver needs to be known to the container before it loads any web applications. Therefore, in Tomcat‘s case it should already be in the common/lib folder.
Using Maven profiles may not be the best solution for achieving different database configuration. However I think it definitely has promise in terms of a more general environment-specific packaging/deployment.
The Maven website has a good article that adopts a different approach to building for different environments here.
First published on Nov 26, 2008. Last updated on: Dec 30, 2009.
Update: I recently found a link to this great maven book on Rick Hightower blog.
There is a great section on using profiles for different purposes here.
Thanks for this — took a while to find, but very helpful!
Thanks for the post mate, this explained it very well. The maven war plugin documentation wasn‘t able to explain how simple this kind of thing really is.
Thanks for that article, we needed to set this up earlier. Would have saved a lot of time setting up our pilot environment as it was picked up the default dev properties instead of failing fast with a property-not-set exception!