站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题深入浅出Persistence Layer(2)

深入浅出Persistence Layer(2)

摘要:

深入浅出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...)


↑返回目录
前一篇: JDO2.0的查询语言新特性
后一篇: 深入浅出Persistence Layer(1)