Home 文章 Weblogic 插入式持久性提供者的承诺:Kodo、OpenJPA和Hibernate

feedsky
抓虾
google reader
my yahoo
插入式持久性提供者的承诺:Kodo、OpenJPA和Hibernate E-mail
Weblogic
作者是 Administrator   
2007-11-29 06:20:37

  我的邮箱中有一个bug报告。它报告了在Weblogic server环境中的Hibernate和Kodo之间切换JPA持久性提供者时的一个问题。在再现这个bug的过程中,我学到了一些知识,包括如何在Weblogic Server 10.0中安装Hibernate,以及如何使用一个特定的持久性提供者X部署基于JPA的Web应用程序,然后再使用另一个持久性提供者Y重新部署它而不会使整体(指Weblogic Server)崩溃。

  这次经历促使我在一段停顿之后又开始了文章写作(New Yorker杂志的一幅 漫画 触发了我的写作灵感)。

   总之,这篇文章将:

  1. 描述在Weblogic Server 10.0中安装和配置Hibernate的工作过程
  2. 解答为何不必在Weblogic Server 10.0中安装Kodo或OpenJPA
  3. 展示一个非常简单的基于无状态会话Bean的服务的例子,并演示在Weblogic Server运行状态下在Hibernate、OpenJPA和Kodo之间切换JPA提供者的步骤
  4. 再现产生问题的bug,不仅是因为报告者认为这很严重,在经过简短的实验之后,我也认为如此
  5. 介绍这个bug如何造成应用服务器环境中JPA引导的变化

测试提供者切换的一个非常简单的方法

  我使用了一个简单的服务来检验是否使用了正确的提供者并且运行无误——虽然只是初步的持久性操作。这个简单的服务接口(我将基于它进行测试)如下所示:

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 } 

  关于这个服务将如何实现还不明确;甚至没有说明它将使用JPA。这就是服务定义应有的方式——实现技术的不明确性(这里省略了通常的 IMO——这是我的观点——也许是谦虚的,也许恰恰相反)。

基于JPA和会话Bean的应用程序入门

  我将简短地讨论一下如何在使用JPA的无状态会话Bean中实现这个服务。这是相当基本的知识。如果您已经熟悉JPA,可以直接跳到 下一节

   在这个简单的例子中,一个使用JPA的无状态会话Bean实现了这个服务。

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    * test.
24    * It is this name which must be specified in the configuration file
25    * META-INF/persistence.xml as
26    * 
27    *   
28    * 
29 */ 30 @PersistenceContext(unitName="test") 31 EntityManager em; 32 33 /** 34 * Returns the EntityManager class name that provides the persistence 35 * service. 36 *

37 * NOTE: 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 }

  这个会话bean本身也有不明确的地方。它使用了JPA,但没有声明将要使用哪一个特定的持久性提供者(事实上,定义一个工业标准的持久性API的主要目的之一是为了能够从一个提供者切换到另一个)。

  这个bean并未直接与一个 JPA提供者建立连接,而是依靠容器新获得的功能(即,从JEE 5以来)在需要时注入JPA连接。另外,有个好消息是Weblogic Server 10.0是第一个兼容JEE 5的应用服务器。

  容器能够以两种形式向会话Bean注入JPA依赖关系:@PersistenceUnit或@PersistenceContext注释。我们所使用的是@PersistenceContext(JPAServiceBean.java的第 30和31行)。

  如果让我将它与JDBC进行粗略的类比,那么@PersistenceUnit相当于一个DataSource,而@PersistenceContext则相当于一个Connection。在JPA术语当中,这一对概念是由JPA定义的接口EntityManagerFactory和EntityManager分别实现的。

  通过这些接口和少数其他的方式(例如,Query),JPA已经定义了一个比传统的JDBC通过SQL提供的方式更高层的面向对象抽象与数据库进行交互。通过JPA,您可以借助面向对象视图与底层数据库进行交互。应用程序可以通过 EntityManager接口中的方法创建、更新、查询和删除对象——JPA提供者与应用程序协同工作截听这些对象级的改变并且适当地将它们与相应的INSERT、UPDATE、SELECT和DELETE数据库操作映射起来。

  这个基本的JPA应用程序已经完成,下面是持久性类。

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  * 

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 *

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 timestamp 35 * because many databases will reserve that word. 36 *

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 }

  因为我们使用了O-R映射注释表示Message类的实例与数据库表和列之间的映射关系(或者说是哪个域值能够作为Message实例的主标识符),所以JPA依赖关系已经移入这个Java类中。这些映射信息可以移到一个单独的orm.xml中,从而使这个类恢复Pure Old Java Object (POJO)状态并完善这种不明确的更接近域对象模型的方法。

编码前的测试

  JUnit测试用例通过JNDI查找与服务进行交互并检验两个服务方法返回的是否为预期的结果。

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  * 
  • jndi.name : the JNDI name of the registered Session Bean. 17 *
  • persistence.provider: The logical name of the 18 * persistence provider. Allowed values are kodo or hibernate. 19 *

    20 * The optional Java System Properties are: 21 *

  • weblogic.user : The authenticated user to contact Weblogic server. Defaults to weblogic 22 *
  • weblogic.password : The valid password to contact Weblogic server. Defaults to weblogic 23 *
  • weblogic.url : The URL where Weblogic Server is running. Defaults to t3://localhost:7001 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= 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 logical 61 * name of the intended provider i.e. kodo or hibernate. 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 }
  •   testProvider()测试方法检验这个服务是否为JPA提供者的包含提供者逻辑名的 EntityManager的实现返回了一个类名。另一个测试方法检验服务返回的Message实例是否拥有提供者分配的正确身份。

    构建JPA应用程序和切换提供者的Ant 脚本

      在运行代码之前,最后一步是使用 Ant 编写的 构建脚本

       我们需要使这个脚本编译Java类,将它们封装至EJB模块和EAR中,然后在 Weblogic Server上部署和撤除它们,并运行 JUnit测试。

      由于在切换提供者的时候会有些细微的变化,所以使得这种例行任务的标准构建文件有些复杂。每个提供者使用persistence.xml都有所不同。单独的配置文件位于META-INF/hibernate和META-INF/kodo中,构建脚本负责在可部署的包中包含正确的配置。同样,构建脚本使用提供者的逻辑名(例如,hibernate-ejb.jar、kodo-ejb.jar或openjpa-ejb.jar)创建EJB模块。

      构建的另一个重要的方面是OpenJPA 和Kodo都要求一个编译后增强的步骤。可以使用–javaagent将这个编译后的步骤放到运行时,但是我更愿意用构建脚本进行增强,而不是用–javaagent请求Weblogic server。您可以在 OpenJPA用户手册 中获得有关增强的更多内容。

       为了条件执行,build.xml中将含有一个<target name="check-provider">。

       将通过有效的提供者名称调用这个构建脚本

    $ ant -Dprovider=hibernate

      hibernate、kodo和openjpa都是有效的提供者值。

      默认的构建目标将清理、编译、有条件地增强、封装和撤除先前的部署(如果有的话),并且部署、运行测试和报告错误(如果有的话)。

      通过 build.properties 文件您指定 ${bea.home}和用户凭证等作为您的环境配置脚本。

       为了进行部署,构建脚本用以下内容封装了JPAService.ear:

    $ 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

      在JEE历史上,最简单的Enterprise Application Archive有一个单独的EJB模块,如以下的application.xml 所示(为便于可读性,并未包含的XML头部和命名空间声明):

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

      EJB模块hibernate-ejb.jar看上去不错吧?

    $ 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 

      这个EJB模块包含服务接口、会话bean实现和持久性实体类。Jar还有两个配置文件:用于持久性配置的persistence.xml和用于在Weblogic Server中配置会话bean的weblogic-ejb-jar.xml。这些文件必须能作为META-INF/persistence.xml和META-INF/weblogic-ejb-jar.xml的资源被活动的类装载器加载。

    配置Hibernate持久性单元

      JPA提供者通过persistence.xml配置特定的持久性单元。在这个文件中,需要提供以下内容:

    • 持久性单元的名称
    • 支持的事务类型;选项是RESOURCE_LOCAL和JTA
    • 提供者的类名
    • 持久性Java类名列表或者一个包含了它们的Jar文件。您可以不定义它们,但最好别这样——这个问题不在我们当前的讨论范围之内
    • 映射文件的名称,如果您认为O-R映射注释违反了POJO的本性
    • 将要使用的数据库。在一个容器环境中,您可以指定一个JTA或非JTA数据源的名称,数据源是通过其他方式配置的。此外,您也可以分别指定url、用户凭证和驱动类名称(这也是我们将在这里采用的方法)。

      除此之外,每个提供者还支持许多可配置的属性。如我们将看到的那样,要成功地使用 O-R映射,就要了解这些参数的意义,而且更重要的是知道在哪里应用它们。XML的persistence.xml控制模式允许通过<property name="a.b.c" value="xyz"/>标记指定特定于提供者的属性。不同的提供者使用不同的属性名称,即使它们意义相同。因此,当我们需要从一个提供者向另一个提供者切换应用程序的时候,这些属性成为至关重要的因素。

      通过Google进行了一次快速的搜索之后,我配置了Hibernate的persistence.xml,具体如下

    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> 
    

      ?<provider>指定为org.hibernate.ejb.HibernatePersistence。同样,配置Hibernate以url的方式使用MySQL,即jdbc:mysql://localhost/hibernateDB。

    在Weblogic Server上安装Hibernate

      现在需要下载Hibernate。Hibernate的 站点 整洁、内容丰富,而且下载过程没有一点儿延迟。两个单独的下载包括Hibernate Core version 3.2和一个称为Hibernate EntityManager vers的JPA覆盖。

    <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>
    
    • 如何告知Weblogic Server关于Hibernate类库的信息?但最重要的是我们需要哪个类库?在Hibernate Core中,我发现了39个库,而它的 JPA覆盖有6个之多。我采用了最省事的方式。在这个简单的 Ant任务的帮助下,将所有的45个库放入了Weblogic Server实例域的共享库(即${bea.home}/wlserver_10.0/samples/domains/wl_server/lib)中。

      当然,有些库应该更容易区分,应该除去那些明显的诸如jta.jar或junit-3.8.1.jar的库。现在这些库中许多是Weblogic Server 10.0集成的一部分。实际上,已经在${bea.home}/modules目录中发现了140个jar。要知道有很多关于Weblogic中的antlr库与Hibernate中的antlr库之间发生冲突的 恐怖故事,不过我还未遇到过。

       关于在共享空间放置Hibernate库的重点是它们没有与应用程序封装在一起。在相同的域中部署的任何应用程序现在都可以使用Hibernate。当有许多依赖于不同Hibernate版本的应用程序时,很明显这种方式不再适用了。否则,在共享库中的这种放置方式可以避免其他的问题(产生问题的bug还没有投下阴影)。

    部署和运行

      现在,已经安装了Hibernate,编写了Java源,配置文件和构建脚本也已经就绪。下一步要对应用程序进行编译、封装和部署。我正是这样做的。

    $ ant -Dprovider=hibernate deploy

      在服务器端,我得到一个堆栈跟踪:(

    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)
    

      这个错误消息是有意义的。persistence.xml的配置没有指定事务类型。容器环境中默认的事务类型是JTA。当未将它被告知Weblogic Server的事务管理器的信息时,Hibernate会有所抱怨。配置Hibernate使之明确使用本地事务怎么样?我编辑了persistence.xml

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

      然后再次运行构建脚本,它将部署并运行JUnit测试,

    $ ant -Dprovider=hibernate

      让我惊喜的是,这次部署的应用程序没有出错。但是测试没有成功。

       [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)
    

      通过了一个测试。用org.hibernate.impl.SessionImpl,确切地说,是它的代理对这个bean进行了注入。

      但是,为什么向 Message实例分配标识符没有成功?

       这只不过因为我们设计bean使用容器管理事务,而现在却配置为使用本地事务。所以我们的bean代码和容器都没有提交事务,而且因为标识值指定为由提供者赋值(见Message.java中的@Id注释)并且由Hibernate利用数据库列的自动增量特性为标识赋值——所以也没有提交表示没有标识值赋值。

       当然,我们能够在JPAServiceBean.log()方法中添加明确的事务分界;但这不是解决方案。在容器环境中,我们倾向于使用容器管理的事务,倾向于配置持久性单元使用JTA。

      如何告知Hibernate使用Weblogic事务管理器并将它的事务与容器事务结合起来?我通过Google进行了几分钟的搜索找到了答案。向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"/>

    最后,使用Hibernate进行端到端的测试

      现在,我们专为Weblogic Server的JTA事务配置了Hibernate。这时运行测试没有出现错误,表明使用了正确的提供者,并为消息给定了一个正确的标识符。

    $ 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
    

      重复测试表明已经用ID:2创建了新的消息。

      我检查了MySQL数据库,发现当它的值配置为自动生成的时候,主键列标记为自动增加。

    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"/>

    关于Hibernate部署的摘要

      总结起来,在Weblogic Server 10.0环境中安装和运行使用Hibernate的JPA应用程序步骤如下。

      1. 在Weblogic Server 域中的共享库里添加Hibernate库

      2. 配置JTA事务和自动的表定义属性

      3. 封装、部署和运行测试进行验证

      现在来看一下要采用Kodo运行完全相同的应用程序,我们需要做些什么。

    为何不需要在weblogic Server 10.0中安装Kodo

      Kodo是Weblogic Server 10.0整体的一部分。核心Kodo库随Weblogic Server安装一起提供,可以在${bea.home}/modules/com.bea.core.kodo_4.1.3.jar中获得。Kodo 4.1.3构建于OpenJPA之上, Weblogic Server安装后还在${bea.home}/modules/org.apache.openjpa_0.9.7.jar中提供OpenJPA库。Kodo和OpenJPA依赖于其他几个开源jar(其中最著名的是用于字节码增强的serp)和规范jar,如jpa、jdo、jca或jta。所有这些必要的jar也可以从${bea.home}/modules/目录中获得。

      为了使用Kodo运行完全相同的应用程序,只需一个不同的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>
    

      与使用Hibernate时的配置比较,惟一显著的变化是将提供者类名改为kodo.persistence.PersistenceProviderImpl。

      现在属性名不一样了。例如,在Kodo中可以通过设置kodo.jdbc.SynchronizeMappings为buildSchema来配置自动模式创建。

      经过最小限度的改变,我再使用Kodo作为提供者运行测试。

    $ 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
    

      一切顺利。先前部署的Hibernate单元撤除了。新的部署使用Kodo作为提供者,并返回了正确的提供者。

      Kodo定义了什么模式呢?

    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`)
    )
    

      注意,Kodo没有像 Hibernate那样标记auto-increment的id 。Kodo由自己指派自动生成的标识符。

    这个bug是怎么回事?

      到目前为止,一切似乎都运行良好。我们安装了Hibernate,然后使用Hibernate运行了一个应用程序。然来又将提供者切换为Kodo。您可以用OpenJPA进行类似的测试(它的提供者是org.apache.openjpa.persistence.PersistenceProviderImpl。但是,由于在Weblogic Server中这个提供者是默认的,所以您甚至可以省略它)。那么切换提供者产生的bug在哪儿呢?

      如果您决定以不同的方式安装Hibernate,那么就会出现bug。如果不把Hibernate库放在域的共享库中,Hibernate库还可以放在EAR里。如果这样进行部署封装,则应用程序就不能再次进行部署和撤除(即便将同样的Hibernate作为提供者)。这是为什么呢?

      答案就在于JPA自身提供的一个javax.persistence.Persistence类。这个类用于引导。它搜索可用的提供者并要求每个提供者创建一个持久性单元,即EntityManagerFactory。然而,这个引导类javax.persistence.Persistence会将PersistenceProvider类缓存在一个内部静态Set中,并且不再对Set成员进行更新。

      因此,如果用Web应用程序W封装了Hibernate,一旦用户应用程序直接调用或者由注入过程调用Persistence.createEntityManagerFactory(),则Hibernate持久性提供者X将被Web应用程序W的类装载器 L1加载,并且缓存在javax.persistence.Persistence的静态Set中。随后,Web应用程序W被撤除。L1便会离开作用域。Web应用程序W将被重新部署。这时的类装载器是L2。如果程序再次调用Persistence.createEntityManagerFactory(),则javax.persistence.Persistence中的代码将试图调用X的方法(由L1加载的,而L1已经不在了),而且代码将开始与ClassCastException和长的堆栈跟踪断开。

      如何解决这一bug呢?

      一个简单的解决方案是遵守这里所描述的封装模式。还有一个有难度的方案是修改javax.persistence.Persistence本身。这个方案有难度是因为这一过程中要升级/修补/重新分配一个按照规范定义的(提供的)实现类。

      在另一篇文章中,我将讨论在如何对javax.persistence.Persistence类进行修改,从而允许在涉及多个(可能是不相干的)类装载器的环境中更加良好地运行。

    参考资料

    下载 本文所讨论的源代码、配置文件和构建文件。

    最近更新 ( 2007-11-29 06:20:37 )
     
    Java家,