免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1340 | 回复: 0
打印 上一主题 下一主题

深入浅出Persistence Layer [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2005-03-18 14:13 |只看该作者 |倒序浏览

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

不知道Ambler是否ORM的先驱, 但是可以肯定的是,Ambler的Object思想极大影响了ORM工具的发展。Apache ObJectRelationalBridge (OJB)的
[color="#888888"]PersistenceBroker设计
就和Ambler的论文同出一澈。
今天,ORM(Object Relation Mapping)的产品可谓百花齐放,
其中著名的有:Hibernate,OJB,Torque,TopLink,Castor
,还有不计其数的轻量级ORM产品。虽然ORM如此让人眼花缭乱,但是究其低层原理、设计思想,都如出自同一门派般的类似。
Hibernate中文网站长
[color="#888888"]robbin
如是说:"不管JDO也好,Hibernate也好,TopLink也好,CocoBase也好,还是 Castor,还是什么Torque,OJB,软件的使用和配置方法可以各异,但本质上都是ORM,都是对JDBC的对象持久层封装,所以万变不离其宗"。
Scott W. Ambler早在1998年就写出了关于ORM Persistence Layer的详细
[color="#888888"]设计论文
。 根据这个设计思路,Artem Rudoy 开发了一个开源的ORM实现 -- PL(Persistence Layer)
[color="#888888"]开源项目

不知道Ambler是否ORM的先驱, 但是可以肯定的是,Ambler的Object思想极大影响了ORM工具的发展。Apache ObJectRelationalBridge (OJB)的
[color="#888888"]PersistenceBroker设计
就和Ambler的论文同出一澈。
今天,ORM(Object Relation Mapping)的产品可谓百花齐放,
其中著名的有:Hibernate,OJB,Torque,TopLink,Castor
,还有不计其数的轻量级ORM产品。虽然ORM如此让人眼花缭乱,但是究其低层原理、设计思想,都如出自同一门派般的类似。
Hibernate中文网站长
[color="#888888"]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类层次封装了需要与一组持久对象协同工作的行为。
主流的Persistence
Layer都有自己的配置文件,用于存储对象类与数据库的映射信息。执行save,delete,update,retrieve等操作时,
Persistence Layer会自动生成所需要的sql语句并执行。这一核心过程是Persistence
Layer的灵魂,ORM、JDO、EJB均无能例外。深入篇讲述PL的 映射->装载->生成SQL 全过程。
一.概观
二.深入
三.浅出
Step1.准备
Step2.映射
Step3.装载
Step4.生成
Step1.准备
1.下载PL项目
下载
[color="#888888"]Artem Rudoy的开源项目PL
,这是根据Scott W. Ambler的论文实现的ORM。解压缩到某个路径。
2.安装数据库
安装
[color="#888888"]MySQL数据库
,建议使用V3.23以上的版本。同时下载
[color="#888888"]MySQL JDBC Driver
。将它加到CLASSPATH中。
3.创建测试数据库
在MySQL数据库中创建pltest数据库,执行解压缩路径下的testmySqlTest.sql,创建测试的数据表。
4.修改数据库连接属性
打开解压缩路径下的testmySqlTest.xml,修改user和password,其他不变。
Step2.映射
解压缩路径下的testschema.xml,是PL的映射文件。
以下是映射文件的一个片段:

顶层是map节点,map节点包含class和association子节点。
class节点代表一个类,class节点包含class-name,table-name,database-name和attribute子节点。
association节点代表一个关联关系。(关联信息的处理在此不作深入,将另文叙述)
Step3.装载
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\";
// 装载数据库信息
[color="blue"]PersistenceBroker.getInstance().loadConfig(new XMLConfigLoader(dir + "mySqlTest.xml"));
// 装载类-数据表映射信息
[color="blue"]PersistenceBroker.getInstance().loadConfig(new XMLConfigLoader(dir + "schema.xml"));
蓝色的两句完成了将映射信息装载到内存。整个过程的顺序图如下:

可以看到,
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的关联。
Step4.生成
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
//
[color="blue"]selectStatement = getSelectSql();
// Add 'FROM' and 'WHERE' clauses to the select statement
selectStatement.addSqlClause(" ");
[color="blue"]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
[color="blue"]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
2).取WHERE部分
下面是getFromAndWhereSql的代码
public SqlStatement getFromAndWhereSql() throws PlException
{
// Create new statement
SqlStatement statement = new SqlStatement();
// Add 'FROM' clause to the select statement
[color="blue"]statement.addSqlClause(" " + getRelationalDatabase().getClauseStringFrom() + " ");
boolean isFirst = true;
ClassMap classMap = this;
do
{
AttributeMap map = classMap.getAttributeMap(0);
if (map != null)
{
[color="blue"]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 [color="blue"]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.运行阶段
在pl.test.Test类中添加以下代码:
RetrieveCriteria criteria1 = new RetrieveCriteria(Person.class);
criteria1.getWhereCondition().addAndCriteria(criteria1.getGreaterThanCriteria("id"));
java.util.Vector parameters1 = new java.util.Vector();
parameters1.add(new Long(2));
Cursor result1 = criteria1.perform(parameters1);
[color="blue"]result1 = criteria1.perform(parameters1);
while (result1.next()) {
System.out.println(" person"+((Person)result1.getObject()).getName());
}
result1.close();
这段代码能够生成以下SQL语句:
SELECT person.id, person.name FROM person WHERE (person.id>2)
让我们来看看PL是如何将上一阶段生成的SQL语句中的"?"号替换成真实值的。
运行阶段生成SQL语句顺序图如下:

在顺序图的最后,我们追踪到GreaterThanCriteria.fillSqlStatement方法,就是这个方法在运行阶段将"?"号匹配成了参数值:
public void fillSqlStatement(pl.sql.SqlStatement statement, java.util.Iterator parameters) throws pl.PlException{
pl.map.ColumnMap cm = getAttributeMap().getColumnMap();
statement.addSqlClause(cm.getFullyQualifiedName() + ">?");
[color="blue"]statement.addParameter(cm.getConverter().convertFrom(parameters.next()), cm.getPlType());
}
addParameter()方法根据参数的类型,将参数值转换成数据库的类型,从而完成了运行阶段生成SQL语句。
Artem Rudoy 非常有趣,在pl.converter.TrivialConverter类说明中有这样一句:
"Trivial converter. Does nothing."


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/5142/showart_16338.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP