Play 2.4 with Guice and MyBatis

Timo Loist

The Play Framework is getting more popular and Version 2.4 finally includes Dependency Injection as the default mechanism to access central resources instead of singletons.

I personally loathe the singleton pattern, perhaps more than it deserves. However this made me gladly jump on the train for version 2.4.

This will be a tutorial how to use MyBatis with Guice and the Play Framework. Why this tech stack?

  • I really like the approach taken by the Play Framework. This potentially means I would like Ruby on Rails as well, but I prefer statically compiled languages, so well, I use Play.
  • Guice is the default dependency injection framework in Play, but on top of that it stores its configuration in Java (instead of e.g. XML).
  • MyBatis is traditionally configured in XML but offers an integration with Guice as well. It is a persistence framework (non-JPA) which is SQL oriented.

There is a code sample accompanying this blog post available at GitHub.

Background

Working on my main project from time to time the need to investigate certain incidents arised. For this I needed to some reports taken from the database, which are not normally correlated by the application. However for reasons not discussed here, this could not be part of the main application.

What I did instead is to create a maintenance application with Play and Java, which worked on the same DB schema and read the same data as my main project, however it was primarly focussed on analyzing these incidents. For this I needed a persistence layer which has non-commercial support for Oracle Databases in Java (which removes Slick or jOOQ from my list), is highly customizable and can be integrated to an already existing database. That left me with with the following options (as far that I know):

  • eBean which was the primary persistence framework in Play before Play–1518.
  • MyBatis which I had prior experience with
  • JPA as a default fit on Java.

As you have guessed by now I have chosen MyBatis for its adaptability (You can adapt JPA to some degree but I needed more - I know because I tried…). I decided against eBean mainly because of next to no experience with it and the reason it was deprecated in Play scared me away of it.

Existing resources

If you look for how to set up this tech stage you will come across Inoio’s nice tutorial for Play 2.1 which has an updated version done by Alex Klibisz which is nearly up to date (Play 2.3.6).

They do a great job in showing a possible way of doing things, however they don’t go into detail about the reasoning why they do it the way they do. This is helpful information in case something goes wrong or if you want to deviate from the choices they took. I personally always want certain decisions a bit differently, which is why nearly all resources almost never fully match the setup I want.

To quote something I learned from a Videogame (Sid Meier’s Civilization which claims the origin is a man named Lao Tzu):

Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime

I want this post to be different, instead of showing you a possible implementation and let you start from there, I want to take you on a journey on why I did it the way I did - so that you can better understand my choices and deviate from them in case you don’t.

Step 1: Configure build.sbt

We need to add the needed libraries, so we add

libraryDependencies ++= Seq(
    javaJdbc,
    "org.mybatis" % "mybatis" % "3.3.0",
    "org.mybatis" % "mybatis-guice" % "3.6",
    "com.google.inject.extensions" % "guice-multibindings" % "4.0"
)

to the build.sbt file. Let’s see what we’ve just added here:

  • org.mybatis.mybatis:3.3.0 - the MyBatis dependency is straight forward. That is the persistence framework we wan’t to use.
  • org.mybatis.mybatis-guice - MyBatis also offers a Guice integration, which plays nicely into the setup Play already has (beginning from version 2.4). This seems to be a good fit. It brings us the org.mybatis.guice.configuration.ConfigurationProvider, which is basically where we inject deviating configuration into. It can also serve as a place to look for possible configurations, but be aware that if you miss an option there, be sure to check the MyBatis XML Configuration as well, as it seems this is the primary maintained source.
  • com.google.inject.extensions:guice-multibindings - the Guice-Multibinder extension is actually a dependency for MyBatis-Guice. If you miss to add it, you might be greeted with something like java.lang.NoClassDefFoundError: com/google/inject/multibindings/MapBinder.
  • javaJdbc - is a needed dependency to add the Play Database support and to access the Connection Pool / DataSource - namely the play.db.DBModule which provides access to the Database class we need.

Note that there should be the following line present in your build.sbt, which means that we want to use the new dependency injecting router. (This is the default when creating new projects, e.g. by activator new - but if you migrate you need to add it manually).


routesGenerator := InjectedRoutesGenerator

Step 2: Configure MyBatis

MyBatis Guice offers us a base module to inherit from: org.mybatis.guice.MyBatisModule. so we define a module

public class MyBatisModule extends org.mybatis.guice.MyBatisModule {
    
    @Override
    protected void initialize() {
        environmentId("development");
        bindConstant().annotateWith(
            Names.named("mybatis.configuration.failFast")).
            to(true);
        bindDataSourceProviderType(PlayDataSourceProvider.class);
        bindTransactionFactoryType(JdbcTransactionFactory.class);
        addMapperClass(UserService.class);
    }
        
    /*
     * Provides a {@link DataSource} from the {@link Database}
     * which can be injected from Play.
     */
    @Singleton
    public static class PlayDataSourceProvider implements Provider<DataSource> {
        final Database db;
            
        @Inject
        public PlayDataSourceProvider(final Database db) {
            this.db = db;
        }
            
        @Override
        public DataSource get() {
            return db.getDataSource(); 
        }
    }
}

Note that we don’t override configure as usual in Modules because it is already finally implemented in org.mybatis.guice.AbstractMyBatisModule, which delegates to initialize instead.

  1. The environment ID is a mandatory configuration for MyBatis, but can be useful where we need different environments. This is basically the equivalent to the XML Configuration of the environment.
  2. The mybatis.configuration.failfast is a Boolean property, which causes the configuration to eagerly load some configurations. This will cause myBatis to provide us earlier with error messages.
  3. The PlayDataSourceProvider is a link to the class below. The Database class will be provided by play at runtime, but it is not available at the time the configuration is wired so it is not possible to inject it directly into the module. The only things that can be injected directly into modules are the Environment and the Configuration classes, see the example from the documentation (additional note: with exactly this signature!).

    With declaring a separate class (the PlayDataSourceProvider) which is dependent on the Database, we can delay this dependency and then Guice will take care of the dependency for us.

    Also even if I loathe the Singleton pattern, I am fine with something being a Singleton, that is in this example I am fine with the @Singleton annotation. That is because this one is easily exchangeable and the Injection Framework guarantees its uniqueness. A choice which can be easily revoked which is in contrast to the use of the pattern. There are also additional reasons, but well, that’s another topic.
  4. The TransactionFactory creates, well, Transactions for us. JdbcTransactionFactory is the proper choice for us here.
  5. addMapperClass(UserService.class) registers UserService as a mapper, which directly leads us to the next chapter.

Step 3: Add Services / Mapper to the project

Let’s say we want to add our service to app/service/UserService.java. We define an interface like in the MyBatis Getting Started

public interface UserMapper {
    List<User> all();
    
    User getUserById(Long id);
}

However this time I don’t go for the Annotation based approach (e.g. @Select("SELECT * FROM Users ORDER BY id")) which would also be possible, like in the tutorial, but elaborate on the

[…] a MyBatis mapper XML file could also be used

part of the Getting Started Guide for multiple reasons:

  • The annotation based approach is already covered by the other tutorials.
  • I thought that for my running example I needed more power and MyBatis is still primarly focussed on XML configuration (even though I am using the MyBatis-Guice integration and not the XML configuration).

    In hindsight I didn’t use the more powerful features, so the annotation approach might have been sufficient as well.

For that to work we add a UserMapper.xml file right next to the UserMapper.java file. It is important that the file is under app/service/UserMapper.xml (Note: this decision will be revoked later in the post but is the very first approach I took).

app is the default source root in play applications, so the full qualified name of the UserMapper should be service.UserMapper and could look like Mapped SQL Statement out of the Getting started, however the namespace attribute should match the fully qualified name of the Interface and the Id of the Select statement should be getUserById to match the method from the interface.

However this would still fail, because Play won’t copy the XML file to the classpath. The answer is already provided by the previous tutorials:

// Add app folder as resource directory so that mapper xml files are
// in the classpath...
unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "app" )
  
// ...but filter out java and html files that would then also be copied
// to the classpath.
excludeFilter in Compile in unmanagedResources := "*.java" || "*.html"

Wire it together in the controller

Now lets reap the fruits of our previous labour: Use MyBatis in conjunction with Guice in Play 2.4 depency injected:

public class Application extends Controller {
    private UserMapper userService;

    @Inject
    public Application(UserMapper userMapper) {
        this.userService = userMapper;
    }

    public Result listUsers() {
        return ok(users.render(userService.all()));
    }

    public Result showUser(Long id) {
        return ok(profile.render(userService.getUserById(id)));
    }

}

This is a basic example how to use it. It requires:

  • The UserMapper which is an instance of the previously defined interface and which will be injected by Guice into the controller.
    For details on how to use the XML configuration please see the documentation for Mapped SQL Statement or the corresponding working example in the GitHub Repository.
  • Some view templates users and profile with the corresponding parameters.

For more enhanced examples it might necessary to have a look at the @Transactional annotation. This is a feature of MyBatis-Guice which automatically wraps the call in a transaction. For these simple select statements it seems not to be necessary.

Step 3.5: Inspect the Build or What are we doing here?

Now we have a fully working example, but let’s take a step back and review what we have done to achieve this. The lines which caught my attention were those:

// Add app folder as resource directory so that mapper xml files are
// in the classpath...
unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "app" )
  
// ...but filter out java and html files that would then also be copied
// to the classpath.
excludeFilter in Compile in unmanagedResources := "*.java" || "*.html"

A good approach to get to know what happens here is that SBT (or Activator or the Play CLI which are all basically SBT) allows us to inspect the build. So let’s do this by entering sbt and hopefully we find out why this all works together.

inspect unmanagedSources

...snip...
[info] Reverse dependencies:
[info]  compile:resources
...snip...

inspect compile:resource
[info] Reverse dependencies:
[info]  compile:copyResources

So basically we determined that the copyResources task (in the compile scope) transitively depends on unmanagedResources which is why this advice is useful. Even better, we can now show the value of the property

show compile:copyResources

[info] ArrayBuffer(
    ...more...,
    <PrjRoot>/app/sql/services/UserMapper.xml,
    <PrjRoot>/target/scala-2.11/classes/sql/services/FieldService.xml),
    ...more... )

(Sadly the indentation is not that nice if you do it yourself)

So we can now even validate if the resource is copied to the target directory. However what I dislike is that the we now have merged the resource directory with the source directory, which was previously not the case. To make things worse it is done for the whole tree!

Can we be more precise?

So let’s assume we didn’t follow the advice given from the previous tutorial and would like it to be more specific (don’t include the whole app directory, just the app/services directory) like this:

unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "app" / "services" )

Which would result in the UserMapper.xml to reside under <PrjRoot>/target/scala-2.11/classes/UserMapper.xml.

Starting the application, sadly we get to know that this does not work. Based from our Configuration class, where the mapper is added the the framework, we can trace the call down to org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource where we can see that it looks for the UserMapper under services. In case we added app/services as another root it would flatten the directory path and this lookup would fail, because then it should look for UserMapper.xml instead of services/UserMapper.xml (derived from the package name).

This is the reason why we need to add the app root directory to the unmanagedResourceDirectories. However as a side effect this will add all other classes and directories to the path as well - so this is why we filter them out again with:

excludeFilter in Compile in unmanagedResources := "*.java" || "*.html"

That left me with a bad feeling. Instead of white listing the files we want to add, we now added all and blacklisted (filtered) all java and html files instead.

Step 4: Separating the resource tree from the source tree again

This works for SBT, however it overlaps the source directory with the resources directory. The addition of the excludeFilter is an indicator for this. What’s worse the IDE I am currently working with (IntelliJ) gets confused with it and takes all Java files as resources.

However we now have the knowledge to do this better. What we ultimatevily want is that the UserMapper.xml should reside under services in the runtime classpath. What we have done so far is to ‘fix’ SBT to include the XML file from the source directory.

Benefit: 
The XML file lies next to the source file in the source tree.
Disadvantage: 
This mixes up source and resource directories.

The answer to that is an easy one. We added the app directory to the unmanagedResourceDirectories, so now it consists of app and conf (Tip: you can check that yourself by inspecting the property, remember?). What if we include the file to conf/services to begin with?

Benefit: 
No mixture of source and resource path and we don't need to filter the resources to not contain java files.
Disadvantage: 
The XML file is now not lying right next to the interface source file.

I prefer the separation of the directory structure and not to mess with the build system, so I take the conf route. However I think I could also live with the other approach if I could limit the effect to mix both trees (resource and source) only to the app/services folder. This would make the build more complicated (because we then need to apply a custom rule for the resources). I am pretty confident that this would be possible as well with SBT, but I didn’t follow that path because I was satisfied with the conf folder solution.

Other useful things: Logging

As MyBatis is just a short wrapper around SQL it could be useful to see the final generated SQL scripts. This is particular useful if you have a highly dynamical script where the script is constructed with multiple conditions. The only thing needed to see the final SQL statement is to configure the logger accordingly.

To enable logging for the UserMapper, which fully qualified name is service.UserMapper just add the following snippet to the default conf/logback.xml:

<logger name="service.UserMapper" level="DEBUG" />

This configures the statements of this mapper to be logged.

The GitHub Example doesn’t use an Oracle but an H2 Database, where you can of course also enable the trace of the H2 database instead (remove the TRACE_LEVEL_FILE=0 part from the JDBC Url in the application.conf file).

Where to go from here

Hopefully you are now confident how to configure Play 2.4 to work with MyBatis as a persistence layer. Also you should have a basic understanding how both frameworks interact with each other and what are the things we needed to configure in Play to support MyBatis and why.

There are multiple further routes you could take now:

  • I am almost sure you are already familiar with the Play Framework and Guice, however in case you are not I highly recommend to read into those two!
  • If you are interested how the build works and how you can interact with SBT further, I would recommend the blog posts SBT - A Task Engine by James Roper. Potentially you can take a look and see if you can merge the resource and source tree only for app/service but still package the XML configuration under a path where it can be found at runtime.
  • If you want to tune MyBatis more specifically to your needs, I would recommend to read the MyBatis documentation. Specifially the XML documentation. The Guice configuration worked well for me so far but MyBatis primarly supports the XML configuration, so potentially there are things that can only be done there.

Also I would recommend to validate if MyBatis is the persistence framework you want to use at all or if you prefer an Object Relational Mapper (e.g. Hibernate) which take a different approach.

Thumb profil

Timo is at home on the JVM and grew up with Java. Starting with Scala he acquired a taste for functional languages as well as for the topics of automatic build management and software architecture. He works for innoQ Germany as a Consultant.

More content

Comments

Please accept our cookie agreement to see full comments functionality. Read more