Switching Datasource with CDI Alternatives and Stereotypes

Here we are, using H2 in our test environment, Derby in development and Postgres in production. It’s 2014, and Java EE still doesn’t have a decent configuration specificationSo how do we change datasources in Java EE depending on our environment ? There are several possibilities (from external property files, Maven resource filtering, Maven profiles with different configuration or JNDI tricks) but this post will only concentrate on one: switching datasource with CDI alternatives and stereotypes.

Use Case

Let’s say we have a Java EE web application (a CRUD application for a Speaker entity using JSF, EJB, JPA and CDI), running on JBoss, and we need to easily switch datasources: a H2 in-memory datasource in our test environment, a Derby datasource for development and a Postgres datasource in production. Each datasource has a unique JNDI name and points to a database (either in-memory or server). Then, with just a bit of XML configuration (yes, Java EE still uses XML, not JSon ;o) we want to switch datasources. This is how it looks like in a nice diagram:

 

Switch Datasource with CDI Alternatives DS

Notice the @DevDatabase and @ProdDatabase in the diagram. You will see later that these annotations are, in fact, CDI stereotypes and they will be very helpful in solving our use case.

Datasource Configuration

JBoss Console with DataSource

JBoss Console with the three DataSources

Something a bit painful in Java EE is how you create and configure new datasources. Thanks to @DataSourceDefinition annotation brought in Java EE 6, we can now create new datasources using an annotation in our code.  I won’t be doing this here, as I prefer to use JBoss admin CLI. The following commands will create a datasource for H2, Derby and Postgres (check the jboss-setup.cli script because you need to create the Derby and Postgres JDBC drivers) and the result can be seen in the JBoss Console:


/subsystem=datasources/data-source=H2DS:add(driver-name=h2, user-name=sa, password=sa, connection-url="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", jndi-name=java:/global/datasources/H2DS, enabled=true)
/subsystem=datasources/data-source=DerbyDS:add(driver-name=derby, user-name=app, password=app, connection-url="jdbc:derby://localhost:1527/sample;create=true", jndi-name=java:/global/datasources/DerbyDS, enabled=true)
/subsystem=datasources/data-source=PostgresDS:add(driver-name=postgres, user-name=postgres, password=postgres, connection-url="jdbc:postgresql://localhost:5432/postgres", jndi-name=java:/global/datasources/PostgresDS, enabled=true)

Now that our JBoss knows these three datasources, let’s configure them in our persistence.xml file.

JPA Persistence Units

In a persistence.xml file we can have several persistence units. So the idea is to create three persistence units, each one pointing to a different datasource. So we will have:

  • alternative-test-pu the test datasource pointing to H2 in-memory at java:/global/datasources/H2DS
  • alternative-dev-pu the development datasource pointing to Derby at java:/global/datasources/DerbyDS
  • alternative-prod-pu the production datasource pointing at java:/global/datasources/PostgresDS

This is the way it looks like in the persistence.xml (for clarity I’ve omitted a few properties, you can check here the entire file):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             version="2.1"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="alternative-test-pu" transaction-type="JTA">
        <jta-data-source>java:/global/datasources/H2DS</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.sql-load-script-source" value="insert.sql"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.transaction.flush_before_completion" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        </properties>
    </persistence-unit>
    <persistence-unit name="alternative-dev-pu" transaction-type="JTA">
        <jta-data-source>java:/global/datasources/DerbyDS</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.sql-load-script-source" value="insert.sql"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.transaction.flush_before_completion" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>
        </properties>
    </persistence-unit>
    <persistence-unit name="alternative-prod-pu" transaction-type="JTA">
        <jta-data-source>java:/global/datasources/PostgresDS</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.sql-load-script-source" value="insert.sql"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.transaction.flush_before_completion" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
        </properties>
    </persistence-unit>
</persistence>

This kind of configuration can be unusual in a real project. You might prefer to have several persistence.xml files, each one declaring one persistent unit, and let a Maven profile to switch between files. But this is not the purpose of this post.

Injecting the EntityManager

How do we inject an EntityManager in Java EE 7? Well, we take any managed bean, add a @Transactional annotation to get a managed transactional demarcation, and the @PersistenceContext annotation will do the job: given a persistence unit (here alternative-test-pu) the container will inject an EntityManager. In our application, the SpeakerBean is the one responsible to do CRUD operations on the Speaker entity, so this is how we inject the EntityManager;

@Named
@Transactional
@ConversationScoped
public class SpeakerBean implements Serializable {

    @PersistenceContext(unitName = "alternative-test-pu")
    private EntityManager entityManager;
    ...

But as you can see in this code, there is no way to switch between persistence units here, and therefore, no way to switch datasources. The idea is to inject the EntityManager using the CDI @Inject annotation:

@Named
@Transactional
@ConversationScoped
public class SpeakerBean implements Serializable {

    @Inject
    private EntityManager entityManager;
    ...

Now, we need to produce the EntityManager to be able to inject it.

Producing three EntityManagers

As you might know by now, the only way to inject an EntityManager in Java EE is, either by using the @PersistenceContext annotation, or by producing it with CDI (thanks to producers). Take any POJO, add any attribute or method, annotate it with @Produces, and the result will be injectable by CDI. So the class below will produce our three EntityManagers, each pointing to a different persistent unit:

public class DatabaseProducer {

    @Produces
    @PersistenceContext(unitName = "alternative-test-pu")
    private EntityManager entityManagerTest;

    @Produces
    @PersistenceContext(unitName = "alternative-dev-pu")
    private EntityManager entityManagerDev;

    @Produces
    @PersistenceContext(unitName = "alternative-prod-pu")
    private EntityManager entityManagerProd;
}

This code won’t deploy because the deployment is ambiguous. If you don’t qualify a producer, a bean, or an injection point, it automatically gets a @Default qualifier. So here, the three EntityManager attributes have the same @Default qualifier and CDI doesn’t know how to make the difference between one another. The way to separate them is either to create different qualifiers, or stereotypes. Remember our use case: being able to switch from one datasource to another. Stereotypes is what we want. So what we really want is the following code (notice the @Alternative annotation and both stereotypes @DevDatabase and @ProdDatabase):

public class DatabaseProducer {

    @Produces
    @PersistenceContext(unitName = "alternative-test-pu")
    private EntityManager entityManagerTest;

    @Produces
    @Alternative
    @DevDatabase
    @PersistenceContext(unitName = "alternative-dev-pu")
    private EntityManager entityManagerDev;

    @Produces
    @Alternative
    @ProdDatabase
    @PersistenceContext(unitName = "alternative-prod-pu")
    private EntityManager entityManagerProd;
}

The way to read this code is “By default, produce the test EntityManager. If the @DevDatabase alternative is activated, produce the development EntityManager. If the the @ProdDatabase alternative is activated, produce the production EntityManager“.

Using CDI Stereotypes and Alternatives

In CDI, a stereotype encapsulates various properties including scope, interceptor bindings, qualifiers, alternatives, etc, into a single reusable package. In terms of code, it’s just a Java annotation annotated with @Stereotype. Below is the @DevDatabase stereotype:

@Stereotype
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface DevDatabase {
}

The code will be similar for the @ProdDatabase stereotype. We now have one stereotype for the development database, one for the production database, and none for the test database (as it is the default).

On the other hand, alternatives are beans whose implementation is specific to a particular client module or deployment scenario. That’s exactly what we want: depending on our deployment scenario (test, development, production) we want a different EntityManager. So let’s bundle stereotypes and alternatives to get what we want.

Producing three EntityManagers with Stereotypes and Alternatives

A stereotype can indicate that the bean (or producer) to which it is applied is an @Alternative. An alternative stereotype lets us classify beans by deployment scenario. So, on our @DevDatabase and @ProdDatabase stereotypes we jus need to add the @Alternative qualifier:

@Stereotype
@Alternative
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface DevDatabase {
}

Thanks to this stereotype with an alternative on it, we can now do the datasource switch in the beans.xml files. If the file is empty, no alternative is activated, so the default will apply and the test EntityManager pointing to H2 will be used. Instead, if we want to switch to the production Postgres database, we need to activate the alternative as follow:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       bean-discovery-mode="all" version="1.1"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">

    <alternatives>
        <stereotype>org.agoncal.sample.cdi.alternatives.database.cdi.ProdDatabase</stereotype>
    </alternatives>

</beans>

Conclusion

In this post I’ve shown you how to use CDI to switch datasources. CDI comes with a powerful implementation of the Bridge Design Pattern in the name of Alternatives. This allows us to swap one implementation by another one, in our case, from one EntityManager to another one. Being strongly typed, the only way to achieve this in CDI is by using Stereotypes (CDI hates injection with String names) and producing the appropriate EntityManager, thanks to Producers and beans.xml.

By the way, thanks to JBoss Forge for kicking out a web application with few commands.

So, download the code (or fork it on GitHub), give it a try, and give me some feedback.

Categories: Java

Tagged as: ,

7 Comments »

  1. Hi Antonio,

    So every time you deploy to a different environment you have to change your bean.xml file by hand? Or maybe using maven profiles to switch bean.xml? If you need a maven profile to automate this process I don’t see the point of doing all this. Just switch the persistence.xml file and you are done. What did I miss here?

    Cheers,

    Alex

    • You are exactly right. In most projects you will have a different persistent.xml (and other configuration files) per environment (e.g. Maven profile) and you will just switch the entire configuration. Here I am just giving an example on how to use Alternatives and Stereotype to, programmatically, do the same. And as you’ve pointed out, you then need to change the beans.xml by hand.

  2. Any way to switch among different persistence units at runtime maintaining transactions managed by container?

  3. I’ve tested stereotyped alternatives and it work fine as far as you are injecting in the same isolation level (jar/war), Each jar having its own beans.xml has its own rules of injecting. So it is not possible to control what is injected in the lower level (jar) while being in another jar. So, thaks to Your great article I’ve found that Spring remains still winner

  4. I have added my comment on the wrong place, I am sorry about that. Here is my comment :

    Hi Antonio, very good example for CDI alternatives. I have one question : everything works if all databases are running I mean Derby, H2, Postrgre but if some of them does not running, exception is being thrown during deployment because it is impossible creating of persistence unit. Is there some workaround about that ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s