Community

EJBs in BlueMix (with JPA: DB2 and MySQL backends) (packaged server)

Share this post:

Dave Cohen / Advisory Software Engineer / IBM Cloud OE Runtime Test


This article will use a BlueMix JPA Sample Application with a db2 or mysql database driven by either EJBs or Servlets.
JPA (Java Persistence Architecture API) is a set of APIs that is used for reading and writing data to databases via Java objects. An EJB (Enterprise Java Bean) is a server side Java object that contains business logic with special qualities of service (collaborators) such as transactions and security. We’ll provide some code snippets for EJBs and JPA, and also explain the packaging required for deploying this type of application to BlueMix.

Sample code for this article can be found here:

<a style="font-weight: inherit; font-style: inherit; color: #00aed1;" title="JPA Blue Mix Project" href="https://hub.jazz.net/project/dacohen01/EJB%20BlueMix%20Sample/overview" target="_blank"><span style="font-weight: inherit; font-style: inherit;">JPABlueMixProject</span></a> 

EJBs (Enterprise Java Beans)

The sample uses annotations to create callable handles to EJBs that are invoked from servlets.
Annotations (e.g. @EJB ListDBEJB listEJB; ) simplify the process of creating these callable handles to EJBs by just
specifying the EJB class name (e.g. ListDBEJB) followed by the instance name (e.g. listEJB):

Code in servlet that calls the EJB

@EJB ListDBEJB listEJB;

OutBean output = null; //output object to receive results from EJB call
InBean input = new InBean(); // create/instantiate a new input object

input.setSql(Boolean.parseBoolean(request.getParameter("isMySQL"))); // set the DB type into
// input object
output = listEJB.list(input); // call the ejb and get the results in output instance

EJB definition in the EJB’s java file containing your business logic:

    @Stateless     // annotation for stateless session EJB bean
@LocalBean // local ejb, can only be called from within same JVM, as per ejbLite feature
public class ListDBEJB {
// your methods and business logic here
}

 


JPA

The PopulateDB function (via EJB or servlet) creates a database table using native SQL:

q = em.createNativeQuery("CREATE TABLE  CUSTOMERACCT ( C_NUM  SMALLINT
NOT NULL , C_NAME CHARACTER(30) NOT NULL , C_MONEY DECIMAL(12,2)
NOT NULL,PRIMARY KEY ( C_NUM ) )");

and then populates the rows of the table using standard JPA APIs:

    // set values into the CustomerAcct java object("ca") and then persist them into the CustomerAcct database table
for (short j = 0; j &lt; 8; j++) {
ca = new CustomerAcct();
ca.setCustomerAcct((short) (j + 1));
ca.setCustomerMoney(BigDecimal.valueOf(money[j]));
ca.setCustomerName(name[j]);
em.persist(ca);
}
em.close();

The ListDB functions lists out the customers in the database table using a named query (query invoked from a servlet or EJB, but the query itself is defined in the JPA entity):

    EntityManager em = emfToUse.createEntityManager();  // get an entity manager handle

OpenJPAQuery q = OpenJPAPersistence.cast(em
.createNamedQuery("listCustomers")); // run the named query

Collection coll = null;
coll = q.getResultList(); // gather up the result set into a java collection

if (coll != null) { // if we have results
Iterator it = coll.iterator(); // get the collection into a java iterator
CustomerAcct[] cas = new CustomerAcct[8]; // set up an array for the results
int i = 0;
CustomerAcct ca = null;
while (it.hasNext()) { //iterate through the results of the query
ca = (CustomerAcct) it.next(); // get the next result
System.out.println("customer acct from result set: "+ca);
cas[i] = ca;
i++;
}
ob.setCustomerAccts(cas); //set array of customers into output object
}

Above query as defined in the CustomerAcct JPA Entity:

@Entity
@Table(name="CUSTOMERACCT")
@NamedQuery(
name="listCustomers",
query="select c from CustomerAcct c"
)

The CustomerCredit function locates a customer record and updates that customer’s balance in the CUSTOMERACCT table:

CustomerAcct customerAcct = em.find(CustomerAcct.class, customerNumber);  // find the customer record for this "customerNumber"
double money = customerAcct.getCustomerMoney().doubleValue()+ moneyToCredit; //determine the credit $$
customerAcct.setCustomerMoney(new BigDecimal(money)); // set new balance value
em.close();

____________________________________________________________________________________________________

Pushing a standalone war file(sample with just DB2):

Export war file from eclipse
note: the following makes use of the autoconfig capability of IBM Cloud OE Runtime
The autoconfig capability generates the necessary dataSource stanza in the server.xml in order
to make the DB2 service bound to the application map to the backend database.

e.g. DataSource stanza automatically generated by autoconfig support in server.xml

&lt;library id='db2-library'&gt;
&lt;fileset dir='${server.config.dir}/lib' id='db2-fileset'
includes='db2jcc4.jar db2jcc_license_cu.jar' /&gt;
&lt;/library&gt;

&lt;jdbcDriver id='db2-driver' libraryRef='db2-library' /&gt;

&lt;dataSource id='db2-MyDataSourceDB2' jdbcDriverRef='db2-driver'
jndiName='MyDataSourceDB2' statementCacheSize='30' transactional='true'&gt;
&lt;properties.db2.jcc databaseName='${cloud.services.MyDataSourceDB2.connection.dbname}'
id='db2-MyDataSourceDB2-props' password='${cloud.services.MyDataSourceDB2.connection.password}'
portNumber='${cloud.services.MyDataSourceDB2.connection.port}'
serverName='${cloud.services.MyDataSourceDB2.connection.host}' user='${cloud.services.MyDataSourceDB2.connection.username}' /&gt;
&lt;/dataSource&gt;

Finally the last thing to do is push the application to BlueMix creating and binding the required services (dataSources in this case):

cf push jpa -p JPAServletProject.war
cf create-service SQLDB SQLDB_OpenBeta MyDataSourceDB2
cf bind-service jpa MyDataSourceDB2
(note this message comes out after bind service: "TIP: Use 'cf push' to ensure your env variable changes take effect" )
cf stop jpa
cf start jpa

note: you can just as well stop and start the application, instead of pushing it again, this is faster

note: the plan names (e.g. "SQLDB_OpenBeta" for DB2) used in the cf create-service commands above, can
be found by issuing the CF MARKETPLACE command


Alternately (if using MySQL) a packaged server with server.xml will be required:

Packaging if using packaged server (required for MySQL)

The EJBs (circled in RED) in this sample are in the same package as the servlets (highlighted in yellow under the “my.application” package) ofthe web application, this is known as the EJB in WAR structure:

JPA App Structure

Notice above that the CustomerAcct JPA entity (the Java Object mapping of the CustomerAcct table) resides in the myjpa package.

The persistence.xml, which describes the persistence units, is under the META-INF folder.
Persistence units specify the persistent unit names, dataSources and JPA entity name(s) necessary to access the database table.

The business logic of an application can inject a Persistence Unit (via annotation: @PersistenceUnit) and use it to to gain access to a database table which automatically maps that table to a JPA entity, easing accessing to the database table:

Example annotation to inject a persistent unit:
(as used in the business logic/java code of all of the EJBs and Servlets in this sample)

 @PersistenceUnit(unitName = "CustomerQuery")  EntityManagerFactory emf;

Its also important to understand the associations that are created in the contents of the persistence.xml:

Notice the persistent unit name, dataSource name and JPA entity name

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"&gt;
&lt;persistence-unit name="CustomerQuery"&gt;
&lt;jta-data-source&gt;MyDataSource&lt;/jta-data-source&gt;
&lt;class&gt;myjpa.CustomerAcct&lt;/class&gt;
&lt;properties&gt;
&lt;property name="openjpa.LockTimeout" value="30000" /&gt;
&lt;property name="openjpa.jdbc.TransactionIsolation" value="read-committed" /&gt;
&lt;property name="openjpa.Log" value="none" /&gt;
&lt;property name="openjpa.jdbc.UpdateManager" value="operation-order" /&gt;
&lt;/properties&gt;
&lt;/persistence-unit&gt;
&lt;persistence-unit name="CustomerQueryDB2"&gt;
&lt;jta-data-source&gt;MyDataSourceDB2&lt;/jta-data-source&gt;
&lt;class&gt;myjpa.CustomerAcct&lt;/class&gt;
&lt;!-- exclude-unlisted-classes&gt;true&lt;/exclude-unlisted-classes --&gt;
&lt;properties&gt;
&lt;property name="openjpa.LockTimeout" value="30000" /&gt;
&lt;property name="openjpa.jdbc.TransactionIsolation" value="read-committed" /&gt;
&lt;property name="openjpa.Log" value="none" /&gt;
&lt;property name="openjpa.jdbc.UpdateManager" value="operation-order" /&gt;
&lt;/properties&gt;
&lt;/persistence-unit&gt;
&lt;/persistence&gt;

The same JPA entity is being used by both persistent units in the persistence.xml above, one for MySQL (using a jndiName of “MyDataSource”) and the other for DB2 (using a jndiName of “MyDataSourceDB2”).

These dataSources map to the following dataSource stanzas in server.xml:

MySQL dataSource entry(server.xml):

&lt;library id='jdbc-lib'&gt;
&lt;fileset dir='${server.config.dir}/lib' includes='mysql-connector-java-5.1.26-bin.jar'&gt;&lt;/fileset&gt;
&lt;/library&gt;
&lt;jdbcdriver id='jdbc-driver' libraryref='jdbc-lib'&gt;&lt;/jdbcdriver&gt;
&lt;datasource id='mysql-MyDataSource' jdbcdriverref='jdbc-driver' jndiname='MyDataSource' statementcachesize='30' transactional='true'&gt;
&lt;properties databasename='${cloud.services.MyDataSource.connection.name}' id='mysql-MyDataSource-props'
password='${cloud.services.MyDataSource.connection.password}' portnumber='${cloud.services.MyDataSource.connection.port}'
servername='${cloud.services.MyDataSource.connection.host}' user='${cloud.services.MyDataSource.connection.user}'/&gt;
&lt;/datasource&gt;

DB2 dataSource entry(server.xml):

&lt;library id='db2-library'&gt;
&lt;fileset dir='${server.config.dir}/lib' id='db2-fileset'
includes='db2jcc4.jar db2jcc_license_cu.jar' /&gt;
&lt;/library&gt;

&lt;jdbcDriver id='db2-driver' libraryRef='db2-library' /&gt;

&lt;dataSource id='db2-MyDataSourceDB2' jdbcDriverRef='db2-driver'
jndiName='MyDataSourceDB2' statementCacheSize='30' transactional='true'&gt;
&lt;properties.db2.jcc databaseName='${cloud.services.MyDataSourceDB2.connection.dbname}'
id='db2-MyDataSourceDB2-props' password='${cloud.services.MyDataSourceDB2.connection.password}'
portNumber='${cloud.services.MyDataSourceDB2.connection.port}'
serverName='${cloud.services.MyDataSourceDB2.connection.host}' user='${cloud.services.MyDataSourceDB2.connection.username}' /&gt;
&lt;/dataSource&gt;

The feature list in server.xml tells us what functionality will be included in the Liberty server’s footprint when it is started:
(note: webProfile-6.0 encompasses features for JPA, EJB and Servlet):

<featureManager>
<feature>webProfile-6.0</feature>
</featureManager>

Once the web app is written, compiled and exported to a war, it needs to be packaged up into a packaged server to pick up the dataSource stanzas and feature list indicated above. This packaging is usually accomplished using the server command from the bin directory of Liberty (i.e. wlp/bin/server.bat or wlp/bin/server depending on the OS you are running on).

copy the war into wlp//usr/servers/defaultServer/apps/

and copy the server.xml into wlp//usr/servers/defaultServer

server.xml loc

example server command to perform the packaging of the server (note: stop server before packaging):

 

<strong>wlp/bin/server package defaultServer --include=usr</strong> 


the above command will create this packaged server zip: wlp\usr\servers\defaultServer\defaultServer.zip

Finally the last thing to do is push the application to BlueMix creating and binding the required services (dataSources in this case):

cf push jpa -p defaultServer.zip
cf create-service mysql 100 MyDataSource
cf create-service SQLDB SQLDB_OpenBeta MyDataSourceDB2
cf bind-service jpa MyDataSource
cf bind-service jpa MyDataSourceDB2
(note this message comes out after bind service: "TIP: Use 'cf push' to ensure your env variable changes take effect" )
cf stop jpa
cf start jpa

note: you can just as well stop and start the application, instead of pushing it again, this is faster

note the plan names (e.g. "100" for mysql and "SQLDB_OpenBeta" for DB2) used in the cf create-service commands above,
can be acquired using the command: cf marketplace


Now verify that the app is up and running ok

 

cf apps

name requested state instances memory disk urls
jpa started 0/1 512M 1G jpa.<b>ng.bluemix.net</b>

 

You can invoke it from a browser with the following URL: jpa.ng.bluemix.net/JPAServletProject

The home page looks like this:

Home Page

Note:
PopulateDB must be run before ListDB or CustomerCredit so that there is a database to work with.

You must keep the database type consistent across all calls, either MySQL or DB2, otherwise the database won’t be there

You can use the “Drop Table” button to drop the table and start all over again if you like

Have Fun!

More stories
May 7, 2019

We’ve Moved! The IBM Cloud Blog Has a New URL

In an effort better integrate the IBM Cloud Blog with the IBM Cloud web experience, we have migrated the blog to a new URL: www.ibm.com/cloud/blog.

Continue reading

May 1, 2019

Two Tutorials: Plan, Create, and Update Deployment Environments with Terraform

Multiple environments are pretty common in a project when building a solution. They support the different phases of the development cycle and the slight differences between the environments, like capacity, networking, credentials, and log verbosity. These two tutorials will show you how to manage the environments with Terraform.

Continue reading

April 29, 2019

Transforming Customer Experiences with AI Services (Part 1)

This is an experience from a recent customer engagement on transcribing customer conversations using IBM Watson AI services.

Continue reading