<?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>
Managing multiple JPA persistence units with guice-persist
TweetPosted 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:
In this case, we are going to create and assemble classes as the following diagram:
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.