When you install an application for the first time, many times you want ‘bootstrap’ data to exist before the application is used for the very first time. Here’s a simple but easy technique to get bootstrap data inserted into your application upon first startup.

Prior to the following approach, I used to have ‘default.data’ and ‘sample.data’ tasks in my ant scripts that either ran SQL manually, or started up a separate Spring ApplicationContext and inserted data explicitly. You had to run it manually from ant, which 1) required that ant be installed and 2) that you had the project checked out on the box that you were installing data to. Many production and QA environments didn’t meet this criteria, and even if they did, it is still cumbersome and isn’t as nice as an ‘automatic’ solution.

My newer automatic solution moves this type of data insertion logic into the actual application, where it is better managed and tested. There is another benefit too though: Since I use Hibernate and JCR as the EIS-tier APIs, it is far easier to insert data via these standard APIs than to do so via raw SQL. This is because you can write all of your initial data objects and graphs with Java or Groovy POJOs, utilizing the benefits of powerful IDE coding features, and let the Hibernate and JCR frameworks insert all the data with dependencies for you: a massive time saver.

So, here’s a simple way to do this:

You create a POJO that will execute at application startup automatically after its dependencies have been injected. Here’s an example:


import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

public class BootstrapDataPopulator implements InitializingBean {

private transient final Log log = LogFactory.getLog( getClass() );

//Spring's transaction manager - needed to ensure data is inserted in a transaction:
//any failures, and we can roll back everything.
private PlatformTransactionManager transactionManager = null;

//all other managers/dependencies here

public BootstrapDataPopulator() {
}

public void setTransactionManager( PlatformTransactionManager transactionManager ) {
this.transactionManager = transactionManager;
}

//other dependency setter methods

public void afterPropertiesSet() throws Exception {
if ( this.transactionManager == null ) {
throw new IllegalStateException( "transactionManager property must be set." );
}
//assert other injected dependencies here

TransactionTemplate txnTemplate = new TransactionTemplate( this.transactionManager );
txnTemplate.afterPropertiesSet();

txnTemplate.execute( new TransactionCallback() {
public Object doInTransaction( TransactionStatus status ) {
try {
insertBoostrapData();
} catch ( Exception e ) {
//error - roll back. Runtime exception triggers rollback:
if ( e instanceof RuntimeException ) {
throw (RuntimeException)e;
} else {
throw new RuntimeException( e );
}
}
return null;
}
} );
}

private boolean guestUserExists() {
return userManager.findUserByUsername( "guest" ) != null;
}

protected void insertBoostrapData() throws Exception {
//if the guest user account exists, take this as a sign that the bootstrap data populator has
//already been run, so just exit quietly:
if ( guestUserExists() ) {
if ( log.isDebugEnabled() ) {
log.debug( "Bootstrap data insertion has already occurred. Returning quietly." );
}
return;
}

if ( log.isInfoEnabled() ) {
log.info( "Inserting bootstrap data..." );
}

//otherwise, populate bootstrap data:
log.debug("Creating guest user...");
User guestUser = someManager.create( new User( "Guest", "guest@domain.com" ) );

//insert all other objects and call any other helper methods here

}
}

The key to this code working as a POJO in your normal application configuration is due to two things:

  1. The insertBootstrapData() method is wrapped in the TransactionTemplate. If anything goes wrong during the execution of that method, it will be rolled back and the database remains in a consistent state
  2. The first bit of code in insertBootstrapData() checks to see if a well-known entity already exists in the database. If it does, you can assume that the bootstrap data has already been inserted, and return quietly.

You can create other ‘Bootstrap Beans’ like this to do other things as well. The above one initialized RDBMS data. You can have another one that initializes your JCR repository. Or another that checks against a license store to ensure that the application installed is allowed to be installed. All sorts of things…