Arch2Arch Tab BEA.com
Syndicate this blog (XML)

The promise of Persistence Providers: Kodo, OpenJPA and Hibernate

Bookmark Blog Post

del.icio.us del.icio.us
Digg Digg
DZone DZone
Furl Furl
Reddit Reddit

Pinaki Poddar's Blog | June 25, 2007   7:42 AM | Comments (10)


The promise of pluggable Persistence Providers: Kodo, OpenJPA and Hibernate

The Bug That Started It

A bug landed in my mailbox. The bug reported a problem while switching JPA persistence provider between Hibernate and Kodo in Weblogic server environment. In course of reproducing the bug, I learned few bits about installing Hibernate in Weblogic Server 10.0 and also about deploying a JPA based Web application with a specific persistence provider X, and then redeploying it with another provider Y without bringing the whole house (meaning Weblogic Server) down.

This experience nudged me out of my hibernation from blog writing triggered by a cartoon in New Yorker magazine.

Anyway, this blog will

  • describe a working way to install and configure Hibernate in Weblogic Server 10.0
  • answer why you do not need to install Kodo or OpenJPA in Weblogic Server 10.0
  • show the steps to switch JPA provider between Hibernate, OpenJPA and Kodo in a running Weblogic Server with a very simple Stateless Session Bean based service as an example
  • reproduce the bug that started it all not only because the reporter thought it was important but I also agreed after my brief experiment
  • present how the bug impacts a change in how JPA bootstraps in application server environment

    A very simple mechanics to test provider switching

    I used a simple service as the mechanics to verify correct provider is being used and the provider is behaving correctly albeit for rudimentary persistence operations. The simple service interface against which I will test is

    JPAService.java
    01 package service;
    02 /**
    03  * A very simple service to verify the persistence provider being used.
    04  * The service also can persist a simple log message in a database.
    05  *  
    06  @author ppoddar
    07  *
    08  */
    09 public interface JPAService {
    10   /**
    11    * Returns the name of the active provider.
    12    */
    13   public String getProvider();
    14   
    15   /**
    16    * Logs the given message.
    17    
    18    @param message an arbitray message string.
    19    
    20    @return a Message instance that has been persisted. The service will
    21    * attach a timestamp to the message.
    22    */
    23   public Message log(String message);
    24 }

    The service is non-committal about how it is to be implemented; it does not even state that it is going to use JPA. That is the way a service definition should be -- non-committal about implementation technology (omitted the customary IMO here -- assume this whole rambling as my opinion -- humble or otherwise).

    JPA & Session Bean based Application Primer

    I will undertake a brief discussion on implementing this service in Stateless Session Bean that uses JPA. It is pretty basic. If you already familiar with JPA, you may skip to the next section.

    In our simple example, this service is realized by a Stateless Session Bean that uses JPA.

    JPAServiceBean.java
    01 package session;
    02 
    03 import javax.ejb.Remote;
    04 import javax.ejb.Stateless;
    05 import javax.persistence.EntityManager;
    06 import javax.persistence.PersistenceContext;
    07 
    08 import service.JPAService;
    09 import service.Message;
    10 
    11 /**
    12  * A Stateless Session bean that uses an injected JPA EntityManager to implement
    13  * the contract of {@link JPAService}.
    14  
    15  @author ppoddar
    16  *
    17  */
    18 @Stateless
    19 @Remote(JPAService.class)
    20 public class JPAServiceBean {
    21   /**
    22    * Inject an EntityManager for a persistent unit simply named 
    23    <code>test</code>.
    24    * It is this name which must be specified in the configuration file
    25    * META-INF/persistence.xml as
    26    <pre>
    27    *   <persistence-unit name="test">
    28    </pre>
    29    */
    30   @PersistenceContext(unitName="test")
    31   EntityManager em;
    32   
    33   /**
    34    * Returns the EntityManager class name that provides the persistence 
    35    * service. 
    36    <p>
    37    <B>NOTE</B>: As the entity manager is injected by the container, it is
    38    * often a proxy. Hence the return value is the class name of the 
    39    * delegate of the actual injected instance 
    40    */
    41   public String getProvider() {
    42     return em.getDelegate().getClass().getName();
    43   }
    44   
    45   public Message log(String message) {
    46       Message result = new Message(message);
    47       em.persist(result);
    48       return result;
    49   }
    50   
    51 }

    The session bean in its own way is non-committal too. It commits to use JPA but does not declare which particular persistence provider it is going to use (in fact, one of the major purpose of defining an industry-standard persistence API is to be able to switch from one provider to another).
    The bean does not directly establish the connection to a JPA provider; instead it depends on the container's newly (i.e. since JEE 5) acquired capability to inject a JPA connection whenever required. And good news is Weblogic Server 10.0 is the first JEE 5 compliant application server.
    The container can inject JPA dependency into a Session Bean in two forms: @PersistenceUnit or @PersistenceContext annotation. We used @PersistenceContext (line 30:31 in JPAServiceBean.java).
    If you allow me a broad analogy of JPA with JDBC, @PersistenceUnit is equivalent to a DataSource and @PersistenceContext is to a Connection. In JPA terminology, this pair of concepts is realized by JPA defined interfaces EntityManagerFactory and EntityManager respectively.
    Via these interfaces and few other (e.g. Query), JPA has defined a higher level, object-oriented abstraction to interact with databases than what JDBC had traditionally provided via SQL. Through JPA you interact with underlying database via object oriented view. Your application creates, updates, queries, deletes objects through the methods on EntityManager interface -- JPA provider working in tandem with your application intercepts these object level changes and maps them to corresponding database operations of INSERT, UPDATE, SELECT and DELETE as appropriate.

    Just to complete this basic JPA application, here is the persistent class.

    Message.java
    01 package service;
    02 
    03 import java.io.Serializable;
    04 import java.util.Date;
    05 
    06 import javax.persistence.*;
    07 
    08 /**
    09  * A simple persistent message. The entity is declared to be serializable to
    10  * pass across processes.
    11  <p>
    12  * The message is assigned an identifier by the persistence provider.
    13  * A timestamp is also set to the message when it is constructed.
    14  <p>
    15  * Annotates the fields with their persistent properties.
    16  
    17  @author ppoddar
    18  *
    19  */
    20 @SuppressWarnings("serial")
    21 @Entity
    22 public class Message implements Serializable {
    23   /** 
    24    * Annotates the field as primary identity for this instance. Also the
    25    * persistence provider will generate the identity value. That is why
    26    * there is no corresponding setter method.
    27    */
    28   @Id
    29   @GeneratedValue
    30   private long id;
    31   
    32   /**
    33    * Annotates the field to carry a TIMESTAMP value in the database column.
    34    * The column name is different than the default value of <code>timestamp</code>
    35    * because many databases will reserve that word.  
    36    <p>
    37    * The field is immutable by application (no setter method) and set on 
    38    * construction with current time. 
    39    */
    40   @Temporal(TemporalType.TIMESTAMP)
    41   @Column(name="createdOn")
    42   private Date timestamp;
    43   
    44   /**
    45    * Does not annotate but the field will be persisted nevertheless by 
    46    * convention of String and primitive types being persistent by default.
    47    */
    48   private String body;
    49   
    50   protected Message() {
    51     this("");
    52   }
    53   
    54   public Message(String body) {
    55     timestamp = new Date();
    56     this.body = (body == null"" : body;
    57   }
    58 
    59   public long getId() {
    60     return id;
    61   }
    62 
    63   public String getBody() {
    64     return body;
    65   }
    66 
    67   public Date getTimestamp() {
    68     return timestamp;
    69   }
    70 }

    JPA dependency has crept in this Java class because we are using O-R mapping annotations to denote how instances of Message class is mapped to database table and columns, or which field value can be treated as primary identifier for a Message instance. One can move these mapping information to a separate orm.xml thereby restoring Pure Old Java Object (POJO) status to this class and also hammer down the nail of non-committal approach further down to domain object model.

    Test before Code

    A JUnit Test Case contacts the service via JNDI lookup and verify that two service methods are returning the expected result.

    TestJPAService.java
    01 package junit;
    02 
    03 import java.util.Properties;
    04 
    05 import javax.naming.Context;
    06 import javax.naming.InitialContext;
    07 
    08 import service.JPAService;
    09 import service.Message;
    10 import junit.framework.TestCase;
    11 
    12 /**
    13  * A JUnit test case to verify that a Session Bean is using the correct provider.
    14  
    15  * The test must be invoked with two mandatory Java System Properties:
    16  <LI><code>jndi.name</code> : the JNDI name of the registered Session Bean.
    17  <LI><code>persistence.provider</code>: The <em>logical</em> name of the 
    18  * persistence provider. Allowed values are <code>kodo</code> or <code>hibernate</code>.
    19  <p>
    20  * The optional Java System Properties are:
    21  <LI><code>weblogic.user</code> : The authenticated user to contact Weblogic server. Defaults to <code>weblogic</code>
    22  <LI><code>weblogic.password</code> : The valid password to contact Weblogic server. Defaults to <code>weblogic</code>
    23  <LI><code>weblogic.url</code> : The URL where Weblogic Server is running. Defaults to <code>t3://localhost:7001</code>
    24  
    25   
    26  @author ppoddar
    27  *
    28  */
    29 public class TestJPAService extends TestCase {
    30   private static final String USER = System.getProperty("weblogic.user",     "weblogic");
    31   private static final String PWD  = System.getProperty("weblogic.password""weblogic");
    32   private static final String URL  = System.getProperty("weblogic.url",    "t3://localhost:7001");
    33   private static final String JNDI_NAME = System.getProperty("jndi.name");
    34   private static final String PERSISTENCE_PROVIDER = System.getProperty("persistence.provider");
    35   
    36   private static JPAService service;
    37   
    38   /** 
    39    * Sets up the test by contacting Weblogic Server and looking up in JNDI
    40    * for the registered Session Bean.
    41    
    42    */
    43   @Override
    44   public void setUp() throws Exception {
    45     assertNotNull("Must specify JVM System property -Djndi.name=<JNDI name of Session Bean> to run this test", JNDI_NAME);
    46     assertNotNull("Must specify JVM System property -Dpersistence.provider=[kodo|hibernate] to run this test", PERSISTENCE_PROVIDER);
    47     if (service == null) {
    48           Properties p = new Properties();
    49           p.put(Context.INITIAL_CONTEXT_FACTORY,  "weblogic.jndi.WLInitialContextFactory");
    50           p.put(Context.SECURITY_PRINCIPAL,       USER);
    51           p.put(Context.SECURITY_CREDENTIALS,     PWD);
    52           p.put(Context.PROVIDER_URL, URL);
    53           System.out.println("Contacting server " + URL + " as " + USER + " for " + JNDI_NAME);
    54           InitialContext ctx = new InitialContext(p);
    55           service = (JPAService)ctx.lookup(JNDI_NAME);
    56     }
    57   }
    58   
    59   /**
    60    * Asserts that the Persistence Provider class name contains the <em>logical</em>
    61    * name of the intended provider i.e. <code>kodo</code> or <code>hibernate</code>.
    62    *
    63    */
    64   public void testProvider() {
    65     String actual = service.getProvider();
    66     System.err.println("Logical Persistence Provider is [" + PERSISTENCE_PROVIDER + "]");
    67     System.err.println("Actual  Persistence Provider is [" + actual + "]");
    68     assertTrue("*** ERROR: " + actual + " is not provided by " + PERSISTENCE_PROVIDER, actual.indexOf(PERSISTENCE_PROVIDER!= -1);
    69   }
    70   
    71   /**
    72    * Simply logs (persists) a message via the remote service.
    73    * The service will affix a timestamp with the persisted message it returns.
    74    * Verifies that the timestamp against the time when the message has been
    75    * sent.  
    76    *
    77    */
    78   public void testLog() {
    79     long senderTime = System.currentTimeMillis();
    80     String body = "A message sent for logging on " + senderTime;
    81     Message message = service.log(body);
    82     long createTime = message.getTimestamp().getTime();
    83     long elapsedTime = createTime - senderTime;
    84     System.err.println("Persisted Message [id:" + message.getId() " timestamp:" 
    85         message.getTimestamp().getTime() " body:"+ message.getBody() "]");
    86     System.err.println("Time elapsed between the message to send and persisted is " + elapsedTime + "ms");
    87     assertTrue(elapsedTime>=0);
    88   }
    89 }

    One test method testProvider() verifies that the service returns a class name for the JPA provider's implementation of the EntityManager that contains the logical name of the provider. The other test verifies that the Message instance returned by the service has a proper identity assigned by the provider.

    Ant Script to Build JPA Application and Switch Providers

    The last but not the least bit of preparation, before we start running code, is a build script written in Ant.
    We need this script to compile Java classes, package them into EJB module and EAR, deploy and undeploy them in Weblogic Server and run the JUnit test.
    The standard build file for such routine tasks is made slightly more complex because we have slight variations as we switch provider. Each provider uses different persistence.xml. The separate configuration files are placed in META-INF/hibernate, META-INF/kodo and the build script takes care of including the correct configuration in the deployable package. Also the build script creates the EJB module by the logical provider name e.g. hibernate-ejb.jar, kodo-ejb.jar or openjpa-ejb.jar.
    The other important aspect in building is both OpenJPA and Kodo requires a post-compilation enhancement step. It is possible to push this post-compilation step to runtime with -javaagent but I preferred doing enhancement in build script to invoke Weblogic server with -javaagent. You can read more about enhancement in OpenJPA user guide.
    For conditional execution, you will find a <target name="check-provider"> in build.xml.

    The build script is to be invoked with a valid provider name

    $ ant -Dprovider=hibernate

    The valid provider values are hibernate, kodo and openjpa.


    The default build target cleans, compiles, conditionally enhances, packages, undeploys the previous deployment if any, deploys, runs the test and reports error if any.
    Configure the script for your environment via build.properties file where you can specify ${bea.home} and user credentials etcetera.


    The build script packages JPAService.ear for deployment with following content:

    $ jar tvf JPAService.ear
    106 Sat Jun 23 01:33:00 CDT 2007 META-INF/MANIFEST.MF
    414 Sat Jun 23 01:33:02 CDT 2007 META-INF/application.xml
    2755 Sat Jun 23 01:33:02 CDT 2007 hibernate-ejb.jar

    The simplest Enterprise Application Archive in history of JEE has a single EJB module described in following application.xml (without XML header and namespace declaration for readability):

     <application>
       <display-name>EJB3 Sample Application</display-name>
       <module>
           <ejb>hibernate-ejb.jar</ejb>
       </module>
     </application>

    How does the ejb module hibernate-ejb.jar look like?

    $ jar tvf hibernate-ejb.jar
    106 Sat Jun 23 01:33:00 CDT 2007  META-INF/MANIFEST.MF
    1280 Sat Jun 23 01:33:02 CDT 2007 META-INF/persistence.xml
    662 Fri Jun 22 02:31:58 CDT 2007  META-INF/weblogic-ejb-jar.xml
    169 Sat Jun 23 01:33:02 CDT 2007  service/JPAService.class
    888 Sat Jun 23 01:33:02 CDT 2007  service/Message.class
    839 Sat Jun 23 01:33:02 CDT 2007  session/JPAServiceBean.class

    This EJB module contains the service interface, its session bean implementation and persistence entity class. Also the jar has two configuration files: persistence.xml to configure persistence and weblogic-ejb-jar.xml to configure the session bean in Weblogic Server. These files must be loadable by the active classloader as resources named META-INF/persistence.xml and META-INF/weblogic-ejb-jar.xml.

    Configuring Hibernate Persistence Unit

    JPA provider configures a particular persistence unit via persistence.xml. In this file, you supply the following:

  • name of the persistence unit
  • type of transaction to support; options are RESOURCE_LOCAL and JTA
  • class name of the provider
  • list of persistent Java class names or a Jar file that contains them. You can leave these undefined but better not to -- for reasons that are out of scope of the current topic.
  • name of mapping file if you think O-R mapping annotation violates POJO-nature
  • The database you are going to use. In a container environment, you can specify a name of a JTA or non-JTA datasource configured by other means. Otherwise you can specify the url, user credentials, driver class name individually (this is the approach we will adopt here).

    Over and above, each provider supports a large number of configurable properties. As one will discover, the art of successful usage of O-R mapping lies in knowing the meaning of these parameters and more importantly where to apply them. The XML schema that controls persistence.xml has allowed specification of vendor-specific properties via <property name="a.b.c" value="xyz"/> tag. Different providers employ different names for the properties even when they mean the same thing. Hence, these properties become a significant factor when we consider switching an application from one provider to another.
    After a brief Google search, I configured persistence.xml for Hibernate as follows

    persistence.xml
     <?xml version="1.0"?>
     
     <persistence 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"
       version="1.0">
       <persistence-unit name="test">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
         <class>service.Message</class>
         <properties>
           <property name="hibernate.dialect"                 value="org.hibernate.dialect.MySQLDialect"/>
           <property name="hibernate.hbm2ddl.auto"            value="create"/>
           <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
           <property name="hibernate.connection.url"          value="jdbc:mysql://localhost/hibernateDB"/>
     </persistence>

    The <provider> is specified as org.hibernate.ejb.HibernatePersistence. Also configured Hibernate to use MySQL with url as jdbc:mysql://localhost/hibernateDB.

    Installing Hibernate in Weblogic Server

    Now let us download Hibernate. Hibernate website was neat, informative and download process went without a hitch. Two separate download comprised of Hibernate Core version 3.2 and and a JPA overlay called Hibernate EntityManager vers
  • How to tell Weblogic Server about Hibernate class libraries? But before that which class libraries do we need? In Hibernate Core I found 39 libraries while its JPA overlay had 6 more. I took the easy path. Placed all these 45 libraries in Weblogic Server's samples domain's shared library i.e. ${bea.home}/wlserver_10.0/samples/domains/wl_server/lib with the help of this simple Ant task

    	
    <target name="install-hibernate">
        <property name="domain.lib" value="C:/bea/wlserver_10.0/samples/domains/wl_server/lib"/>
        <property name="hibernate.core.dir" value="D:/hibernate/core-3.2"/>
        <property name="hibernate.core.dir" value="D:/hibernate/entitymanager-3.3.1"/>
        
        <copy todir="${domain.lib}" overwrite="false">
    	<fileset dir="${hibernate.core.dir}">
    	   <include name="hibernate*.jar"/>
    	</fileset>
    	<fileset dir="${hibernate.core.dir}/lib">
    	   <include name="*.jar"/>
    	</fileset>
    	<fileset dir="${hibernate.jpa.dir}">
    	   <include name="hibernate*.jar"/>
    	</fileset>
    	<fileset dir="${hibernate.jpa.dir}/lib">
    	   <include name="*.jar"/>
    	</fileset>
        </copy>
    </target>
    	

    Of course, one should be more discriminating and there were obvious ones one could have excluded such as jta.jar or junit-3.8.1.jar. Many of these libraries are now integral part of Weblogic Server 10.0. Actually,  found 140 jars in ${bea.home}/modules directory. Please be aware that there are horror stories about conflicting antlr libraries installed with Weblogic and packaged with Hibernate, but I have not encountered them yet.

    The main point about placing Hibernate libraries in shared space is they are not packaged with the application. Any application deployed in the same domain now can use Hibernate. The obvious scenario where this style is not suitable is when one has multiple applications that depend on different versions of Hibernate. Otherwise, this placement in shared library can save other troubles (the bug that started it all has still not cast its shadow).

    Deploy & Run

    We now have Hibernate installed, Java sources written, configuration files and build script ready. Next step is to compile, package and deploy the application. So I did.

    $ ant -Dprovider=hibernate deploy

    I got a stack trace on the server side :(

    
    weblogic.application.ModuleException: Exception preparing module: EJBModule(hibernate-ejb.jar)
    
            at weblogic.ejb.container.deployer.EJBModule.prepare(EJBModule.java:399)
            at weblogic.application.internal.flow.ModuleListenerInvoker.prepare(ModuleListenerInvoker.java:93)
            at weblogic.application.internal.flow.DeploymentCallbackFlow$1.next(DeploymentCallbackFlow.java:360)
            at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)
            at weblogic.application.internal.flow.DeploymentCallbackFlow.prepare(DeploymentCallbackFlow.java:56)
            Truncated. see log file for complete stacktrace
    org.hibernate.HibernateException: The chosen transaction strategy requires access to the JTA TransactionManager
            at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:329)
            at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
            at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:915)
            at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:730)
            at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:127)
    
    

    The error message made sense. The configuration in persistence.xml had not specified the transaction type. Default transaction type in container environment is JTA. Hibernate is complaining as it is not told about the transaction manager in Weblogic Server. How about configuring Hibernate explicitly to use local transaction? I edited the persistence.xml to

    <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">

    And ran the build script again which will deploy and run the JUnit test,

    $ ant -Dprovider=hibernate

    To my pleasant surprise, this time the application deployed without any error. But failed the test

     
          [echo] Running JUnit Test: junit.TestJPAService ...
        [junit] Logical Persistence Provider is [hibernate]
        [junit] Actual  Persistence Provider is [org.hibernate.impl.SessionImpl]
        [junit] Test junit.TestJPAService FAILED
         [echo] *** ERROR: There are test failures. Output is shown below
       [concat] Testsuite: junit.TestJPAService
       [concat] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 2.934 sec
       [concat]
       [concat] ------------- Standard Output ---------------
       [concat] Contacting server t3://localhost:7001 as weblogic for JPAService
       [concat] ------------- ---------------- ---------------
       [concat] ------------- Standard Error -----------------
       [concat] Logical Persistence Provider is [hibernate]
       [concat] Actual  Persistence Provider is [org.hibernate.impl.SessionImpl]
       [concat] ------------- ---------------- ---------------
       [concat] Testcase: testLog(junit.TestJPAService):    FAILED
       [concat] Message is not assigned any identifier
       [concat] junit.framework.AssertionFailedError: Message is not assigned any identifier
       [concat]     at junit.TestJPAService.testLog(Unknown Source)
    
    
    One test passed. The bean did get injected with org.hibernate.impl.SessionImpl or rather its proxy.
    But why did it fail to assign an identifier to the Message instance?
    Simply because we designed the bean to use container managed transaction but now configured to use local transaction. So neither our bean code nor the container committed the transaction, and as identity value is specified to be assigned by the provider (see @Id annotation in Message.java) and Hibernate assigns such identity leveraging auto-increment feature of database columns -- so no commit also implied no assignment of identity value.
    We can, of course, add explict transaction demarcation in JPAServiceBean.log() method ; but that is not a solution. In container environment, we would like to use container managed transaction and configure the persistence unit to use JTA.

    How does one tell Hibernate to use Weblogic Transaction Manager and join its transactions with the container transaction? Further Google search and after few minutes the answer was found. Add the following properties to persistence.xml.

    
    	<property name="hibernate.transaction.factory_class"           value="org.hibernate.transaction.JTATransactionFactory"/>
    	<property name="hibernate.transaction.manager_lookup_class"    value="org.hibernate.transaction.WeblogicTransactionManagerLookup"/>
    	<property name="hibernate.transaction.flush_before_completion" value="true"/>
    	<property name="hibernate.transaction.auto_close_session"      value="true"/>
    
    

    Finally the test runs end to end with Hibernate

    We now have Hibernate configured for JTA transaction in Weblogic Server. The test now runs without error, showing that the right provider is being used and the message has been given a proper identifier.
    
    $ ant -q -Dprovider=hibernate
         [echo] =====================================================
         [echo]     Build Configuration for hibernate
         [echo] =====================================================
         [echo] Base directory  : D:\project\switch
         [echo] Deployed Target : D:\project\switch/JPAService.ear
         [echo] EJB Module      : D:\project\switch/tmp/hibernate-ejb.jar
         [echo] Configuration   : D:\project\switch/META-INF/hibernate/persistence.xml
         [echo] Packaging EJB Module for hibernate at D:\project\switch/tmp/hibernate-ejb.jar
         [echo] Packaging EAR Module for hibernate at D:\project\switch/JPAService.ear
         [echo] Packaging D:\project\switch/tmp/test-JPAService.jar for running the tests
         [echo] Undeploying JPAService from t3://localhost:7001 ...
         [echo] Deploying JPAService to t3://localhost:7001 ...
         [echo] Running JUnit Test: junit.TestJPAService ...
        [junit] Logical Persistence Provider is [hibernate]
        [junit] Actual  Persistence Provider is [org.hibernate.impl.SessionImpl]
        [junit] Persisted Message [id:1 timestamp:1182755464176 body:A message sent for logging on 1182755464166]
        [junit] Time elapsed between the message to send and persisted is 10ms
    
    

    Repeating the test showed that a new message has been created with ID:2.

    I checked the MySQL database and found that the primary key column is marked auto-increment as its value is configured to be auto-generated

    
    mysql> show create table message;
    mysql>| message | CREATE TABLE `message` (
      `id` bigint(20) NOT NULL auto_increment,
      `body` varchar(255) default NULL,
      `createdOn` datetime default NULL,
      PRIMARY KEY  (`id`)
     
     The configuration that create the table definition for us is <property name="hibernate.hbm2ddl.auto" value="create"/> 

    Summary on Hibernate Deployment

    To sum it up the steps to install and run a JPA application with Hibernate in Weblogic Server 10.0 environment.

    1. Add Hibernate libraries in shared library in Weblogic Server domain

    2. Configure JTA transaction and Automatic table definition properties

    3. Package, deploy and run the test to verify

    Let us now see what we need to do the run the exact same application with Kodo.

    Why you do not need to install Kodo in weblogic Server 10.0

    Kodo is an integral part of Weblogic Server 10.0. The core Kodo library comes with Weblogic Server installation and is available in ${bea.home}/modules/com.bea.core.kodo_4.1.3.jar. Kodo 4.1.3 is built on top of OpenJPA and Weblogic Server installation also supplies OpenJPA library at ${bea.home}/modules/org.apache.openjpa_0.9.7.jar. Kodo and OpenJPA depend on several other open source jars (most notably serp for byte-code enhancement) and specification jars such as jpa, jdo, jca or jta. All these requisite jars are available too in ${bea.home}/modules/ directory.

    To run the exactly same application with Kodo, all we need is a different persistence.xml

    persistence.xml
    01 <?xml version="1.0"?>
    02 
    03 <persistence xmlns="http://java.sun.com/xml/ns/persistence"
    04   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    05   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    06     http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
    07   version="1.0">
    08   <persistence-unit name="test" transaction-type="JTA">
    09     <provider>kodo.persistence.PersistenceProviderImpl</provider>
    10     <properties>
    11       <property name="kodo.ConnectionURL"           value="jdbc:mysql://localhost/kodoDB"/>
    12       <property name="kodo.ConnectionDriverName"     value="com.mysql.jdbc.Driver"/>
    13       <property name="kodo.jdbc.SynchronizeMappings" value="buildSchema"/>
    14     </properties>
    15   </persistence-unit>
    16 </persistence>

    If you compare with the configuration for Hibernate, the only significant change is to change the provider class name to kodo.persistence.PersistenceProviderImpl.
    The property names are different now. For example, automatic schema creation is configured in Kodo by setting kodo.jdbc.SynchronizeMappings to buildSchema.

    With this minimal change, I ran the test with Kodo as provider.

    $ ant -Dprovider=kodo

    
    $ ant -q -Dprovider=kodo
         [echo] =====================================================
         [echo]     Build Configuration for kodo
         [echo] =====================================================
         [echo] Base directory  : D:\project\switch
         [echo] Deployed Target : D:\project\switch/JPAService.ear
         [echo] EJB Module      : D:\project\switch/tmp/kodo-ejb.jar
         [echo] Configuration   : D:\project\switch/META-INF/kodo/persistence.xml
         [echo] Enhancing persistent classes
         [echo] Packaging EJB Module for kodo at D:\project\switch/tmp/kodo-ejb.jar
         [echo] Packaging EAR Module for kodo at D:\project\switch/JPAService.ear
         [echo] Packaging D:\project\switch/tmp/test-JPAService.jar for running the tests
         [echo] Undeploying JPAService from t3://localhost:7001 ...
         [echo] Deploying JPAService to t3://localhost:7001 ...
         [echo] Running JUnit Test: junit.TestJPAService ...
        [junit] Logical Persistence Provider is [kodo]
        [junit] Actual  Persistence Provider is [kodo.persistence.KodoEntityManagerImpl]
        [junit] Persisted Message [id:251 timestamp:1182762774929 body:A message sent for logging on 1182762774918]
        [junit] Time elapsed between the message to send and persisted is 11ms
    
    

    Everything has gone right. The previously deployed Hibernate unit is undeployed. The new deployment used Kodo as provider and returned the right provider.
    What schema did Kodo define?

    
    mysql> use kododb;
    mysql> show create table message;
     | message | CREATE TABLE `message` (
      `id` bigint(20) NOT NULL,
      `body` varchar(255) default NULL,
      `createdOn` datetime default NULL,
      PRIMARY KEY  (`id`)
    ) 
    
    
    Notice that Kodo did not mark the id for auto-increment as Hibernate did. Kodo assigns auto-generated identifier on its own.

    What about the bug?

    So far everything seems to work alright. We installed Hibernate, ran a application using Hibernate. Switched the provider to Kodo. You can do the similar test with OpenJPA (its provider is org.apache.openjpa.persistence.PersistenceProviderImpl. But you can even omit the provider because that is the default when used inside Weblogic Server). So where is the bug about switching provider?
    The bug appears if you decide to install Hibernate in a different way. Instead of placing Hibernate libraries in shared library of the domain, the libraries can be placed in the EAR itself. If that is the deployment packaging then the application can not be deployed and redeployed again (even with the same Hibernate as provider). Why?
    The answer takes us to a class javax.persistence.Persistence provided by JPA itself. This class is used as bootstrap. This class searches for available providers and asks each provider to create a persistence unit i.e. EntityManagerFactory. However, this bootstrap class javax.persistence.Persistence caches the PersistenceProvider classes in an internal static Set and does not refresh the Set members ever.
    Hence, if Hibernate is packages with Web Application W, once the user application directly or the injection process calls Persistence.createEntityManagerFactory(), the Hibernate persistence provider X gets loaded by classloader L1 of Web Application W and is cached in the static Set of javax.persistence.Persistence. Later the Web Application W is undeployed. L1 has gone out of scope. The application W is redeployed. Now the classloader is L2. Someone calls Persistence.createEntityManagerFactory() again. The code in javax.persistence.Persistence tries to invoke the method on X (loaded by L1 who is no more) and code starts breaking with ClassCastException and long stack traces.
    How to solve this bug?
    The easy solution is to adhere to the packaging scheme described here. The difficult solution is to change javax.persistence.Persistence itself. This is a difficult because of the process involved in upgrading/patching/redistributing a specification defined (and supplied) implementation class.
    In another installment of this ramblings, I will discuss the proposed changes in javax.persistence.Persistence class that will allow it to function better in an environment where multiple (possibly unrelated) classloaders are involved.
     
     

    Resources

    Download the source code, configuration and build files discussed in this article.

     

    Good Night and Good Luck


  • Comments

    Comments are listed in date ascending order (oldest first) | Post Comment

    • Where is the rest of this post?

      Posted by: mindows on July 2, 2007 at 12:09 PM

    • I think it was problem with the new Windows Writer and dev2dev site that ate up the post. Now I had retried to put the entire post and hopefully all the bits will appear now. Thank you for reading.

      Posted by: pinaki.poddar on July 2, 2007 at 1:12 PM

    • I have just encountered the horror conflicting antlr. Just put a named query in our Message Class, you will get a named query error, caused by a NoClassFoundException of the class HqlToken... I installed hibernate in the shared way described here...

      Posted by: jpsantos on July 27, 2007 at 1:30 PM

    • When I was trying to deploy a JSP - EJB3 session bean - Hibernate JPA application in WebLogic 10, I got "URI is not hierarchical" error. Can you please post a web example of EJB3 session bean/Hibernate JPA.

      Posted by: mat_sunil on September 12, 2007 at 8:28 AM

    • Remember encountering this "URI is not hierarchical" error, but unfortunately do not recall how it got resolved (most probably it was a packaging issue).

      I hardly work on web-side of things ... but in this blog pages, once I presented an example that different access mechanics namely JSP, Ajax via DWR to stateless Session Bean that invokes a JPA-based services. May be that can give you some pointers...

      http://dev2dev.bea.com/blog/pinaki.poddar/archive/2006/05/from_ejb_30_to.html

      Posted by: pinaki.poddar on September 12, 2007 at 8:44 AM

    • I believe, I have found the answer to the "URI is not hierarchical" error. It has to do with this bug:
      http://jira.jboss.com/jira/browse/EJBTHREE-719

      Using this patch:
      http://jira.jboss.com/jira/secure/attachment/12313451/ArchiveBrowser.java and repackaging jboss-archive-browsing.jar solved that issue for me.

      However, now I believe I'm stuck between two issues. To resolve the Antlr issue, it is recommended to keep the Hibernate jars in the application with an EAR structure and put the following in weblogic-application.xml:
      <wls:prefer-application-packages> 
      <wls:package-name>antlr.* </wls:package-name>
      </wls:prefer-application-packages>
      
      However, taking this approach does not allow getting around the Classloader issue discussed at the end of this article. If I encounter a solution, I will post it. Given that this is detailed in the release notes for Workshop 10.1 with CR326466, restarting the server during application redeployment seems like the only solution at this point.

      Posted by: jbayer on December 17, 2007 at 5:48 PM

    • Thank you very much!! You solved my problems! Excelent explanation.

      Posted by: ersbea on December 21, 2007 at 1:31 PM

    • Hi, I have tried out the JPA Hibernate combination in weblogic. One main problem i am facing is trapping Exceptions.The persist method of EntityManager is not giving any Exceptions (Checked). So the final Exception i got an APPsSetRollBack Only Exception from weblogic. How will i identify the root cause of the problem like if primary key violation,forign key violation etc from database . Can any one give a suggestion on this.

      Posted by: harikrishnan_pillai@infosys.com on April 17, 2008 at 1:54 AM

    • The exception raised at persistence tier (e.g. OptimisticLockException) often gets wrapped/buried/nested in the long stack traces by the upper layers before the user application receives an exception. One way will be to write a simple utility that recurse through an given Throwable to look for a specific Throwable of type T. I have such a utility somewhere in my laptop and if you want I can post it to you.

      Posted by: pinaki.poddar on April 21, 2008 at 8:01 AM

    • Hi Pinaki, Nice blog. I have some issues while using OpenJPA. I have some codes already written in KODO and I want to use OpenJPA instead of KODO. I am using Weblogic Workshop 10 for development. I tried 2 ways to switch from KODO to OpenJPA: 1. In the existing KODO project, removed all the Kodo jars like kodo.jar, jpa.jar, serp.jar etc. and used OpenJPA jars downloaded from apache site (openjpa-0.9.7 and higher). Modified the persistence.xml to use OpenJPA provider. But this one didn't work. I got following exceptions: org.apache.openjpa.persistence.PersistenceException: There were errors initializing your configuration: com.solarmetric.license.LicenseException: No product license key was found. Plea se ensure that you either have a valid "license.bea" file in your CLASSPATH or in the directory spec ified by the "bea.home" environment variable, or else that you have a valid license key specified in the "kodo.LicenseKey" property of you Kodo configuration. If you need to request an evaluation lice nse, please go to http://commerce.bea.com or contact sales@bea.com.

      2. Other option I tried is, created a new project and applied OpenJPA jars and then copied all the kodo source files to it. Modified the persistence.xml for OpenJPA. But this too didn't work. I got the following exception: org.apache.openjpa.persistence.ArgumentException: Could not locate metadata for the class using alias "ProspectActivity". Registered alias mappings: "{CredentialMap=[class com.bea.common.security.store.data.CredentialMap], DomainRealmScope=[class com.bea.common.security.store.data.DomainRealmScope], WLSCredMapCollectionInfo=[class com.bea.common.security.store.data.WLSCredMapCollectionInfo], ResourceMap=[class com.bea.common.security.store.data.ResourceMap], BEASAMLRelyingParty=[class com.bea.common.security.store.data.BEASAMLRelyingParty], BEASAMLAssertingParty=[class com.bea.common.security.store.data.BEASAMLAssertingParty], PKIResourceMap=[class com.bea.common.security.store.data.PKIResourceMap], IdPPartner=[class com.bea.common.security.store.data.IdPPartner], Credential=[class com.bea.common.security.store.data.Credential], XACMLRoleAssignmentPolicy=[class com.bea.common.security.store.data.XACMLRoleAssignmentPolicy], PKITypeScope=[class com.bea.common.security.store.data.PKITypeScope], XACMLTypeScope=[class com.bea.common.security.store.data.XACMLTypeScope], WLSPolicyCollectionInfo=[class com.bea.common.security.store.data.WLSPolicyCollectionInfo], BEASAMLPartner=[class com.bea.common.security.store.data.BEASAMLPartner], WLSRoleCollectionInfo=[class com.bea.common.security.store.data.WLSRoleCollectionInfo], ProspectActivity=null, Top=[class com.bea.common.security.store.data.Top], WLSCertRegEntry=[class com.bea.common.security.store.data.WLSCertRegEntry], Endpoint=[class com.bea.common.security.store.data.Endpoint], UserPasswordCredential=[class com.bea.common.security.store.data.UserPasswordCredential], SPPartner=[class com.bea.common.security.store.data.SPPartner], PasswordCredentialMap=[class com.bea.common.security.store.data.PasswordCredentialMap], Partner=[class com.bea.common.security.store.data.Partner], XACMLEntry=[class com.bea.common.security.store.data.XACMLEntry], RegistryScope=[class com.bea.common.security.store.data.RegistryScope], PasswordCredential=[class com.bea.common.security.store.data.PasswordCredential], XACMLAuthorizationPolicy=[class com.bea.common.security.store.data.XACMLAuthorizationPolicy]}" at org.apache.openjpa.meta.MetaDataRepository.getMetaData(MetaDataRepository.java:345) at org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getClassMetaData(JPQLExpressionBuilder.java:164) at org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.resolveClassMetaData(JPQLExpressionBuilder.java:142) at org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getCandidateMetaData(JPQLExpressionBuilder.java:211) at org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getCandidateMetaData(JPQLExpressionBuilder.java:181) Truncated. see log file for complete stacktrace

      I really don't know whats happening as entity is already defined in persistence.xml and in one of ur blog u told to initialize the entity before using it; I did that too but to no avail. I tried using latest versions of OpenJPA as well, but still the same error. Can you please guide me how to get rid of this error? Thanks, Meghnath

      Posted by: mac1sam on April 21, 2008 at 1:47 PM



    Only logged in users may post comments. Login Here.

    Powered by
    Movable Type 3.31