主流的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...)