CSDN博客

img infocc

深入浅出Persistence Layer

发表于2004/7/9 16:53:00  473人阅读

深入浅出Persistence Layer

深入浅出Persistence Layer(1)
from Martin的blog: http://www.matrix.org.cn/blog/martin/

Scott W. Ambler早在1998年就写出了关于ORM Persistence Layer的详细设计论文。 根据这个设计思路,Artem Rudoy 开发了一个开源的ORM实现 -- PL(Persistence Layer)开源项目

不知道Ambler是否ORM的先驱, 但是可以肯定的是,Ambler的Object思想极大影响了ORM工具的发展。Apache ObJectRelationalBridge (OJB)的PersistenceBroker设计就和Ambler的论文同出一澈。

今天,ORM(Object Relation Mapping)的产品可谓百花齐放, 其中著名的有:Hibernate,OJB,Torque,TopLink,Castor ,还有不计其数的轻量级ORM产品。虽然ORM如此让人眼花缭乱,但是究其低层原理、设计思想,都如出自同一门派般的类似。

Hibernate中文网站长robbin如是说:"不管JDO也好,Hibernate也好,TopLink也好,CocoBase也好,还是 Castor,还是什么Torque,OJB,软件的使用和配置方法可以各异,但本质上都是ORM,都是对JDBC的对象持久层封装,所以万变不离其宗"。

让我们一起来看看开源项目PL的实现,一起来领会大师Ambler的设计,相信这一定是一段美妙的旅程。

一.概观
二.深入
三.浅出

一.概观
下图展示了Ambler 的持久层设计概要(Ambler, 1998b)。

这个设计吸引人的地方是应用程序开发者只需要知道下面几个类就可以将他们的对象持久化:PersistentObject、PersistentCriteria 类及其子类、PersistentTransaction和Cursor。其它的类并不会由应用程序代码直接访问,但是需要开发和维护它们以支持这些“public”的类。

类描述:
ClassMap 映射类,封装了将类映射到关系数据库的行为,包括类-数据表,类属性-表字段的关系映射。

Cursor 这个类封装了数据库中游标的概念。

PersistenceBroker 维护到诸如数据库或者文本文件等持久机制的连接,并且处理对象应用程序与持久机制之间的通信。

PersistentCriteria 这个类层次封装了根据指定条件进行获取、更新、删除等所需的行为。

PersistenceMechanism 一个封装了对文本文件、关系数据库、对象数据库等的访问方法的类层次。对关系数据库,这个树封装了复杂的类库,例如微软的ODBC 或者Java 的JDBC,这样可以保护你的组织不受这些类库改变的困扰。

PersistentObject 这个类封装了使单个实例持久化的行为,所有需要持久化的业务对象都从这里派生出来。

PersistentTransaction 这个类封装了支持持久机制的简单以及嵌套事务所需的行为。

SqlStatement 这个类层次知道如何根据ClassMap 对象构造insert、update、delete和select 语句。

以上的类代表了一种内聚的概念,换句话说,每个类只做一件事,并且做得很好。这是一个好的设计的基础。PersistentObject封装了使单个对象持久化的行为,而PersistentCriteria类层次封装了需要与一组持久对象协同工作的行为。

(con...)

更多资源:
参与论坛讨论:http://www.matrix.org.cn/forum.asp
更多技术文章:http://www.matrix.org.cn/article.asp
Matrix java门户:http://www.matrix.org.cn
原文地址:http://www.matrix.org.cn/article/851.html
任何获得许可转载此文章,须在显著位置标明Matrix的原文地址,并做链接至原文页面,查看详细的版权说明

深入浅出Persistence Layer(2)
from Martin的blog: http://www.matrix.org.cn/blog/martin/

主流的Persistence Layer都有自己的配置文件,用于存储对象类与数据库的映射信息。执行save,delete,update,retrieve等操作时,Persistence Layer会自动生成所需要的sql语句并执行。这一核心过程是Persistence Layer的灵魂,ORM、JDO、EJB均无能例外。深入篇讲述PL的 映射->装载->生成SQL 全过程。

一.准备
二.映射
三.装载
四.生成

一.准备
1.下载PL项目
下载Artem Rudoy的开源项目PL,这是根据Scott W. Ambler的论文实现的ORM。解压缩到某个路径。

2.安装数据库
安装MySQL数据库,建议使用V3.23以上的版本。同时下载MySQL JDBC Driver。将它加到CLASSPATH中。

3.创建测试数据库
在MySQL数据库中创建pltest数据库,执行解压缩路径下的test/mySqlTest.sql,创建测试的数据表。

4.修改数据库连接属性
打开解压缩路径下的test/mySqlTest.xml,修改user和password,其他不变。


二.映射
解压缩路径下的test/schema.xml,是PL的映射文件。
以下是映射文件的一个片段:

映射文件

顶层是map节点,map节点包含class和association子节点。
class节点代表一个类,class节点包含class-name,table-name,database-name和attribute子节点。
association节点代表一个关联关系。(关联信息的处理在此不作深入,将另文叙述)

三.装载

pl.test.Test 是PL提供的测试类中的其中一个, 这个测试类很小但是测试内容却不少,包含:
1)单一对象和事务的测试
2)单一继承对象的测试
3)关联支持的测试
4)空条件、简单条件、复杂条件的测试
5)乐观锁定测试
6)代理对象、代理对象条件测试
...

PL虽小,却是五脏俱全!


1.读映射文件

PL装载映射信息是通过PersistenceBroker.loadConfig()方法完成的。
如下是pl.test.Test类的代码

public void performTest()
{
try
{
PersistenceBroker.getInstance().setDebug(true);
PersistenceBroker.getInstance().init();

String dir = "D://workspace//PersisterLayer//test//";
// 装载数据库信息
PersistenceBroker.getInstance().loadConfig(new XMLConfigLoader(dir + "mySqlTest.xml"));
// 装载类-数据表映射信息
PersistenceBroker.getInstance().loadConfig(new XMLConfigLoader(dir + "schema.xml"));

跟踪PersistenceBroker.loadConfig()方法,发现其调用了ConfigLoader.loadConfig()方法,而ConfigLoader是一个接口,ConXMLConfigLoader才是实现。
因此查看 XMLConfigLoader.loadConfig方法:

public void loadConfig(PersistenceBroker broker) throws PlException
{
this.broker = broker;
try
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(in);

Element root = getFirstNamedChildElement(document, "map");
if(root == null)
throw new PlException("Invalid XML document. No map definition found");

Element child = null;

// 获取数据库连接信息
child = getFirstNamedChildElement(root, "database");
while(child != null)
{
pl.sql.RelationalDatabase pm = getRelationalDatabase(child);
broker.addRelationalDatabase(pm);

child = getNextNamedSiblingElement(child, "database");
}

// 获取映射类信息
child = getFirstNamedChildElement(root, "class");
while(child != null)
{
ClassMap cm = getClassMap(child);
broker.addClassMap(cm);

child = getNextNamedSiblingElement(child, "class");
}


可以看到, 这个方法通过getRelationalDatabase(child)方法创建一个pl.sql.RelationalDatabase对象(存储数据库信息),然后通过
broker.addRelationalDatabase(pm) 将 RelationalDatabase对象保存在 PersistenceBroker类中。
类的映射信息也类似,先通过 getClassMap(child) 构造 ClassMap 对象,然后 调用broker.addClassMap(cm) 将ClassMap对象保存在
PersistenceBroker类中。

2. 内存中的映射信息

查看XMLConfigLoader的getClassMap方法,可以看出PL是怎样在内存中构造映射类的.
下图是映射类的关系图:


1)为每一个database创建一个DatabaseMap;
2)为每一个table创建一个TableMap;TableMap中存储了对DatabaseMap的关联;
3)为每一个表字段(column)创建一个ColumnMap;ColumnMap中存储了对TableMap的关联
4)为每一个class创建一个ClassMap; ClassMap中用ArrayList attributeMaps 来存放类的属性列表
5)为每一个类的属性(attribute)创建一个AttributeMap;AttributeMap中存储了对ColumnMap的关联。


四.生成

PL生成SQL语句是分别在两个阶段完成。第一个阶段是在初始化阶段,从映射文件schema.xml读取映射信息,这个阶段生成的是基本的SQL语句。 第二阶段是运行阶段,从程序调用中取得实际值(如查询的条件,更新的字段值等),从而生成真实运行的SQL语句。

1.初始化阶段

查看PersistenceBroker.addClassMap方法, 如下,
public void addClassMap(ClassMap classMap) throws PlException
{
classMap.init();
classMaps.put(classMap.getName(), classMap);
}
它先执行classMap.init()方法,然后将classMap存放到PersistenceBroker的私有对象classMaps中,classMaps是一个TreeMap。

classMap.init是一个非常重要的方法,一个classMap类只执行一次。这个方法初始化了对这个classMap所有操作的SQL语句。
(读入配置文件时就可以构造所有要操作的SQL语句?简直象玩魔术啊!可行吗?请思考!)


我们知道对一个数据表的操作有select,insert,delete,update四种操作,每一种操作在PL中使用SqlStatement类保存其sql语句 。
ClassMap类中则为每一个操作定义了一个私有的SqlStatement对象。如下是ClassMap类的定义:

public class ClassMap
{
private String name = null;
private SqlStatement selectStatement = null;
private SqlStatement selectProxyStatement = null;
private SqlStatement selectTimestampStatement = null;
private SqlStatement insertStatement = null;
private SqlStatement deleteStatement = null;
private SqlStatement updateStatement = null;

ClassMap一共有6个SqlStatement对象, 除了CRUD4个操作外,还有selectProxyStatement(用来实现代理),selectTimestampStatement(用来实现最后修改的记录)。


init方法就是用来初始化这些SqlStatement对象的,初始化了这些SqlStatement对象,也就有了基本的sql语句了。
如下是ClassMap的init方法
public synchronized void init() throws pl.PlException
{
// We don't have to init class map twice
if(isInited)
return;
// Init all statements
//
// Init SELECT statement
//
selectStatement = getSelectSql();
// Add 'FROM' and 'WHERE' clauses to the select statement
selectStatement.addSqlClause(" ");
selectStatement.addSqlStatement(getFromAndWhereSql());

现在我们来看看SqlStatement是怎样获得SQL语句的。
从上面代码可以看出构造selectStatement的SQL语句关键方法有两个:getSelectSql和getFromAndWhereSql。
上面代码执行完将selectStatement打印出来可以看到sql为: SELECT person.id, person.name FROM person WHERE person.id=?

1.取SELECT部分
下面是getSelectSql的代码:
public SqlStatement getSelectSql() throws PlException
{
// Create new statement
SqlStatement statement = new SqlStatement();

// Add 'SELECT' clause to the select statement
statement.addSqlClause(getRelationalDatabase().getClauseStringSelect() + " ");
// Add clauses for all attributes. Do not add ", " before the first attribute
boolean isFirst = true;
ClassMap classMap = this;
do
{
for (int i = 0; i < classMap.getSize(); i++)
{
statement.addSqlClause((isFirst ? "" : ", ") +
classMap.getAttributeMap(i).getColumnMap().getFullyQualifiedName());
isFirst = false;
}
其中的 getRelationalDatabase().getClauseStringSelect() 取到 " SELECT "
而classMap.getAttributeMap(i).getColumnMap().getFullyQualifiedName()) 将分别取到 person.id 和 person.name

2.取WHERE部分
下面是getFromAndWhereSql的代码
public SqlStatement getFromAndWhereSql() throws PlException
{
// Create new statement
SqlStatement statement = new SqlStatement();

// Add 'FROM' clause to the select statement
statement.addSqlClause(" " + getRelationalDatabase().getClauseStringFrom() + " ");
boolean isFirst = true;
ClassMap classMap = this;
do
{
AttributeMap map = classMap.getAttributeMap(0);
if (map != null)
{
statement.addSqlClause((isFirst ? "" : ", ") + map.getColumnMap().getTableMap().getName());
}
classMap = classMap.getSuperClass();
isFirst = false;
}
// Add 'WHERE key=?' to the select statement
if(getKeySize() > 0 || inheritanceAssociations.length() > 0)
{
statement.addSqlClause(" ");

statement.addSqlClause(getRelationalDatabase().getClauseStringWhere() + " ");
for(int i = 0; i < getKeySize(); i++)
{
statement.addSqlClause((i > 0 ? " " + getRelationalDatabase().getClauseStringAnd() + " " : "") +
getKeyAttributeMap(i).getColumnMap().getFullyQualifiedName() + "=?");

}

}

其中的 getRelationalDatabase().getClauseStringFrom() 取到 " FROM ",
map.getColumnMap().getTableMap().getName() 取到 表名"person"
getRelationalDatabase().getClauseStringWhere() 取到 "WHERE"
getKeyAttributeMap(i).getColumnMap().getFullyQualifiedName() 取到 person.id

2.运行阶段
(con...)

更多资源:
参与论坛讨论:http://www.matrix.org.cn/forum.asp
更多技术文章:http://www.matrix.org.cn/article.asp
Matrix java门户:http://www.matrix.org.cn
原文地址:http://www.matrix.org.cn/article/852.html
任何获得许可转载此文章,须在显著位置标明Matrix的原文地址,并做链接至原文页面,查看详细的版权说明

0 0

相关博文

我的热门文章

img
取 消
img