现在,越来越多的企业项目需要一种将 Java 对象绑定到关系数据 ? 并且在众多关系数据库上进行绑定 ? 的可靠方法。遗憾的是,(许多人已体会到其中的艰难)内部解决方案很难构建,而对其进行长期维护和扩展就更难了。在本文中,BrUCe Snyder 向您介绍了使用 Castor JDO 的基础知识,Castor JDO 是一种开放源码数据绑定框架,它恰好基于百分之百纯 Java 技术。
Castor JDO(Java 数据对象 (Java Data Objects))是一种开放源码的、百分之百 Java 数据绑定框架。Castor JDO 最早发布于 1999 年 12 月,它是第一批可用的开放源码数据绑定框架之一。自那时以来,这项技术已获得了长足的发展。现在,往往将 Castor JDO 同许多其它技术(既有开放源码的,也有商业的)结合使用,以将 Java 对象模型绑定到关系数据库、XML 文档以及 LDAP 目录上。
在本文中,您将了解使用 Castor JDO 的基本知识。我们将从关系数据模型和 Java 对象模型开始,然后讨论在二者之间进行映射的基础知识。接下来,我们将讨论 Castor JDO 的一些特性。通过使用一个基于产品的简单示例,您将了解一些基本内容,如(关系的和面向对象的)继续、从属与相关关系、Castor 的对象查询语言(Object Query Language)实现以及 Castor 中短事务和长事务的比较。因为本文只是对 Castor 的简介,所以我们在这里将使用非常简单的示例,而且我们也不对任何一个主题进行深入讨论。读完本文之后,您将对这项技术在整体上有了一个较好的了解,也为将来的探索打下了较好的基础。
请注重,本文将不深入讨论对象-关系映射这一一般性的主题。想了解关于对象-关系映射的更多知识,请参阅参考资料一节。
假如您想了解 Castor JDO 与 Sun JDO 规范之间的差异,那么请参阅“Castor 与 Sun JDO 规范有何差异?”。
开始
有时我喜欢通过对数据建模来开始项目;而有时我喜欢通过对对象建模来开始项目。针对本文的目的,我们将从数据模型开始。Castor 附带了一些 JDO 示例,它们给出了围绕某个产品概念的数据模型和对象模型。我们将使用其中的一个示例,将其贯穿应用于本文的几个不同阶段。图 1 是该示例数据模型的实体-关系(ER)图。为简单起见,该图不含显式的外键。然而,需要注重的是表之间确实存在标识引用。
获取源代码
Castor 项目的源代码是通过一种称为 ExoLab 许可证的 BSD 样式许可证发布到 Java 技术社区的。请参阅参考资料以下载 Castor JDO。
图 1. Castor JDO 示例数据模型
图 2 是该示例对象模型的 UML 类图。JDO 示例提供了表到对象的一对一表示。
图 2. Castor JDO 示例对象模型
Castor 的 Java 对象很象 JavaBeans 组件。因此,(除非将特性映射成直接访问)对象的每个特性通常都有一对取值(Accessor)和赋值(mutator)方法(getter/setter)。更加复杂的关系可以含有用于其它目的的额外的逻辑。对象-关系映射可以迅速变得极其复杂;然而,这些示例实际上却十分简单。
清单 1 显示了 Product 对象的源代码。注:对于 product 表中包括标识在内的每一列,代码都含有对应的特性。对于刚接触对象-关系映射的人来说,对应于标识符的特性可能显得很希奇,但这种构造的确十分常见,而且也在其它对象-关系框架中使用。Castor 在内部使用对象标识来跟踪对象。此外,还有两个 java.util.Vector 用于关联的 ProductDetail 和 Category 对象。还要注重的是,对象包括一个 toString() 方法,它在调试应用程序时可用作日志记录用途。
清单 1. Product.java
package myapp;
import java.util.Vector;
import java.util.Enumeration;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.Persistent;
public class Product implements Persistent
{
private int _id;
private String _name;
private float _price;
private ProductGroup _group;
private Database _db;
private Vector _details = new Vector();
private Vector _categories = new Vector();
public int getId()
{
return _id;
}
public void setId( int id )
{
_id = id;
}
public String getName()
...
public void setName( String name )
...
public float getPrice()
...
public void setPrice( float price )
...
public ProductGroup getGroup()
...
public void setGroup( ProductGroup group )
...
public ProductDetail createDetail()
{
return new ProductDetail();
}
public Vector getDetails()
{
return _details;
}
public void addDetail( ProductDetail detail )
{
_details.add( detail );
detail.setProduct( this );
}
public Vector getCategories()
{
return _categories;
}
public void addCategory( Category category )
{
if ( _categories.contains( category ) ) {
_categories.addElement( category ); category.addProduct( this );
}
}
...
public String toString()
{
return _id + " " + _name;
}
}
在查看图 2 时,请注重 Product 与 ProductDetail 对象和 Category 对象有不同的关系:
一个 Product 可以包含多个 ProductDetail。这是一对多关系。
多个 Product 可以包含多个 Category 对象。这是多对多关系。
addDetail() 和 addCategory() 方法用于将对象添加到每个 java.util.Vector 以治理它们各自的关系。
映射描述符的因果
正如有些人相信生活的要害是要正确理解并接受个人的命运一样,我发现使用 Castor JDO 的要害是要正确理解并实现映射描述符。映射描述符提供关系数据库表和 Java 对象之间的连接(映射)。映射描述符的格式是 XML ? 这是有充分理由的。Castor 项目的另一半是 Castor XML(参阅参考资料)。Castor XML 提供出色的 Java 到 XML 和 XML 到 Java 数据绑定框架。Castor JDO 利用 Castor XML 的能力来将 XML 文档数据分解到 Java 对象模型,以便读取映射描述符。
将对象和特性映射成元素和属性
每个 Java 对象都由一个 <class> 元素表示,而对象中的每个特性都由一个 <field> 元素表示。此外,关系表内的每列都由一个 <sql> 元素表示。清单 2 显示了上面所见到的 Product 对象的映射。先请看该清单,接下来我们将讨论该代码的一些亮点。
清单 2. Product 的映射
<class name="myapp.Product" identity="id">
<description>Product definition</description>
<map-to table="prod" xml="product" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="name" type="string">
<sql name="name" type="char" />
</field>
<field name="price" type="float">
<sql name="price" type="numeric" />
</field>
<!-- Product has reference to ProductGroup,
many products may reference same group -->
<field name="group" type="myapp.ProductGroup">
<sql name="group_id" />
</field>
<!-- Product has reference to ProductDetail
many details per product -->
<field name="details" type="myapp.ProductDetail" required="true"
collection="vector">
<sql many-key="prod_id"/>
</field>
<!-- Product has reference to Category with
many-many relationship -->
<field name="categories" type="myapp.Category" required="true"
collection="vector">
<sql name="category_id"
many-table="category_prod" many-key="prod_id" />
</field>
</class>
<class> 元素支持一些重要的属性和元素。例如,Product 映射使用 identify 属性来指示对象的哪个特性充当对象标识符。<class> 元素还支持 <map-to> 元素,该元素告诉 Castor 每个对象映射到什么关系表上。<field> 元素也支持一些属性。
请注重,所有 <field> 和 <sql> 元素的映射都包含 type 属性。类型属性向 Castor 指示:该在内部使用什么 TypeConvertor 来在对象和关系数据类型之间进行转换。
定义关系
对于每个“对多”关系,都存在类型属性的非凡情形。如同以前所提到的那样,Product 中的两个 java.util.Vector 处理一对多和多对多关系。一对多关系存在于称为 details 的 <field> 元素中。details 元素中的信息告诉 Castor:特性是 java.util.Vector 类型的 Collection;Collection 包含 ProductDetail 类型的对象;并且它是 required(即,不能为空值)。<sql> 元素告诉 Castor:<field> 的对象映射含有一个称为 prod_id 的 SQL 列,该列将被用于标识一对多关系。
多对多关系存在于称为 categories 的 <field> 元素中。categories 元素告诉 Castor:特性是 java.util.Vector 类型的 Collection;该 Collection 含有 Category 类型的对象;并且它是 required(即,它不能为空值)。<sql> 元素告诉 Castor:该关系利用一个称为 category_prod 的额外的表。它还声明该 <field> 的对象映射含有一个称为 prod_id 的 SQL 列,该列应该同 category_id 一起用来标识多对多关系。
ProductGroup 和 Product 之间还存在另一种关系。该关系是一对多关系,通过这一关系,许多 Product 就能够与同一个 ProductGroup 之间存在关系。虽然该关系与 Product 和 ProductDetail 之间的一对多关系相同,但其运行却相反(多对一),因此我们只在映射中看到了关系的“对一”的一方。
Castor 中的继续
Castor 使用两种类型的继续:Java 继续和关系继续。清单 3 含有 Computer 的映射。正如您能从图 1 中回忆起来的一样,Computer 是 Product 的继续。
当 Java 对象只是继续一般基类或实现一个接口时,无须在映射描述符中反映该继续。然而,因为 Product 和 Computer 都是已映射的类,所以必须为 Castor 注明继续以正确地反映它。<class> 元素的 extends 属性用于表示 Computer 和 Product 之间的关系。在图 1 中请注重 comp(计算机)表并不包括来自 prod(产品)表的列。相反,prod 表包含基本信息,comp 表继续了该表。
清单 3. Computer 的映射
<class name="myapp.Computer" extends="myapp.Product" identity="id">
<description>Computer definition, extends generic
product</description>
<map-to table="computer" xml="computer" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="cpu" type="string">
<sql name="cpu" type="char"/>
</field>
</class>
从属关系 vs. 相关关系
Castor 对两个对象之间的关系进行了区分,分为从属或相关,并且对每种类型的关系维护不同的生命周期。至此,我们只讨论了独立(或相关)对象。独立对象是那些未在映射描述符的 <class> 元素中指定 depends 属性的对象。假如您想在独立对象上执行任何 CRUD(创建、读取、更新和删除)操作,那么您可以直接在对象上执行这些操作。
清单 4 包含从属对象 ProductDetail 的映射。请注重,<class> 元素包含 depends 属性。这就告诉 Castor:ProductDetail 的所有操作都从属于 Product。在这里,Product 是主对象而 ProductDetail 是从属对象。假如您想在 ProductDetail 对象上执行任何 CRUD 操作,那么您只能通过其主对象来执行这些操作。也就是,您必须首先获取一个 Product 并使用取值方法导航到 ProductDetail。每个独立对象可能只有一个主对象。
清单 4. ProductDetail 的映射
<class name="myapp.ProductDetail" identity="id" depends="myapp.Product">
<description>Product detail</description>
<map-to table="prod_detail" xml="detail" />
<field name="id" type="integer">
<sql name="id" type="integer"/>
</field>
<field name="product" type="myapp.Product">
<sql name="prod_id" />
</field>
<field name="name" type="string">
<sql name="name" type="char"/>
</field>
</class>
在对象模型上执行查询
Castor 实现了对象查询语言(OQL)的 ODMG 3.0 规范的一个子集。OQL 的语法类似于 SQL 的语法,但它却使您能够查询对象模型,而不是直接查询数据库。在支持多个数据库时,这可能是一项强大的功能。Castor 的 OQL 实现在内部将 OQL 查询转换成用于数据库的适当的 SQL。使用 bind() 方法将参数绑定到查询上。以下是 OQL 查询的一些简单示例。
Castor 的 OQL 实现并不在整个查询中继续使用全限定对象名,相反它支持对象别名的使用。在下面的这些查询中,c 就是这样的一个别名。
假如想要查询以找出所有 Computer,可以执行下列查询:
SELECT c FROM myapp.Computer c
假如想要查询以找出标识等于 1234 的 Computer,可以以:
SELECT c FROM myapp.Computer c WHERE c.id= $1
开始,后跟:
query.bind( 1234 )
要查询名称与非凡字符串相似的 Computer,可以执行下列查询:
SELECT c FROM myapp.Computer c WHERE c.name LIKE $1
后跟:
query.bind( "%abcd%" )
要查询其标识符属于一列标识符之内的 Computer,可以执行下列查询:
SELECT c FROM myapp.Computer c WHERE c.id IN LIST ( $1, $2, $3 )
后跟:
query.bind( 97 )
query.bind( 11 )
query.bind( 7 )
对 Castor OQL 实现的更具体讨论超出了本文的范围。要了解更多信息,请参阅参考资料一节中的参考资料。
Castor 中的事务
Castor 的持久性操作在事务上下文内进行。然而,Castor 不是事务治理器。相反,它更象是高速缓存治理器,因为它利用事务的原子性来将对象持久保存到数据库。这种设置答应应用程序更加轻易地提交或回滚对对象图的任何更改。运行于非受管环境下的应用程序必须显式提交或回滚事务。当应用程序服务器运行于受管环境下时,应用程序服务器可以治理事务性上下文。
短事务 vs. 长事务
Castor 中正常的事务被称为短事务。Castor 还提供了长事务这一概念,长事务由两个短事务组成。我们可以使用一个典型的 Web 应用程序来理解两种类型的事务之间的差异。对需要从数据库读取数据、将数据显示给用户然后把数据提交给数据库的应用程序来说,我们查看有可能冗长的事务时间(这对于 Web 应用程序是很典型的)。但不能在数据库中无限地保持写锁。为了解决这一问题,Castor 利用了两个短事务。例如,第一个短事务使用只读查询来具体化对象,这让您可以在不打开事务就显示对象。假如用户做了更改,则启动第二短个事务,update() 方法将更改过的对象带入事务的上下文。清单 5 是一个长事务的示例。
清单 5. 长事务示例
public LineItem getLineItem()
{
db.begin();
LineItem lineItem = ( LineItem ) db.load( LineItem.class,
lineItemNum, Database.ReadOnly );
db.commit();
return lineItem;
}
public void updateOrder( LineItem li )
{
db.begin();
db.update( li );
db.commit();
}
因为第一个短事务和第二个短事务之间的时间间隔是任意的,所以您将不得不执行一个脏检查(dirty check)来确定是否在数据库中更改了对象。脏检查用于验证对象在长事务期间没有被修改。您可以通过指定映射描述符的 <sql> 元素中的 dirty 属性来启用脏检查。为了使脏检查正确工作,每个对象都必须保持一个时间戳记。假如您实现了 Timestampable 回调接口,Castor 将在第一个短事务中设置时间戳记并在第二个短事务期间检查该时间戳记。
Database 支持
Castor JDO 的主要特性是:它提供了将 Java 对象绑定到关系数据库的 API。Castor JDO 支持下列数据库:
DB2
HypersonicSQL
Informix
InstantDB
Interbase
mysql
Oracle
PostgreSQL
SAP DB
SQL Server
Sybase
Generic(用于一般 JDBC 支持)
添加对其它数据库的支持十分轻易。参阅参考资料以获取进一步的信息。
结束语
在本文中,我们讨论了使用 Castor 将关系数据模型映射到 Java 对象模型的基础知识。本文中所使用的示例与 Castor 源代码中当前所提供的示例非常相近。这些简单的示例肯定不能涵盖 Castor 的全部能力。实际上,Castor 支持许多其它特性,包括键生成、延迟装入、LRU 高速缓存、不同的锁定/访问方式以及更多。假如您想了解关于这些特性以及许多其它特性的更多信息,请参考参考资料一节。
Castor JDO 只不过是当今开放源码世界中许多可用数据绑定解决方案中的一种。如本文所显示的那样,Castor 为内部解决方案提供了完善的替代方案,内部解决方案通常需要您为您支持的每个数据库维护不同的 SQL 和 JDBC 代码。而且,因为 Castor 是开放源码的、基于社区的项目,所以 Castor 得到了不断的改进。我鼓励您研究这一技术,甚至可能成为 Castor 社区的一员。