BlogJava

研究、学习Java的地方

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  161篇文章 :: 3篇收藏:: 87篇评论:: 0个Trackbacks

公告



Locations of visitors to this page

文章

收藏

相册

Java

软件工程

数据库

新闻

存档


正在读取评论……


JDO 专栏


    摘要:新本版有了很大的改变,其中BTree索引已经非常成熟,增加了新的查询模式“懒加载查询评估模式”(lazy query evaluation  mode),相比5.x版本在性能上有了很大的改进,相同时间内的内存开销下降了90%。Db4O的开发人还按照Poleposition数据库标准编写了相应的手册
由于并没有把查询的执行完全绑定到特定的客户端应用中,所以新的懒加载查询模式同样提高了C/S应用的性能。DB4O同时还发布了“ObjectManager”一个数据库查询客户端软件的6.0版本。
DB4O的官方网址:http://www.db4o.com/    (全文共458字)——点击此处阅读全文


    摘要:一个KodoJdo配置文件的例子    (全文共988字)——点击此处阅读全文


    摘要:关于“KodoJDO进行数据库操作的步骤”一文的更正    (全文共300字)——点击此处阅读全文


    摘要:KodoJDO进行数据库操作的步骤    (全文共19091字)——点击此处阅读全文

 PersistenceManagerFactory pmf =
        JDOHelper.getPersistenceManagerFactory(props);

在我试验着写一个JDO程序的时候出现了下面两个问题,在我使用JDOHelper.getPersistenceManagerFactory时报错了。开始以为是什么很大的问题后来发现是所需的类没有加载:

classnotfound:javax.transaction.synonym

这个类是JDO1.1中运行时必须的一个事务类,它在jta-spec1_0_1.jar中,可以看出Kodo JDO使用了jta101规范来实现事务部分。

classnotfound:javax.resource.cci.Connection

这个类应该属于J2EE表准的一部分,我看了一下Kodo的类集成关系的文档,其中有这样一段

interface com.solarmetric.util.Closeable

这里可以看出KodoPersistenceManager集成了这个类,所有这个类也是必须的。


为什么要用JDO
Java开发人员已经有好几种存取数据库的方法:序列化,JDBC,面向对象映射工具,对象数据库,以及实体EJB。那为什么还要介绍其他的存储架构呢?答案是,上面每一种实现存储的方案都存在一定的限制。JDO正在尝试解决这些限制。

序列化 是Java建立的一种传输机制,它能够把对象的信息转换成一系列的字节码,这些字节码可以可以被传输到网络或者存储到一个文件中。序列化的使用非常简单,但他还是有限制的。它必须立即存取对象的特征,而且它不适合存取大批量的数据。在它更改一个对象的属性时如果有错误发生他不能回滚错误的修改,因此不适于应用程序的数据完整性要求。多个线程或程序不能同时互不干扰的读写序列化数据。所有这些不足是的序列化无法满足大多数数据存储要求。

许多程序员使用 JDBC   API来操作关系数据库。JDBC克服了许多序列化中存在的缺点:它可以操作大批量的数据,有确保数据一致性的机制,支持信息的并发存取,可以使用已经非常成熟的SQL语言。不幸的是,JDBC使用起来并不像序列化那么简单。JDBC使用的关系范例不是被设计用于存储对象的,因此,它迫使你放弃代码中使用面向对象程序存储数据的可能。

由软件厂商创建的架构可以为你实现对象和关系数据库之间的映射。 这种对象-关系映射支持可以是的你专注于对象模型的设计而不必关心面向对象和关系数据库之间的匹配。不幸的是每一种对象-关系映射支持都有一套他自己厂商实现的API。你不得不使自己的代码迁就某一个单独厂商的实现。假如这个厂商提高价格或者停止对bug更改的支持,如果你想用其他的厂商实现架构时,你就不得不重写你的代码。

比对象关系数据库映射更好的是,一些软件厂商开发了一种新的存储对象到数据库的方法。这种对象数据库使用起来常常比对象关系映射软件简单。ODMG组织成立的目的就是创建一个访问对象数据库的标准API。一些厂商也尊崇ODMG组织的要求,因此由于厂商实现不同带来的麻烦也解决了。一些公司对于从关系数据库转向对象数据库显得犹豫不决,因为有大量的数据存储在传统的关系数据库中。个别数据库分析工具可以用于对象数据库,然而大量的数据存储使用的仍然是关系数据库。由于这些原因,对象数据库被很好的利用。

Java平台的企业级应用中引入了实体EJB。 实体EJB是一个组件,他描述了数据库中的持久性数据信息。EJB使用类似于对象-关系映射的办法,它提供了一个持久性数据的面向对象的表示。不同于对象关系软件,EJB对于关系数据库没有限制;它描述的持久性信息可以来自一个企业信息系统(EIS)或者其他的存储设备。而且,EJB使用了一个严格标准,实现它的厂商必须遵循这个标准。不幸的是,EJB标准在面向对象方面稍微有些欠缺,比如一些高级的特性:继承、多态和复合关系等。另外,EJB的代码编写很复杂,而且它是一个重量级组建需要消耗应用服务器很很多的资源来运行。但是,EJB中的会话Bean和消息驱动Bean有很多优势,所以JDO规范详细定义了JDO如何与他们进行集成。

JDO集成了很多上述持久性机制的特性,这使得在JDO中创建一个持久类就像创建一个序列化类一样简单。JDO支持批量数据的存储,数据一致性,并发处理和JDBC的查询功能。就像对象-关系映射软件和对象数据库一样,它允许使用面向对象的高级概念比如“继承”。它避免了像EJB中实体Bean一样必须依赖于来自厂商定义的严格规范。同样,像EJB一样,JDO不规定任何特定的后端数据库。


 但是,这里还是要说一下,世界上从来就没有“万灵丹”。所以,JDO并不是对于每一个应用程序都是有好处的。很多应用程序完全可以使用其他更理想的存储机制。


可持久化对象(PersistenceCapable

JDO中,所有用户定义的可持久化类都要扩展javax.jdo.spi.PersistenceCapable接口。这个接口包含很多复杂的接口,它可以使具体的JDO实现能够控制这个类对象的持久字段。幸运的是,你不用自己写代码实现这个接口。实际上你在写JDO持久化类的时候和写其他的类没什么区别,不必继承父类,字段或方法。

下面是一个具体的例子,你可以看到它和我们平常写的类没什么区别:

package org.lgdtest;

public class User

{

    private String    name;

    private String    age;

    private User ()

    {

    }

    public User (String name, String name)

    {

        this.name = name;

        this.name = name;

    }

         public String getName(){

                      return name;

         }

         public String getAge(){

             return age;

      

}

JDO增强工具(Enhancer

为了让开发人员不必直接实现复杂PersistenceCapable接口,很多JDO厂商都提供类增强工具(Enhancer)。这个工具可以在你的类中自动加上实现PersistenceCapable接口的方法。有的厂商提供的工具是在源代码中加入PersistenceCapable接口的实现,有的是在.class文件中加入PersistenceCapable的实现。使用直接在.calss文件中增强的方法好处是在你调试程序是如果有错误出现显示的出错位置(行数)与你实际的源代码中的位置是一样的,这样增强的过程就不影响你的调试了。下图显示的是类增强的过程:

所有的类增强过程最终都会生成标准的二进制类文件,也就是说类被“增强”之后并不影响它在其他地方(非JDO环境)使用。类增强的兼容机制确保了你的类增强之后可以直接打包给其他的开发人员使用而不必担心另一个使用者是否和你使用的是同一个JDO厂商实现。也就是说增强后的类可以在任何JDO厂商实现中使用。

持久化类和持久化调用类(Persistence-Capable Persistence-Aware )

概念:

如果一个类通过“增强”实现了PersistenceCapable接口,那么这个类被称为持久化类(Persistence-Capable)。而一个能够直接访问持久化类(public protected)的类称为持久化调用类(Persistence-Aware)

持久化调用类直接访问持久化类的字段和属性,它也必须被“增强”,每次在它访问持久化对象的时候“增强器”都会向JDO实现(这里指的是厂商的JDO应用实现)通过被访问的字段将要被修改或读取。这样JDO实现就可以同步持久对象的字段值与实际映射的数据库字段的值。除非JDO持久化调用类同时也是一个持久化类否则“增强器”是不会为它实现PersistentCapable接口的。

一般情况下,最好把持久化类的字段设置为private,如果字段还要被它的子类访问就把它设置为pretected。除此之外,这也有利于状态的封装       某些情况非持久化类要访问持久类的publicprotected字段,我们前面讲过,如果被访问的字段属于持久字段那么这个访问他的类必须是被增强了的Psersistence-Aware的类,但我们现在的类实际上不是,这就出现了冲突,而如果把字段设置为private就避免了这一问题的出现。

持久化类的限制

讲了那么多JDO的好处,现在要说说JDO有那些限制。其实这些限制很少,它对我们实际的使用影响很少。

无参数构造函数

JDO规范要求所有的持久化类必须有一个无参数的构造类型。如果需要,这个无参数构造函数可以是private的。

继承

JDO完全支持持久化类的继承。它允许一个持久化类继承自一个非持久化类,或者继承自另一个持久化类,也允许非持久化类继承持久化类。但这里有一些限制:

l         持久化类不能继承某些本地的系统类,比如:java.net.Socketjava.lang.Thread

l         如果一个持久化类是继承自一个非持久化类的话,那么这个非持久化类的超类不能被持久化。

持久字段

JDO控制所有持久化字段的状态。在你访问一个字段的时候,JDO要确保这个字段已经从数据库中导出。当你设置一个字段的时候,JDO会记录下它的状态这样新的值就会被保存到数据库中。

JDO包含对很多内置字段类型的支持。这些类型可以大致分为3类:常量、变量和关系类型。

常量类型:常量一旦定义就不能改变。唯一改变常量型持久字段的方法是给字段分配一个新的值。JDO支持以下持久字段的常量类型。

l         所有的原始类型(int,float等等);

l         原始类型的封装类型(java.lang.Integer,java.lang.Float);

l         java.lang.String;

l         java.math.BigInteger;

l         java.math.BigDecimal;

l         java.lang.Number;

l         java.util.Locale;

变量类型:这种类型的字段不必分配新的值就可以修改。这种类型的字段可以使用它们的内部方法直接被修改。JDO规范要求JDO实现厂商支持以下几种字段的变量类型:

l         java.util.Date;

l         java.util.HashSet;

注意,大多数JDO厂商不仅仅支持上面2中类型,还提供了对更多变量类型的支持,比如:

l         java.util.Vector;

l         java.uitl.ArrayList;

l         java.util.Hashtable;

l         java.util.TreeMap;

如果你的持久化类声明了子类字段,那么JDO实现允许你通过子类直接修改这个字段的值。比如你的持久化对象有一个为java.util.Date的字段,那么JDO厂商会提供一个叫做JDODate的子类来设置这个字段的值。这个子类的任务就是跟踪这个字段的状态看他是否被修改了。JDODate重载类所有java.util.Date的方法,它会在字段的值发生变化时通知JDO实现,这样JDO实现就在把修改后的字段的新的值保存到数据库中。

当然,当你开发并使用一个持久化类时,有一件事是非常重要的,你要知道这些变量字段是如何实现的,比方说数组字段的实现是非常麻烦的。JDO允许你使用持久化数组字段,并且能够知道什么时候这个数组被分配了新的值或者它被设置为空(null)。因为数组不能被子类化,所以JDO无法确定什么时候数组的值发生了变化,因此,你在设置数组字段的值的时候要明确的告诉JDO实现你已经改变了字段的值。这种情况下字段称为“脏”字段,这一过程通过JDOHelpermakeDirty方法实现。

下面显示的是一个访问变量持久化字段的例子:

/**

 * .

 * 假定Person是一个持久化类.

 */

public void addChild (Person parent, Person child)

{

       //大多数变量字段可以直接修改;JDO会跟踪字段的状态

    Date lastUp = parent.getLastUpdated ();

    lastUp.setTime (System.currentTimeMillis ());

    Collection children = parent.getChildren ();

    children.add (child);

    child.setParent (parent);

 

       //数组字段需要显示的“变脏”操作来实现字段值的修改

    parent.setObjectArray (new Object[0]);

    child.getObjectArray ()[0] = parent;

    JDOHelper.makeDirty (child, "objectArray");

    // 也可以使用 child.setObjectArray (child.getObjectArray ());

}

就像上面的拥有父子关心的持久对象一样,JDO支持除了标准的Java类型外还支持持久化对象之间的关系。所有的JDO实现都允许用户定义的持久化对象或持久化对象集合作为持久化字段的类型。

总结:

这次我们讲了,JDO的持久化对象,类的增强以及持久化对象的一些限制。但是,在今后的开发中你会发现,这些限制对你开发影响并不是很大,甚至你根本不会碰到这些有关的限制。



JDO架构简介

下图显示了JDO架构主要的几部分内容:

JDOARC

JDOHelper :javax.jdo.JDOHelper类拥有一个静态的助手(helper)方法。这个方法可以获得一个持久对象的生命周期还可以用来创建一个与具体实现厂商无关的PersistenceManagerFactory的实例。

PersistenceManagerFactory:javax.jdo.PersistenceManagerFactory类可以通过JDOHelper类的助手方法获得,这是一个标准的工厂类,他可以创建PersistenceManager类。

PersistenceManagerjavax.jdo.PersistenceManager接口是应用程序经常要使用的一个主要的JDO接口。每一个PersistenceManager负责控制一组持久化对象而且他还可以创建新的持久化对象或删除现有的持久化对象。TransactionPersistenceManager之间存在这一对一的关系,同时PersistenceManager又是ExtentQuery的工厂类,也就是说这两个对象可以通过PersistenceManager创建。

PersistenceCapable:用户定义的持久化类都必须扩展实现PersistenceCapable接口。大多数JDO实现的供应商都提供一种“增强器”(enhancer)的功能,它可以向你要实现的持久化类中增加PersistenceCapable接口的实现。也就是说,其实你根本不会自己去实现这个接口。

Transaction:每一个PersistemceManagerjavax.jdo.Transaction都是一一对应的。Transactions用来处理事务,它使得持久化数据可以成批的一次性添加到数据表中,如果出现异常就将数据回滚。

Extent:java.jdo.Extent是映射数据库中具体表的类的一个逻辑视图。Extent可以拥有自己的子类,它通过PersistenceManager获得。

Query:java.jdo.Query接口用具体的厂商JDO来实现,它负责处理JDO查询语言(JDOQL),这些JDOQL最终被解释为实际的数据库SQL语言。同样这个接口也是通过PersistenceManager获得的。

下面的例子显示的JDO接口如何操作并执行一个查询并更新持久化对象。

 

例子:JDO接口的交互

//通过助手类获得PersistenceManagerFactory

    PersistenceManagerFactory factory=

        JDOHelper.getPersistenceManagerFactory(System.getProperties());

    //通过PersistenceManagerFactory获得PersistenceManager对象

    PersistenceManager pm=factory.getPersistenceManager();

    //创建并开始一个事务

    Transaction tx=pm.currentTransaction();

    tx.begin();

    //查询employee表中每周工作时间大于40小时的研究人员

    Extent ex=pm.getExtent(Employee.class,false);

    //获得一个Query

    Query query=pm.newQuery();

    //设置这个query作用的范围,即查询的是那个表或记录集

    query.setCandidates(ex);

    query.setFilter("division.name == \"Research\" "+ "&& avgHours > 40");

    Collection result=(Collection)query.execute();

    Employee emp;

    for(Iterator itr=result.iterator();itr.hasNext();){

      emp=(Employee)itr.next();

      emp.setSalary(emp.getSalary()*2);

    }

    //提交记录释放资源

    tx.commit();

    pm.close();

factory.close();

 

上面的代码片断包括了JDO几个主要的接口,在此你可以对JDO各个接口的使用方法有一个粗略的印象,以后实际的应用中JDO接口也都是这样使用的。

 

JDO的异常

JDO不会抛出通常的运行时异常,比如NullPointerExceptions IllegalArgumentException它只抛出JDOException异常。JDOExcetion的结构如下图所示,这是一个继承的层次结构,从他们的字面含义就可以看出它们的用途,在这里就不详细说了,要想了解JDO异常的结构可以参考它们的JavaDoc
JDOException


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文件中被访问。

实例回调

你的持久化类可以通过继承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 ()

    {

    }

}


第1页,共2页