Deploying Data Source Configuration

14 Jun 2017

Since Java EE 6 it's possible to define data sources in a portable way.

This does mean though that the data source is embedded in the application archive. For some use cases, this is exactly what's needed, but for others it may not be ideal.

 

The alternatives are typically defining a data source on the Java EE server itself, using an admin console or CLI of some kind.

These alternatives do typically require a different syntax to be used, which can be problematic if the data source is often moved in and out of the application. They also require a vendor specific syntax, which in turn can be problematic if application servers from different vendors have to be supported.

 

A somewhat obscure feature of Java EE data source definitions allows for another variant; deploying an application archive (e.g. a .war) containing only a data source definition and its driver and making that data source available to a second application. This is made possible since the valid JNDI locations for registering the data source includes java:global, which is the special server wide namespace.

 

To demonstrate this with some example code, we first create a Maven project with only a single web.xml as follows:

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1" >
     
     <data-source>
        <name>java:global/datasource</name>
        <class-name>org.h2.jdbcx.JdbcDataSource</class-name>
        <url>jdbc:h2:mem:test</url>
    </data-source>
 
</web-app>

 

And the following pom.xml:

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
     
    <groupId>fish.example</groupId>
    <artifactId>datasource</artifactId>
    <version>1</version>
     
    <packaging>war</packaging>
     
    <build>
       <finalName>datasource</finalName>
    </build>
     
    <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
     
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.195</version>
        </dependency>
    </dependencies>
     
</project>

 

We then build and install this as usual using mvn clean install.

Next, we build a simple application that consumes this data source, which we call datasource-consumer. It only contains a single java file:

 

@Startup
@Singleton
public class StartupService {
     
    @Resource
    private DataSource dataSource;
 
    @PostConstruct
    public void init() {
        try (Connection connection = dataSource.getConnection()) {
            out.println(
                connection.getMetaData().getDatabaseProductName() + "-" +
                connection.getCatalog()
            );
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
     
}

 

 

And the following pom.xml:

 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
     
    <groupId>fish.example</groupId>
    <artifactId>datasource-consumer</artifactId>
    <version>1</version>
     
    <packaging>war</packaging>
     
    <build>
       <finalName>datasource-consumer</finalName>
    </build>
     
    <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
     
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
         
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.195</version>
        </dependency>
    </dependencies>
     
</project>
 
 

We subsequently deploy both the "resource providing app" and the "resource consuming app" via Payara Micro as follows:

java -jar payara-micro.jar --deploy ~/.m2/repository/fish/example/datasource/1/datasource-1.war --deploy ~/.m2/repository/fish/example/datasource-consumer/1/datasource-consumer-1.war

And lo and behold, during startup we see "h2-test" being printed in the log.

Having a separately deployable data source means we can configure the data source outside the application, but still use only Java EE APIs and descriptors. If we do venture a bit back into the vendor specific realm, we can even add another layer to the configurability by using placeholders in the deployment descriptors using the somewhat common ${} syntax.

 

For example:

 
<?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1" >
     
     <data-source>
        <name>java:global/datasource</name>
        <class-name>org.h2.jdbcx.JdbcDataSource</class-name>
        <url>jdbc:h2:mem:${db}</url>
    </data-source>
 
</web-app>
 
 

If we now start up Payara Micro as follows, we can control the DB (catalog) where we connect to from the commandline:

java -Ddb=foo -jar payara-micro.jar --deploy ~/.m2/repository/fish/example/datasource/1/datasource-1.war --deploy ~/.m2/repository/fish/example/datasource-consumer/1/datasource-consumer-1.war

If we look at the logs again, we'll now see "h2-foo" being printed.

Defining a data source this way is not perfect yet, as unlike the deployment of a resource connector (.rar), the class loader for an application is often isolated. So though the resource is indeed globally defined, the classes the resource makes use of are not. Hopefully the technique demonstrated here can still be useful for some cases as-is, while better solutions may be presented in the future.

 
 

Comments

Subscribe