Using EAR-level defined data source as JTA data source in persistence.xml of a shared JAR

Viewed 491

In a Jakarta EE 8 environment: Is it possible to define a ("portable JNDI") data source at EAR level [1] in application.xml and use this data source as JTA data source in the persistence.xml inside a library/JAR module?

Purpose: Creating a common JAR module that defines JPA entities together with corresponding "repositories", so that this JAR module can be used by multiple WAR modules (e. g. a RESTful API and a UI module) and package this modules as an EAR that is deployable to multiple application servers.

With the following attempt/method (for a full example I created a simple git repo [2]), the deployment of such an EAR fails (at least with Payara and WildFly).


Attempt/Method

Let's say there's an application consisting of 2 WAR modules and both WAR modules uses a shared JAR module, so that there is a application structure something like this:

ear/
├── shared-lib-jar
|   ├── ...
|   └── META-INF
|       ├── ...
|       └── persistence.xml
├── api-war/
|   └── ...
├── ui-war/
|   └── ...
├── application.xml
├── ...

In the application.xml of the EAR the data source is defined like this:

<application>
  <!-- ... -->
  <data-source>
    <name>java:app/appDS</name>
    <!-- ... -->
  </data-source>
</application>

In the persistence.xml the JNDI name defined in the application.xml is used as the JTA data-source:

<persistence>
  <persistence-unit name="..." transaction-type="JTA">
    <jta-data-source>java:app/appDS</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <!-- ... -->
  </persistence-unit>
</persistence>

unexpected/faulty behavior/situation with different (2) application servers

Setup: org.h2.jdbcx.JdbcDataSource as class-name and a "file-based" data base

Payara (5.2020.2)

The data base file will not be created and the server log says:

[2020-07-07T22:56:32.731+0200] [Payara 5.2020] [SEVERE] [AS-DEPLOYMENT-00026] [javax.enterprise.system.tools.deployment.dol] [tid: _ThreadID=168 _ThreadName=admin-thread-pool::admin-listener(11)] [timeMillis: 1594155392731] [levelValue: 1000] [[
  JNDI lookup failed for the resource: Name: foo-core, Lookup: java:app/appDS, Type: javax.sql.DataSource.]]

[2020-07-07T22:56:32.731+0200] [Payara 5.2020] [SEVERE] [] [javax.enterprise.system.core] [tid: _ThreadID=168 _ThreadName=admin-thread-pool::admin-listener(11)] [timeMillis: 1594155392731] [levelValue: 1000] [[
  JNDI lookup failed for the resource: Name: [foo-core], Lookup: [java:app/appDS], Type: [javax.sql.DataSource]]]

WildFly (1.4.11.Final)

The data base file is created but the server log says:

{"WFLYCTL0062: Composite operation failed and was rolled back. Steps that failed:" => {"Operation step-2" => {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"foo-ear-0.0.1-SNAPSHOT.ear\".WeldStartService" => "Failed to start service
    Caused by: java.lang.IllegalArgumentException: WFLYWELD0037: Error injecting persistence unit into CDI managed bean. Can't find a persistence unit named 'foo-core' in deployment foo-ear-0.0.1-SNAPSHOT.ear for injection point protected javax.persistence.EntityManager com.acme.BookRepository.entityManager"}}}}

1: https://jakarta.ee/specifications/platform/8/platform-spec-8.html#a1688

2: https://gitlab.com/hjoeren/application-level-ds-example

2 Answers

Yes, you are almost done, at least in case of Payara or Glasshish. You do not need to expose your datasource at application.xml.

First declare you datasource at persistence.xml like you have done:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="my.PU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    **<jta-data-source>jdbc/mydatasource</jta-data-source>**
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.logging.level" value="INFO"/>
      <property name="eclipselink.logging.parameters" value="true"/>
      <property name="eclipselink.persistence-context.flush-mode" value="COMMIT"/>
    </properties>
  </persistence-unit>
</persistence>

Then just configure appropriate connection pool and JDBC resource at your Payara (or GF) application server using Admin console or asadmin command. Example of file glassfish-resources.xml

$cat glassfish-resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">    
    <resources>
        <jdbc-connection-pool connection-creation-retry-interval-in-seconds="30" connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" wrap-jdbc-objects="false" res-type="javax.sql.DataSource" `name="mysql_myrootPool"` is-connection-validation-required="true" connection-creation-retry-attempts="10" validate-atmost-once-period-in-seconds="60">
            <property name="User" value="root"/>
            <property name="Password" value="secret"/>
            <property name="URL" value="jdbc:mysql://localhost:3306/mydb?zeroDateTimeBehavior=convertToNull"/>
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="zeroDateTimeBehavior" value="convertToNull"/>
            <property name="characterEncoding" value="utf-8"/>
            <property name="useSSL" value="false"/>
        </jdbc-connection-pool>
        <jdbc-resource enabled="true" `jndi-name="jdbc/mydatasource"` object-type="user" `pool-name="mysql_myrootPool"`/>
    </resources>
$asadmin add-resources glassfish-resources.xml

Please note correct names of pool, persistence unit and resource.

Now you can inject your connection whereever it is allowed (EJB, Servlets, Interceptors , Etc) in any EJB or WEB module included in your EAR like this

@PersistenceContext(unitName = "my.PU")
private EntityManager em;

Finally found the mistake: I put the application.xml in the wrong folder. Instead of putting the application.xml to the root folder, it has to be placed one level deeper in the META-INF folder so that the project structure looks like this:

ear/
├── shared-lib-jar
|   ├── ...
|   └── META-INF
|       ├── ...
|       └── persistence.xml
├── api-war/
|   └── ...
├── ui-war/
|   └── ...
├── META-INF/
|   ├── ...
|   └──application.xml
├── ...

To see the full example, I fixed the mis-configuration in the repo for this question.

Related