Kohei Nozaki's blog 

Managing multiple JPA persistence units with guice-persist


Posted on Sunday Jun 19, 2016 at 06:15PM in Technology


Guice has an extension named guice-persist which aim for providing integration between Guice and a data persistence mechanism. it gives declarative transaction management functionality with the annotation @com.google.inject.persist.Transactional which works with a standalone environment or plain servlet containers such as Tomcat or Jetty.

guice-persist supports JPA and it’s simple to use with only one persistence unit, but to use it with multiple persistence units, it requires some tricks.

The official Guice wiki has only some brief description and I can’t find any complete example to implement it in an actual application. so, in this entry I’ll give you a complete example about how to write a module which manages multiple persistence units.

Module hierarchy

To manage multiple PUs in a module, you should create PrivateModule subclasses of the same number of your persistence units.

Let’s say we have two persistence units that one is named masterPU and another one named slavePU. For example, we have the following persistence.xml in an application:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="masterPU" transaction-type="RESOURCE_LOCAL">
...
    </persistence-unit>
    <persistence-unit name="slavePU" transaction-type="RESOURCE_LOCAL">
...
    </persistence-unit>
</persistence>

In this case, we are going to create and assemble classes as the following diagram:

1788876a 6b06 4be0 a5d1 4cc3c2897b23

MasterPu and SlavePu are qualifier annotations that used for distinguish multiple bindings of JPA classes.

Writing modules

So, how do you write those modules? I’ll show you some important parts of them.

Qualifier annotations

First you need to create the two qualifier annotations something like this:

import static java.lang.annotation.ElementType.*;

@javax.inject.Qualifier
@java.lang.annotation.Target({FIELD, PARAMETER, METHOD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface MasterPu {
}

Don’t forget about SlavePu as well.

JpaPersistPrivateModule

Now here’s the most important part - this class installs JpaPersistModule and rebinds and exposes JPA class bindings:

public class JpaPersistPrivateModule extends PrivateModule {
    protected final String persistenceUnitName;
    protected final Properties props;
    protected final Class<? extends Annotation> qualifier;

    public JpaPersistPrivateModule(final String persistenceUnitName, final Properties props, final Class<? extends Annotation> qualifier) {
        this.persistenceUnitName = persistenceUnitName;
        this.props = props;
        this.qualifier = qualifier;
    }

    public JpaPersistPrivateModule(final String persistenceUnitName, final Class<? extends Annotation> qualifier) {
        this(persistenceUnitName, new Properties(), qualifier);
    }

    @Override
    protected void configure() {
        install(new JpaPersistModule(persistenceUnitName).properties(props));
        rebind(qualifier, EntityManagerFactory.class, EntityManager.class, PersistService.class, UnitOfWork.class);
        doConfigure();
    }

    private void rebind(Class<? extends Annotation> qualifier, Class<?>... classes) {
        for (Class<?> clazz : classes) {
            rebind(qualifier, clazz);
        }
    }

    private <T> void rebind(Class<? extends Annotation> qualifier, Class<T> clazz) {
        bind(clazz).annotatedWith(qualifier).toProvider(binder().getProvider(clazz));
        expose(clazz).annotatedWith(qualifier);
    }

    /**
     * bind your interfaces and classes as well as concrete ones that use JPA classes explicitly
     */
    protected void doConfigure() {
        // write your bindings in your subclasses
        // bindConcreteClassWithQualifier(MyTableService.class);
        // ...
    }

    /**
     * binds and exposes a concrete class with an annotation
     */
    protected <T> void bindConcreteClassWithQualifier(Class<T> clazz) {
        bind(clazz).annotatedWith(qualifier).to(clazz);
        expose(clazz).annotatedWith(qualifier);
    }

    /**
     * binds and exposes a concrete class without any annotation
     */
    protected void bindConcreteClass(Class<?> clazz) {
        bind(clazz);
        expose(clazz);
    }
}

First, this class installs JpaPersistModule and it creates bindings of JPA classes without any annotation but those bindings will not be exposed globally because we are in a PrivateModule. then, this class rebinds them with a qualifier annotation and exposes them with qualifier annotation. eventually, bindings of the four JPA classes will be created with a qualifier annotation.

MyModule, MasterPuModule and SlavePuModule

public class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        install(new MasterPuModule());
        install(new SlavePuModule());
    }

    private static class MasterPuModule extends JpaPersistPrivateModule {
        public MasterPuModule() {
            super("masterPU", MasterPu.class);
        }

        @Override
        protected void doConfigure() {
            bindConcreteClassWithQualifier(MyTableService.class);
        }
    }

    private static class SlavePuModule extends JpaPersistPrivateModule {
        public SlavePuModule() {
            super("slavePU", SlavePu.class);
        }

        @Override
        protected void doConfigure() {
            bindConcreteClassWithQualifier(MyTableService.class);
        }
    }
}

This class installs two JpaPersistPrivateModule subclasses for persistence units and binds a service class named MyTableService which requires injection of EntityManager. this module creates two distinct annotated bindings for the class.

Note that if you need declarative transaction management by @Transactional for your service classes, you should create bindings of them inside doConfigure(). for example, if you creates such bindings in MyModule#configure(), declarative transactions won’t work.

How about PersistFilter?

If you need PersistFilter for those two modules, you need to create a binding for each modules inside doConfigure() as follows:

Key<PersistFilter> key = Key.get(PersistFilter.class, qualifier);
bind(key).to(PersistFilter.class);
expose(key);

Then, install all of modules inside your subclass of ServletModule. after that, create filter mappings inside configureServlets() as follows:

filter("/*").through(Key.get(PersistFilter.class, MasterPu.class));
filter("/*").through(Key.get(PersistFilter.class, SlavePu.class));

Conclusion

We have seen an example of a Guice module that manages two persistence units with guice-persist. check my GitHub repository for the complete example project and testcases.



Comments:

While this solves for multiple persistence unit, can we have these units in different jars?

I want to store common daos in a different jar. The idea is to store separate persistence.xml files. The modules entities specified in its own file and allow it to inherit other entities via dependencies.

Posted by anor1k on November 28, 2017 at 07:26 AM JST #


Leave a Comment

HTML Syntax: NOT allowed