Guice up Hibernate with Warp Persist

Warp Persist is a persistence integration library for Google Guice. It comes with built-in support for Hibernate, JPA, and db4o. Warp Persist proved to be lightweight and unobstrusive. Especially useful in our case was its runtime support for multiple databases, besides other features, such as declarative transactions or dynamic finders.

To begin with, Warp Persist has an API with a fluent interface for creating a Guice module for persistence support. Such a module needs to be installed along with other modules your application may require. For Hibernate, it creates bindings for Session and SessionFactory which may then be injected into your DAOs for example.

Configuration

When configuring Warp Persist you need to think about the scenario you want to use it in. In a desktop or command-line application you’ll probably want to have one Hibernate session per transaction. Conversely, in a Web application one session per request is most likely preferable, the latter often being referred to as open-session-in-view pattern. In any case, you need Warp Persist to associate the session with a so called unit of work. Below I’ll show how to create a Guice module and explain what else needs to be configured.

UnitOfWork.TRANSACTION

Each transaction gets its own Hibernate session in this scenario. Hibernate opens a new session for each transaction and closes it on commit or rollback. For this to work, the Hibernate property current_session_context_class must be set to thread. Warp Persist relies on a session being available using SessionFactory.getCurrentSession().

Module warpModule = PersistenceService.usingHibernate()
    .across(UnitOfWork.TRANSACTION)
    .buildModule();

UnitOfWork.REQUEST

In this scenario, the Hibernate session is associated with an HTTP request. Warp Persist must handle the opening and closing of Hibernate sessions itself because the lifetime of a session is not limited to a transaction, i. e. you can have multiple transactions per session and HTTP request. Warp Persist’s PersistenceFilter takes care of this. The Hibernate property current_session_context_class must be set to managed.

Module warpModule = PersistenceService.usingHibernate()
    .across(UnitOfWork.REQUEST)
    .buildModule();

You need to configure the PersistenceFilter in your web.xml file. Make sure it is the first filter.

<filter>
  <filter-name>warpPersistFilter</filter-name>
  <filter-class>com.wideplay.warp.persist.PersistenceFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>warpPersistFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Alternativley, if you use the Guice Servlet Extension, you need the following filter configuration:

filter("/*").through(PersistenceFilter.class);

Custom Unit of Work

In a Web application with UnitOfWork.REQUEST, you may want to execute database operations outside the scope of an HTTP request, for example in some background thread or during startup. Because the PersistenceFilter does not come into play here, you need to use the WorkManager in order to create your custom unit of work.

public class MyBackgroundTask {
    private final WorkManager unitOfWork;

    @Inject
    public MyBackgroundTask(final WorkManager unitOfWork) {
        this.unitOfWork = unitOfWork;
    }

    public void doSomeWork() {
        unitOfWork.beginWork();

        try {
            // call @Transactional methods
        } finally {
            unitOfWork.endWork();
        }
    }
}

Declarative Transactions

Warp Persist supports declarative transactions by annotating methods with @Transactional. Such methods require a transaction. Already existing transactions are joined. Other propagation settings cannot be configured. It is also possible to annotate classes in order to make all methods in that class transactional. Warp Persist uses Guice AOP interceptors in order to wrap transaction code around methods.

@Transactional
public void doSomethingTransactional() {

}

Rollback behavior may be controlled, e. g.:

@Transactional(rollbackOn = { IOException.class, RuntimeException.class, ... })

@Transactional(rollbackOn = IOException.class, exceptOn = FileNotFoundException.class)

For Hibernate only, read-only transactions are supported:

@Transactional(type = TransactionType.READ_ONLY)

Dynamic Finders

Methods that are supposed to execute Hibernate queries may be annotated with @Finder. This tells Warp Persist to intercept calls to these methods and to generate the implementation. Named queries and normal HQL queries are supported. Parameters may be passed using Guice’s @Named annotation.

@Transactional
@Finder(namedQuery = "getFoosByBarId")
public List<Foo> getFoosByBarId(@Named("barId") final Long barId) {
    // this is never hit
    throw new AssertionError();
}

Support for Multiple Databases

In one of our projects we have to access three different databases at runtime. Warp Persist persist can handle this very nicely. All it takes is an annotation for each database (@Named may be used as well) plus a Guice module for each Hibernate configuration as outlined below.

// Create Hibernate configuration, e. g. AnnotationConfiguration
Configuration configuration =
    new AnnotationConfiguration().configure(configFile);

PersistenceStrategy strategy =
    HibernatePersistenceStrategy.builder()
        .configuration(configuration)
        .annotatedWith(MyAnnotation.class)
        .build();

Module module = PersistenceService.using(strategy)
    .across(UnitOfWork.TRANSACTION) // or UnitOfWork.REQUEST
    .forAll(Matchers.annotatedWith(MyAnnotation.class),
        Matchers.annotatedWith(Transactional.class))
    .buildModule();

Warp Persit internally uses the specified annotation to further qualify the bindings, which is necessary because we want to have different bindings for different Sessions, SessionFactories, etc. You now need to use this annotation in your DAOs for example so Warp Persist knows which database should be accessed.

@Singleton
@MyAnnotation // tells Guice which transaction interceptor to use
public class FooDaoImpl extends AbstractWritableDao<Foo, FooId> implements FooDao {

    @Inject
    public FooDaoImpl(@MyAnnotation final SessionFactory sf) {
        super(sf, Foo.class, FooId.class);
    }
}

Conclusion

Warp Persist is a really nice addition to Guice which saves you from coding much of the typical boilerplate. It is currently being integrated into Guice by its creator Dhanji R. Prasanna who now works for Google and is also the author of the highly recommended book “Dependency Injection”.

Share

Leave a Reply

*

One Response to “Guice up Hibernate with Warp Persist”

  1. Tercio Filho says:

    Clear and objective. very good, I’d like to have this when I was starting with Warp-persist :-) . Thank.,

    Tercio