2004年01月25日

实例回调


你的持久化类可以通过继承javax.jdo.InstanceCallbacks接口来实现回调。这个接口包括4个方法:


jdoPostLoad:这个方法在你的持久化类从数据库中提取数据的时候被JDO实现调用。有关持久字段与数据库字段的映射和提取的描述在JDO源文件中进行描述。


jdoPreStore:这个方法在你向数据库写入数据时被调用。


jdoPreClaer:这个方法在你清除持久化字段的值之前被调用。


jdoPreDelete: 这个方法在从数据库中删除一个对象的时候被调用。


PersistenceCapable接口不同,如果你要实现回调那么这个接口里的所有方法你都得自己来实现。


下面给出一个实例回调得例子:


public class Host   implements InstanceCallbacks


{


       //由于InetAddress字段不能被JDO直接持久化,所以我们通过jdoPostLoad


       //jdoPreStore方法间接的通过主机名这个字段来实现对它的存储。


    private transient InetAddress address;    // 非持久化字段


    private String    hostName;   // 可持久化字段


    // 定义一个HashSet对象准备把InetAddress放进去


    private Set devices = new HashSet ();


    public void jdoPostLoad ()


    {


        //通过主机名获得 InetAddress


        try


        {


            address = InetAddress.getByName (hostName);


        }


        catch (IOException ioe)


        {


            throw new JDOException (“Invalid host name: ” + hostName, ioe);


        }


    }


    public void jdoPreStore ()


    {


        // 获得主机名


        hostName = address.getHostName ();


    }


    public void jdoPreDelete ()


    {


        // 当主机地址被删除时,删除所有相关的信息


        JDOHelper.getPersistenceManager (this).deletePersistentAll (devices);


    }


    public void jdoPreClear ()


    {


    }


}

JDO元数据介绍


JDO中,每一个可持持久化类都有一个相应的元数据文件,这个元数据文件的作用体现在3个方面:


1、  维护持久化类的一致性


2、  重载缺省的持久化行为


3、  JDO实现提供持久化类本身无法提供的附加信息。


JDO元数据文件是XML格式的文件。这个文件的根元素必须是jdo,另外他有一个唯一的必须的子元素package元素,package元素代表持久化类所在的包,他有一个name属性,这个属性的值代表package的完整名称。下面我给出一个最基本的jdo元数据的例子:


<?xml version=”1.0”?>


<jdo>


 <package name=”com.lgd.test”>


   …………….


 </package>


<package name=”org.lgd.test”>


   …………….


 </package>


</jdo>


package元素可以包含1nclass元素,还可以有0n个扩展元素,这些扩展元素通常提供特定的JDO厂商实现的信息。扩展元素一般是extension元素,他有3个属性:


vender-name:厂商的名称,必须有的属性。


Key:扩展属性的名称,每个JDO实现厂商都会提供一系列的属性名。


Value:扩展属性的值


Package中的每个类的描述用class元素,calss元素需要一个name属性来描述他的名称,name属性的写法一般遵循以下价格原则:


l         如果这个clss元素包含在package元素中,那么他的name可以直接写类名就可以了,不用包含包名,比如com.lgd.test.HelloWorld这个类他的name直接写HellpWorld就可以了。


l         同样的对于java.lang,java.util,java.math中的类也可以直接写他的类名。


l         除了上面两种情况,类的name必须是完整的名称,要包含具体的包路径。


l         如果所描述的类是一个内部类,那么他的写法是这样的,parent-calss$inner-class.比如:Human$Man


下面让我们来看看class具体有那些属性:


l         name:类的名称,必须的属性。


l         persistence-capable-supperclass:如果这个类的超类也是一个可持久化的类,并且你希望JDO实现明白他们直接的继承关系,你就要在这里指定。


l         identity-type:它指定了持久化类的一致性类型,如果你定义了objectid-class那么这个属性默认为application否则就是datastore.


l         objectid-class:对于应用程序JDO一致性的持久化类要指定他的JDO一致性类,这个类位于继承树的最底层的类。


l         requires-extent:如果你不从这个类中查询数据,那么可以把它设置为false,缺省情况下是true.


下面给出一个有关class元素的例子:


<?xml version=”1.0″?>


<jdo>


    <package name=”com.lgd.test”>


        <class name=”Human” objectid-class=”Human$ObjectId”>


           


        </class>


        <class name=”African”>


            


        </class>


        <class name=”Asian” persistence-capable-superclass=”Human”>


            


        </class>


        <class name=”Asian$Chinese”>


            


        </class>


    </package>


</jdo>


calss元素还包含extensionfield子元素。我们先来看看field元素。它用来描述持久化类中的字段。这个字段是一个可选字段,如果你不在元数据文件中用field字段来描述, JDO会使用缺省的描述每一个字段,这样一来就给我们省去了很多工作。让我们来看看filed元素都有那些属性:


l         name:字段的名称,要和你在持久化类中的字段名称一致。


l         persistence-modifier:定义JDO如何操作这个字段,如果是可持久字段值为:persistent。如果是非持久字段但是可以被回滚的字段,值为transactional。如果都不是,那么值为none。这些字段的缺省要根据字段的类型而定:


m            如果字段被声明为fianl,transientstatic那么他的缺省值为:none.


m            如果字段类型为原始类型或原始类型的封装类型,那么他的缺省值为:pesistent


m            如果字段类型为java.util.Date,java.util.Locale,java.lang.String,java.lang.Number, java.math.BigDecimailjava.math.BigInteger那么缺省值为:persistent


m            如果字段类型为用户定义的可持久类型那么缺省值为:persistent


m            我们前面说过的数组类型的字段缺省值为:persistent


m            字段类型如果是java.util包中的以下类型缺省为persistent.: Collection, Set, List, Map, ArrayList, HashMap, HashSet, Hashtable, LinkedList, TreeMap, TreeSet, Vector


m            其他的类型缺省为none


l         primary-key:如果这个字段是主键字段就把它设置为true,缺省为false


l         none-value:缺省设置为none代表设置一个空值到数据库中。


l         default-fetch-group:原始类型和原始封装类型缺省为true其他类型设置为false


l         embedded: 原始类型和原始封装类型缺省为true其他类型设置为false


   所有的元素都可能会有一个extension的扩展元素,数组(array)元素和集合(collection)元素会有一个embedded-element属性,它定义了元素或数组的内部元素的类型(embedded-type)


l         key-type:对象用于映射的键,缺省为java.lang.Object


l         embedded-key:内嵌的键


l         value-type:键的值


l         embedded-value:内嵌的键值


下面给出一个包含所有元数据文件元素的例子:


<?xml version=”1.0″?>


<jdo>


    <package name=”org.lgd.test”>


        <class name=”Human” objectid-class=”Human$ObjectId”>


            <field name=”ID” primary-key=”true”/>


            <field name=”name” primary-key=”true”/>


            <field name=”dress”>


                <collection element-type=”Dress”/>


            </field>


        </class>


        <class name=”Dress”>


            <field name=”color”>


                <map key-type=”String” value-type=”ColorTye”/>


            </field>


        </class>


        <class name=”ColorTye”>


            <field name=”red” embedded=”true”/>


                     <field name=”blue” embedded=”true”/>


        </class>


    </package>


    <package name=”org.lgd.test”>


        <class name=”Human”/>


        <class name=”Asian” persistence-capable-superclass=”Human”>


            <field name=”country”>


                <collection element-type=”Asua$Chinese”/>


                <extension vendor-name=”kodo” key=”inverse-owner” value=”Human”/>


            </field>


        </class>


        <class name=”Human$Asian”/>


    </package>


</jdo>


JDO文件的位置:


JDO元数据文件是一个资源文件,在类增强和运行时都会使用到它,如果这个元数据是对一个类的描述,那么他的名称就是类的名称,它与类文件放在同一个目录下。如果JDO元数据文件包含一个包下面的所有类的描述那么它就放在相应的包所在的路径下,也可以是整个包层次中所有的类的描述,比如:对于Human.class的元数据描述文件可以是Human.jdo或者把它包含在一个package.jdo中,那么这个文件的位置可以是:


com/lgd/test/Human.jdo


com/lgd/package.jdo


com/package.jdo


package.jdo

由于JDO元数据文件是一个资源文件,所以它也可以放到.jar文件中被访问。



JDO vs实体Beans:一个善意的忠告


两年前SUN提出了EJB1.1规范,这对于企业级计算来说是一个具历史意义的革新。然而,它存在的缺陷也是不言而喻的。在许多方面,他的远程接口调用很慢而且创建一个平滑的(fine-grained)对象也很困难。


之后又出现了JDO,这是一种新的向数据库存储对象的方法,在很多地方它可以代替EJB实体Beans。看起来JDO是针对实体Bean执行问题的一种解决方案。在大多数应用程序中,数据库的操作基本上就是查询和更新字段的值。很多JSP只需要实现简单的显示和修改操作,因此,EJB的远程接口就变成了一个巨大的开销;远程的gettersetter方法也显得没有必要,尤其是你在重复的调用相同的getter方法的时候。因此,如果能够在客户端操作数据然后把结果一次发送到服务端应该更加合理。


JDO认识到了这一点。实际上,它可以被看作是面向对象的JDBC接口。对于很多开发人员来说,它可以说是一个很好的解决方案,但它也存在着不足。


EJB实体Beans真正的优势在于他们针对服务端的业务逻辑,而JDO却不能,因为JDO是针对客户端的。但是要想明白这一点并不容易,因为当前有关EJB的面向对象设计模式很少。没有现成的方法或继承可以使用。不过我们可以试着想像EJB完全实现了面向对象(实际上这并不容易做到,也许需要很长的时间来完成)。那么,服务端的业务逻辑将成为应用程序设计中的重中之重。比如,我们有一个自定义的customer实体,它有一个方法getCreditLimit()方法,这个方法可以被不同的customers扩展。这样一来就有可能用这些实体Beans来创建一个完全的面向对象的应用程序架构。那么有什么办法使EJBget-set操作更加有效率呢?


EJB接口的改进


EJB2.0中引入了依赖对象的概念。它对EJB的执行效率作出了改进,但还不是很完善。因此,无法在设计时就把对象当作依赖对象来处理。因为一个对象在处理是必须是一个实体;这样在你就可以只显示实体的一个本地化的拷贝,然后使用这个接口来存储数据:

interface EModel extends 
  PossiblySomeKindOfResultSetNavigationInteface ???{
    setField1();
    getField1();
}

下面是实体Bean接口:

interface ERemote extends EJBObject{
        setField1();           //等同于Emodel中的方法
        getField1();
        EModel getModel();
void  setModel(EModel)
        void doSomeServerSideBusiness();      
}

如果我们只想获得一个数据对象(假设Emodel可以通过一个ID来操作EJB句柄)那么home接口就可以包含一个特定的findByModel(Emodel model)方法来得到实体对象。同理我们还可以定义更多的方法来操作EJB。比如下面的方法通过传入的getter或setter获得所需的序列化对象:

Object getModelWith(Object modelGetter, 
  Object getterHintsOrMapper);
void setModelWith(Object modelToSet, 
  Object modelSetter, Object setterHintsOrMapper);

这样的设计可以给我们设计的程序带来极大的灵活性。我们可以通过插件的形式扩展我们的EJB容器,以实现映射XML,JDOSQL查询结果集。这些扩展的功能可以由开发人员来完成,当然也可以通过一些工具的支持来完成这些工作比如Castor。这样就可以控制很多XML消息。比如,一个负责开发货单(invoice)的实体Bean可以控制很多发货单XML消息。


非应用服务器提供商的开发人员可以使用上面介绍的接口。如果由应用服务器提供商来实现上面的接口的话,这个映射的过程就可以变的自动化了。如果容器能够提供一些标准的API,会使运行变得更快。比如,一个数据库服务供应商可以通过XML消息直接产生并运行SQL


同样的,在使用EJB中的finder方法的时候,程序就可能可以判断出到底需要的使什么信息:是一个实体对象或是一个数据?而且finder方法也可以实现一种机制,这种机制可以在数据连接已经被关闭的情况下接收来自客户端的查询。另外,也可以通过这种机制实现数据的过滤,比如,EJB中的finder方法得到的是整个符合条件对象集合(Collection),但也许我们只想得到某一个字段的结果集,那么就可以通过这种方法来实现数据的过滤。


IBM在旧金山的一个组织曾经实现了一个类似的架构。通过一个特定的处理划分模型来指出哪个对象不能被修改。那么只要客户端在运行,我们就能够在本地接收、拷贝或操作数据。

PersistenceManageFactory



PersistenceManageFactory



JDO中一个很重要的类除了JDOHelper助手类之外就是这个PersistenceManagerFactory了,它是进行JDO开发初始化配置和获得PersistenceManager的工厂类,写这篇文章的目的是为了详细介绍这个类的内部方法和它们的用途以便大家在使用JDO是作为参考。


PersistenceManagerFactory


 


上图显示了PersistenceManagerFactory的主要属性和方法。PersistenceManagerFactory负责为应用程序创建PersistenceManager对象实例,它可以设置数据库连接的相关参数并可以设置创建的PersistenceManager对象的缺省设置。你也可以使用它来规划JDO厂商的实现,根据不同厂商提供的附加支持实现应用的优化。


下面我将对PersistenceManagerFactory进行详细的介绍:


1、  如何获得PersistenceManagerFactory


多数JDO厂商都提供构造PersistenceManagerFactory的方法,    每个厂商之间也许存在着不同,但一般情况下还是推荐使用标准的JDOHelpergetPersistenceManagerFactory方法,这个方法的Properties参数可以设置这个工厂对象的属性,一旦获得这个工厂对象,那么他的所有属性会被“冻结”,也就是说你不能再修改这个对象的属性,如果你试图这样做的话,会抛出一个JDOUserException异常,这是因为这个工厂对象可能是来自一个对象池或着它还要被共享供其他的应用使用。


JDO要求具体的PersistenceManagerFactory类必须实现Serializable接口这样就可以把它放到一个文件或是JNDI树中以便随时使用。


 


2、 PersistenceManagerFactory的属性


PersistenceManagerFactory中大部分方法是标准的JavaBean风格的gettersetter方法他们对应相应的属性字段如上图所示)。这些属性可以分为两个范畴Connection的配置以及PersistenceManagerTransaction的缺省选项


1)      Connection配置


所谓Connection就是数据库连接,我通过一段例子代码来讲述它的设置过程。


下面这段代码告诉JDOHelper如何连接到数据库和数据库的登录用户名


public String getConnectionUserName ();


public void setConnectionUserName (String user);


props.setProperty (“javax.jdo.option.ConnectionUserName”, user);


设置连接数据库的密码


 


public String getConnectionPassword ();


public void setConnectionPassword (String pass);


props.setProperty (“javax.jdo.option.ConnectionPassword”, pass);


设置数据库连接的信息


public String getConnectionURL ();


public void setConnectionURL (String url);


props.setProperty (“javax.jdo.option.ConnectionURL”, url);


设置数据库的驱动


public String getConnectionFactoryName ();


public void setConnectionFactoryName (String name);


props.setProperty (“javax.jdo.option.ConnectionFactoryName”, name);


通过JNDI查找连接工厂,你可以使用连接池通过DataSource获得这个连接工厂


public Object getConnectionFactory ();


public void setConnectionFactory (Object factory);


//用于获得本地连接的方法


public Object getConnectionFactory2 ();


public void setConnectionFactory2 (Object factory);


2)      PersistenceManagerTransaction的缺省选项


下面的方法将设置由PersistenceManagerFactory创建的PersistenceMangere和相关的Transaction对象的缺省属性。有些JDO实现厂商也许没有实现全部这些属性,当你设置一个没有被支持的属性的时候会抛出一个JDOUnsupportedOptionException异常。


public boolean getMultithreaded ();


public void setMultithreaded (boolean multithreaded);


props.setProperty (“javax.jdo.option.Multithreaded”, multithreaded);


上面的值如果被设置为true,代表PersistenceManger对象和由这个对象控制的持久化对象可以同时被多个线程访问,如果被设置为false,一些JDO实现可以通过避免同步来优化执行。


public boolean getOptimistic ();


public void setOptimistic (boolean optimistic);


props.setProperty (“javax.jdo.option.Optimistic”, optimistic);


上面的方法缺省为true,代表使用优化处理,有关优化处理将在TransactionType一节中讨论。


public boolean getRetainValues ();


public void setRetainValues (boolean retain);


props.setProperty (“javax.jdo.option.RetainValues”, retain);


如果上面的值设置为true,代表当数据被提交到数据库之后,持久化对象的字段值仍然保留,否则就清除字段的值。


public boolean getRestoreValues ();


public void setRestoreValues (boolean restore);


props.setProperty (“javax.jdo.option.RestoreValues”, restore);


上面的方法用于控制持久化对象的行为和字段的回滚。


public boolean getNontransactionalRead ();


public void setNontransactionalRead (boolean read);


props.setProperty (“javax.jdo.option.NontransactionalRead”, read);


这些方法定义了你是否能够通过Transaction读取持久对象的状态。如果被设置为false,那么你通过外部的Transaction来对持久化对象执行查询就会抛出一个JDOUserException异常。


public boolean getNontransactionalWrite ();


public void setNontransactionalWrite (boolean write);


props.setProperty (“javax.jdo.option.NontransactionalWrite”, write);


这些方法定义了你是否可以通过外部的Transaction对持久化对象进行写的操作,如果是false,那么你通过外部的Transaction来对持久化对象执行更新操作时就会抛出一个JDOUserException异常。


 


public boolean getIgnoreCache ();


public void setIgnoreCache (boolean ignore);


props.setProperty (“javax.jdo.option.IgnoreCache”, ignore);


上面的方法设置当在一个持久化对象上执行测试查询时,是否在当前的处理过程中更改持久化对象的状态,true标识忽略更改。如果设置为false那么在执行查询的时候会立即将结果更新的到数据库。


3、 如何获得PersistenceManager


获得PersistenceManager可以调用下面的方法


public PersistenceManager getPersistenceManager ();


public PersistenceManager getPersistenceManager (String user, String pass);


两个方法都可以获得PersistenceManage,第一个方法通过PersistenceManagerFactorysetConnectionUserNamesetConnectionPassword方法来设置数据库的连接信息,第二个通过传入的参数来设置数据库连接的用户名和密码。


 


4、 属性和支持的选项设置


 


public Properties getProperties ();


public Collection supportedOptions ();


getProperties方法可以获得一个Properites对象,他包含的特定的JDO厂商信息和版本信息,其中包含两个键:


VendorNameJDO厂商的名称


VersionNumberJDO实现的版本号


getProperties方法可以获得一组包含字符串(String)的集合,它包含JDO实现实现的选项:


l         javax.jdo.option.TransientTransactional


l         javax.jdo.option.NontransactionalRead


l         javax.jdo.option.NontransactionalWrite


l         javax.jdo.option.RetainValues


l         javax.jdo.option.Optimistic


l         javax.jdo.option.ApplicationIdentity


l         javax.jdo.option.DatastoreIdentity


l         javax.jdo.option.NonDurableIdentity


l         javax.jdo.option.ArrayList


l         javax.jdo.option.HashMap


l         javax.jdo.option.Hashtable


l         javax.jdo.option.LinkedList


l         javax.jdo.option.TreeMap


l         javax.jdo.option.TreeSet


l         javax.jdo.option.Vector


l         javax.jdo.option.Map


l         javax.jdo.option.List


l         javax.jdo.option.Array


l         javax.jdo.option.NullCollection


l         javax.jdo.option.ChangeApplicationIdentity


l         javax.jdo.query.JDOQL

JDOHelper


 


这一节我们主要讲述JDOHelper这个助手类中的方法,下面的表格列出了JDOHelper中几个常用的方法,如果你想详细的了解JDOHelper所有的方法可以参考JavaDoc文档中的定义。
























JDOHelper


static void makeDirty(Object pc ,String field)


static Object getObjectId(Object pc)


static PresistenceManager getPersistenceManager(Object pc)


static boolean isDirty(Object pc)


static boolean isTransactional(Object pc)


static boolean isPersistent(Object pc)


static boolean isNew(Object pc)


static boolean isDelete(Object pc)


static PersistenceManagerFactory getPersistenceManagerFactory(Properties props)


 


JDOHelper主要进行3个类型的操作:


1、  对象的持久化操作


2、  对象生命周期的操作


3、  创建PersistenceManagerFactory对象。


下面我将一一讲述这3个操作的过程:


1、 对象持久化操作


对象持久化操作要使用到3个方法:makeDirty,getObjectIdgetPersistenceManager。其中makedirty(Object pc,String field)方法是在你操作一个持久化对象的数组字段时使用的,你只要调用这个方法传入要操作的持久化对象,和已经改变了值的字段就会通知JDO实现,你已经修改了字段的值,可以把新的值保存到数据库中了。getObjectId用户比较持久化对象的一致性,如果传入的参数对象与当前比较的持久化对象不是同一个对象这个方法将返回null值。最后一个方法会得到一个PersistenceManager对象。


 


2、 对象生命周期操作


public static boolean isDirty (Object pc);


public static boolean isTransactional (Object pc);


public static boolean isPersistent (Object pc);


public static boolean isNew (Object pc);


public static boolean isDeleted (Object pc);


上面列出的几个方法是对一个持久化对象状态的判断,这些状态的判断是JDO实现进行的,你根本不用操心。


3、 创建PersistenceManagerFactory对象


public static PersistenceManagerFactory getPersistenceManagerFactory (Properties props)


 


你可以通过JDOHelpergetPersistenceManagerFactory方法来获得PersistenceManagerFactory对象,这个方法只有一个参数,是一个java.util.Properties对象。在使用之前你要先设置这个Properties对象的实例。通过调用getPersistenceManagerFactory方法JDO实现会创建一个PersistenceManagerFactory对象或者是一个池化的包含很多PersistenceManagerFactory的实例池。下面我给出一个获得PersistenceManagerFactory的例子:






 


//  properties 通常来自于一个文件


Properties props = new Properties ();


 


// 这个property参数告诉JDOHelper将产生什么样的PMFactory


props.setProperty (“javax.jdo.PersistenceManagerFactoryClass”,


    “kodo.jdbc.runtime.JDBCPersistenceManagerFactory”);


 


// 项目的方法定义了 persistence managers 的缺省设置


props.setProperty (“javax.jdo.option.Optimistic”, “true”);


props.setProperty (“javax.jdo.option.RetainValues”, “true”);


//下面是设置有关数据库信息的,包括用户名、密码、驱动、连接


props.setProperty (“javax.jdo.option.ConnectionUserName”, “solarmetric”);//


props.setProperty (“javax.jdo.option.ConnectionPassword”, “kodo”);//


props.setProperty (“javax.jdo.option.ConnectionURL”, “jdbc:hsql:database”);//


props.setProperty (“javax.jdo.option.ConnectionDriverName”, //


    “org.hsqldb.jdbcDriver”);


//获得一个pmfactory对象。


PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory (props);