EclipseLink JPA Deployed on Tomcat 6 using Eclipse WTP

Tomcat 6 is not a JEE5 compliant server by design as it is a servlet container, however the servlet container is able to run EJB 3.0/JPA applications in application-managed SE (stand alone) mode.

If you want to get a small JPA web application running quickly on Tomcat - the services provided by the Web Tools Project plugin in the Eclipse IDE can take care of the deployment details and set the server into debug mode for you.

This basic example details how to use Eclipse to run/debug a minimum JPA Application Managed web application servlet using EclipseLink JPA as the persistence provider. The goal of this example is to detail the minimum steps needed to run EclipseLink inside Tomcat using the Eclipse IDE - at this point no presentation/controller layer such as JSF, Spring or Struts will be used beyond a basic HttpServlet so we can concentrate on the the integration layer JPA setup.

JTA Datasource

Note: in this example as a redirection test I setup the local link to the global JNDI name to be ds/OracleDS but the global name really is also ds/OracleDS for WAR applications that do not override this value.

JTA transaction support is not really supported, even though the datasource is listed as a jta-data-source in persistence.xml it acts as a non-jta-data-source. Tomcat does not support container managed transactions by design. You will need to install Atomikos or JTOM.

To link the name ds/OracleDS to the jndi global name ds/OracleDS for WAR consumption, add the following <Context> element inside the <Host> element.
The attributes displayName, docBase, path, ResourceLink:global and ResourceLink:name need to be modified.

Note: docBase is very important - it must match your WAR file or you will get a (Name ds is not bound in this Context) exception

Persistence JAR location

Since Tomcat does not have an EJB container - you must add EJB 3.0/JPA 1.0 capability for EclipseLink JPA by placing the specification persistence.jar into the container lib directory $TOMCAT_HOME/lib

I put persistence_1_0.xsd there as well to be safe.

It is not recommended that the WAR include its own version of persistence.jar.

Your eclipse project should reference but not include this jar and xsd.

EclipseLink JAR location

The eclipselink.jar should be placed off of the container lib directory $TOMCAT_HOME/lib

Since Tomcat does not include an EJB container, you may put eclipselink.jar in the web applications's lib directory or higher up in the classloader by putting off of Tomcats' lib directory, where all web applications can access it.

JDBC JAR location

Jars for Oracle 11, MySQL 5 and Sybase 5.5/6.0 are off the container lib directory $TOMCAT_HOME/lib

Create Tomcat Server in Eclipse

Open the servers view
New | Server | Apache | Tomcat 6.

Create Dynamic Web application

persistence-context-ref in web.xml

After the servlet and servlet-mapping elements, place the following persistence-context-ref in your tomcat applications' web.xml so the web application has a reference to the eclipselink persistence unit.

Persistence Unit usage in the WAR

Since we dont have access to @EJB or @PersistenceContext injection we must directly create or own persistence unit in the servlet or on a helper class like an ApplicationService (that is not a session bean)

If the persistence unit transaction-type is JTA as opposed to RESOURCE_LOCAL then the following session customer will be required to modify the connector.

The client will require an implementation of SessionCustomizer that will set the lookupType on the JNDI connector to STRING_LOOKUP instead of Composite.

This will avoid the exception(sic "throught") javax.naming.NamingException: This context must be accessed throught a java: URL

Note: the JNDI name "ds/OracleDS" in web.xml, server.xml and persistence.xml must all match.

importjavax.naming.Context;importjavax.naming.InitialContext;importjavax.sql.DataSource;importorg.eclipse.persistence.config.SessionCustomizer;importorg.eclipse.persistence.sessions.DatabaseLogin;importorg.eclipse.persistence.sessions.JNDIConnector;importorg.eclipse.persistence.sessions.Session;importorg.eclipse.persistence.sessions.server.ServerSession;/**
* See
* http://wiki.eclipse.org/Customizing_the_EclipseLink_Application_(ELUG)
* Use for clients that would like to use a JTA SE pu instead of a RESOURCE_LOCAL SE pu.
*/publicclass JPAEclipseLinkSessionCustomizer implements SessionCustomizer {/**
* Get a dataSource connection and set it on the session with lookupType=STRING_LOOKUP
*/publicvoid customize(Session session)throwsException{
JNDIConnector connector =null;Context context =null;try{
context =newInitialContext();if(null!= context){
connector =(JNDIConnector)session.getLogin().getConnector();// possible CCE// Change from COMPOSITE_NAME_LOOKUP to STRING_LOOKUP// Note: if both jta and non-jta elements exist this will only change the first one - and may still result in the COMPOSITE_NAME_LOOKUP being set// Make sure only jta-data-source is in persistence.xml with no non-jta-data-source property set
connector.setLookupType(JNDIConnector.STRING_LOOKUP);// Or, if you are specifying both JTA and non-JTA in your persistence.xml then set both connectors to be safe
JNDIConnector writeConnector =(JNDIConnector) session.getLogin().getConnector();
writeConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
JNDIConnector readConnector =(JNDIConnector)((DatabaseLogin)((ServerSession)session).getReadConnectionPool().getLogin()).getConnector();
readConnector.setLookupType(JNDIConnector.STRING_LOOKUP);System.out.println("_JPAEclipseLinkSessionCustomizer: configured "+ connector.getName());}else{thrownewException("_JPAEclipseLinkSessionCustomizer: Context is null");}catch(Exception e){
e.printStackTrace();}}}

persistence.xml

20081107: Note use of a JTA datasource configuration is currently only working outside the Eclipse IDE when Tomcat 6 is run as a service - RESOURCE_LOCAL is fine.

If you use a RESOURCE_LOCAL persistence unit configuration you will be able to debug within eclipse. In both JTA and RESOURCE_LOCAL you may deploy the WAR to Tomcat but in the JTA case - Tomcat must run as service and not as a server in the Eclipse IDE.

Make sure that your persistence.xml (and optionaly orm.xml) file is placed off of the src/META-INF directory and not the default WebContent/META-INF dir so that it gets picked up by the servlet classloader from the classes directory.

Entity classes must be explicitly listed as they will not be automatically discovered by the servlet container - since we are not running our EJB 3 entities in an EJB container.

Notice there is no target-server element for the Tomcat 6 web container as target-server is reserved for J2EE compliant EJB container servers.

JTA Persistence.xml

Notice there are no standard SE JDBC properties for the database url, username, password - I have shown a JTA datasource example.

Also notice there is no target-server property because Tomcat does not support container managed transactions. If you require full JTA support you will need to add support for an ExternalTransactionController and implement a org.eclipse.persistence.transaction.tomcat.TomcatTransactionController(I have not tested this) or move to a fully JEE5 compliant container.

Therefore even though we specify JTA, this entityManager is not really JTA because we don't have container management of the transactions. We still need to supply the following wrappers around any update or persist

<?xmlversion="1.0"encoding="UTF-8"?><persistenceversion="1.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_1_0.xsd"><persistence-unitname="statCreateTablesJTA"transaction-type="JTA"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider><jta-data-source>java:comp/env/ds/OracleDS</jta-data-source><class>org.eclipse.persistence.example.unified.business.StatClass</class><!--.....list all entities --><class>org.eclipse.persistence.example.unified.business.StatPackage</class><properties><propertyname="eclipselink.session.customizer"value="org.eclipse.persistence.example.unified.integration.JPAEclipseLinkSessionCustomizer"/><propertyname="eclipselink.logging.level"value="FINEST"/><!-- uncomment the following once - if you wish to have your database tables created by EclipseLink -> <!-- property name="eclipselink.ddl-generation" value="drop-and-create-tables"/--><!-- property name="eclipselink.ddl-generation.output-mode" value="database"/--></properties></persistence-unit>

non-JTA (RESOURCE_LOCAL) Persistence.xml

You may use a JNDI datasource external connection pool defined in Tomcat (do not use standard SE JDBC properties for the database url, username, password even though the transaction-type is RESOURCE_LOCAL).

<?xmlversion="1.0"encoding="UTF-8"?><persistenceversion="1.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_1_0.xsd"><persistence-unitname="statJTA"transaction-type="RESOURCE_LOCAL"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider><non-jta-data-source>java:comp/env/ds/OracleDS</non-jta-data-source><class>org.eclipse.persistence.example.unified.business.StatClass</class><!--.....list all entities --><class>org.eclipse.persistence.example.unified.business.StatPackage</class><properties><propertyname="eclipselink.session.customizer"value="org.eclipse.persistence.example.unified.integration.JPAEclipseLinkSessionCustomizer"/><propertyname="eclipselink.target-database"value="org.eclipse.persistence.platform.database.oracle.OraclePlatform"/><!-- this one overrides --><propertyname="javax.persistence.nonJtaDataSource"value="java:comp/env/ds/OracleDS"/><propertyname="eclipselink.logging.level"value="FINEST"/></properties></persistence-unit>

Direct connection (RESOURCE_LOCAL) Persistence.xml

You may use standard SE JDBC properties for the database url, username, password - I have shown a RESOURCE_LOCAL JDBC example.

Publish EAR

Note: If you notice that changes to persistence.xml in build/classes/META-INF/persistence.xml are not in sync with src/META-INF/persistence.xml - make sure Project | build automatically is checked in the Eclipse IDE.

Perform Object Inserts and a JPQL query

Insert Objects

Note: the application managed transactional state methods - hence the same code whether run as JTA or RESOURCE_LOCAL in the case of Tomcat.

Browser Output

http://127.0.0.1:8080/unified/FrontController?action=demo

Using JNDI outside the Tomcat container for J2SE Applications

Notice: that the JNDI name for this SE Java application uses java:/comp/env/ds/OracleDS with an extra / before comp that is not defined in the datasource above in persistence.xml. This is OK because we are binding our own subcontext here.

Note: this section is a reference for developers that wish to use the same JNDI datasource available in the web container for their standalone SE or JUnit application - outside the web container.

When running JUnit SE code against a datasource I usually just specify the .jdbc. elements in persistence.xml - but I am curious about reusing the JNDI datasource that is available to in-container servlets.

If you want to test code running outside the container against the Tomcat JNDI provider - like in a JUnit SE test case then you will need to do the following which has been verified on a SE stand alone JPA app using EclipseLink as the JPA provider and Tomcat 6 as the JNDI datasource provider.

Setup a JNDI datasource factory/proxy to configure your System properties picked up by (new InitialContext()), create a context using these properties, create all the sub contexts java:, java:/comp etc.. , create a pooled DataSource and bind it to your context before you attempt to use the context.