implements Elegance {

// Elwyn Malethan's musings on software development, mountain biking and general navel–gazing...

Production configuration using Maven Profiles

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:

<hibernate-configuration>
    <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:

<hibernate-configuration>
    <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 ...

<project  ... >

    <!-- ... -->

    <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 ...

<hibernate-configuration>
    <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 ...

<project  ... >

    <!-- ... -->

    <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:

Development Hibernate Configuration
hibernate.connection.url=${jdbc.url}
hibernate.connection.driver_class=${jdbc.driverClassName}
hibernate.connection.username=${jdbc.username}
hibernate.connection.password=${jdbc.password}
Production Hibernate Configuration
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.

Comments (4)

Leave a reply »

 
Ben Douglas
 

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!

Leave a Reply

Your name
 
Email
 
Website
 
Comment

You may use Textile notation here
 
 

Please perform this simple arithmetic test to prove you are human