ibatis初步介绍
在开发过程中最能帮助你的是什么?是框架,一个优秀的框架可以极大的提高你的效率。struts给了我们什么?MVC的实现,国际化、灵活。还有很多。不过,在一个通常的WEB应该中,是不能缺少数据库的,而struts在这方面并没有给我们提供什么有效的帮助。通常情况下我们做这个的时候有几个选择。
最直接的当然是JDBC啊,自己写connect、statment和resultset等等的代码,结果是累死自己。
然后一种方法是EJB,EJB确实是一个好东西,可惜在很多场合用不上,起码它很烦,速度很慢
还有一种选择就是JDO及类似的东西。最著名是free的应该是castor,hibernate等。
现在我们又多了一种选择,就是ibatis Db Layer,它的主页是http://www.ibatis.com,为什么说它好,让我们来看看作者自己的说明吧,使用ibatis的理由
10、知道怎样操作10种以上的数据库
9 、可配置的caching(包括从属)
8、支持DataSource、local transaction managemen和global transaction
7、简单的XML配置文档
6、支持Map, Collection, List和简单类型包装(如Integer, String)
5、支持JavaBeans类(get/set 方法)
4、支持复杂的对象映射(如populating lists, complex object models)
3、对象模型从不完美(不需要修改)
2、数据模型从不完美(不需要修改)
1、你已经知道SQL,为什么还要学习其他东西
另外一点它是100% Open Source Freeware
下面我们就来看一看,做一个简单的ibatis需要哪一些工作。然后一步一步深入探索它的强大功能。在实践中来看它的好处在哪里。
在ibatis的网站上有一个它自己的petstore,在我个人看来是最简洁的petstore了,跟struts1.0结合。应该说是一个不错的教程。希望大家能够好好研究。当然,作为入门。我们先来做一个简单的程序。所采用的库嘛,就仍然是用petstore的了。数据库也是选择Oracle(为什么选择Oracle,很多朋友不理解,怎么不用mysql之类的呢,一个主要的原因是个人爱好,Oracle毕竟是商业数据库,有它的强大之处,另外在linux下它也是免费的,:)。废话少说,先用jpetstore3.1提供的ddl建立一个库吧。
然后在eclipse里建立一个ibatisDemo的工程。加入ibatis提供的库,建立相就的目录。看一下一个最简单的程序需要哪一些文件。我们选择一个简单表,即Category这个表的操作来演示功能
文件路径 | 功能说明 | 备注 |
config/properties/petstore.properties | 可变参数配置文件,所有根据环境不同的参数都放在这里 | |
config/properties/simple/dao.xml | dao配置文件,主要存放dao对象和数据池设置 | |
config/properties/simple/sql-map-config-storedb.xml | 真正的核心配置文件 | |
config/sqlmap/Category.xml | 存放Category的数据操作的SQL | |
com.ewuxi.champion.exception.DaoException.java | 自定义的Exception类,不用说了吧 | |
com.ewuxi.champion.Service.java | 一个服务类,用于初始化 | |
com.ewuxi.champion.persistence.dao.DaoCommon | Dao层的统一操作类,提供一些公共函数 | |
com.ewuxi.champion.persistence.dao.CategoryDb | Category的操作类 | |
com.ewuxi.champion.persistence.vo.Category | valueObject 值对象 | |
com.ewuxi.champion.persistence.dao.CategoryDbTest | 单元测试类 |
下面一个一个文件详细说明
petstore.properties
################################################################## SIMPLE CONFIGURATION SECTION ################################################################## ## SimpleDataSource properties SimpleDriver=oracle.jdbc.OracleDriver
|
这个不用解释,就是数据库的连接串,如果你在自己的机器上运行,当然这些都是需要改的。
dao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dao-config <dao-config> <context name="StoreDaoManager" default="true"> </context> </dao-config> |
上面这一段也是很简单的,连一个dao也没有配置,也就是说,用的是默认的Dao。其中<context name="StoreDaoManager" default="true">表示它是默认的数据库配置(它可以根据名字不同同时连接几个数据库的)。
sql-map-config-storedb.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sql-map-config PUBLIC "-//iBATIS.com//DTD SQL Map Config 1.0//EN" "http://www.ibatis.com/dtd/sql-map-config.dtd"> <sql-map-config> <properties resource="properties/petstore.properties" /> <settings <datasource name="jpestoreSimple" <sql-map resource="sqlmap/Category.xml" /> </sql-map-config> |
这里真正实现了数据库连接,我们使用的是dbcp的连接池。JDBC的配置大家都很熟了。${SimpleDriver}就是指的前面petstore.properties中的SimpleDriver的内容。
而<sql-map resource="sqlmap/Category.xml" />则表示包含Category.xml这个文件。
Category.xml
<?xml version="1.0" encoding="UTF-8"?> <sql-map name="Category">
<result-map name="result" class="com.ewuxi.champion.persistence.vo.Category"> <mapped-statement name="findByPrimaryKeyCategoryDao" result-map="result"> <dynamic-mapped-statement name="findCategoryDao" result-map="result"> <dynamic-mapped-statement name="updateByPrimaryKeyCategoryDao"> <mapped-statement name="deleteByPrimaryKeyCategoryDao"> <!-- ============================================= <parameter-map name="insert-params"> <!-- ============================================= <mapped-statement name="insertCategoryDao" parameter-map="insert-params" > |
上述文件就是真正的SQL所存在的地方。
<result-map name="result" class="com.ewuxi.champion.persistence.vo.Category"> 这一段的内容表示返回的对象是com.ewuxi.champion.persistence.vo.Category,也就是我们值对象。当执行查询的时候,dblay会封装出一个Category对象或者一个Category的list集合。其中数据列CATID就对象javabean的categoryId值。name是自定义的 |
<mapped-statement name="findByPrimaryKeyCategoryDao" result-map="result"> 此处result-map="result"表示返回结果以后,就会参照前面的result来返回对象。select CATID, NAME, DESCN from CATEGORY where CATID = #categoryId#标准的SQL,只不过这一点CATID = #categoryId#有些不同,#categoryId#表示传递一个Category对象时,Dblay会自动取得categoryId的值来执行SQL |
再来看一个
<dynamic-mapped-statement name="updateByPrimaryKeyCategoryDao"> 这个地方就体现了dblayer的强大之处,动态SQL。平常我们经常碰到的情况是根据不同的情况,执行的SQL有一点点不一样。写在程序里,要写不少的if then之类的,在这里,dbLayer给你一下解决了。比如在这里,我们三个值都是String对象,所以通过isNotNull就可以实现几种不同的update了,比如,如果我只想修改DESCN这个字段,只要传过去的Category对象只有categoryId和description有值,就会生成update CATEGORY set DESCN = #description# where CATID =#categoryId#。同样如果传递的对象只有categoryId和name有值,就会生成update CATEGORY set NAME = #name# where CATID =#categoryId#。是否很强大?:) |
前面这两种,参数的传递方式是内置参数,也就是CATID =#categoryId#这种,大家可能不太习惯,那就看一看标准的写法吧。
<!-- ============================================= <parameter-map name="insert-params"> <!-- ============================================= <mapped-statement name="insertCategoryDao" parameter-map="insert-params" > |
这里面的insert语句想来大家都很熟了吧?这个时候怎么取得参数呢?关键在于这里parameter-map="insert-params",表示会读取<parameter-map name="insert-params">的设置,而这个设置也不用多解释了吧,就是按顺序,三个?分别对应三个值。还能指明他们的数据类型。
下面来看看Service.java
package com.ewuxi.champion; import java.io.Reader; import org.apache.commons.logging.Log; import com.ibatis.common.resources.Resources; /** try { String resource = null; resource = "properties/simple/dao.xml"; Reader reader = Resources.getResourceAsReader(resource); } catch (Exception e) { } |
一个静态方法,从resource文件中读出配置,最后用DaoManager.configure(reader);完成配置。
DaoCommon
public static Dao getDefautDao(){ return DaoManager.getInstance().getDao(""); } public static SqlMap getSqlMap(Dao c) throws DaoException { try { DaoManager daoManager = DaoManager.getInstance(c); if (daoManager == null) { SqlMapDaoTransaction trans = (SqlMapDaoTransaction) daoManager.getLocalTransaction(); return sqlMap; public static SqlMap getSqlMap(String c) throws DaoException { return sqlMap; |
三个主要的函数,第一个是得到默认的DAO对象,后两个是根据一个dao对象或者一个参数(也就是前面<context name="StoreDaoManager" >中是name值)。取得SqlMap对象,这个对象是主要的数据操作接口。
/** * @throws Exception * 开始事务,所在session层必须使用它 */ public static void startTransaction() throws Exception { if (!DaoCommon.inTransaction()) { DaoManager.getInstance().startTransaction(); } } public static boolean inTransaction() throws Exception { /** /** |
下面的一些函数是对事务的一些封装。想必也很容易理解。
然后让我们来看CategoryDb的内容
/* * Created on 2003-10-11 * * To change the template for this generated file go to * Window - Preferences - Java - Code Generation - Code and Comments */ package com.ewuxi.champion.persistence.dao; import com.ewuxi.champion.exception.DaoException; /** public class CategoryDb { |
每一个函数都很类似的。关键就在这一句(Category) sqlMap.executeQueryForList("findByPrimaryKeyCategoryDao",vo);。看到"findByPrimaryKeyCategoryDao",这个对应于前面Category.xml中的名字。而vo则是一个Category对象。
最后是CategoryDbTest类,这个是我们的单元测试程序
/* import com.ewuxi.champion.exception.DaoException; /** public class CategoryDb { |
运行结果,测试通过。原代码全部下载
ibatis开发人员指南(翻译自ibatis_db_guide-1-2-8)
介绍
欢迎来到iBATIS Database Layer!这个框架将让你能够更好的在JAVA应用中设计和实现实体层。这个框架有两个主要的组成部分,一个是SQL Maps,另一个是Data Access Objects。另外还包括一些可能很有用的工具。
SQL Maps
Sql Maps是这个框架中最激动人心的部分,它是整个iBATIS Database Layer的核心价值所在。通过使用Sql Maps你可以显著的节约数据库操作的代码量。SQL Maps使用一个简单的XML文件来实现从javaBean到SQL statements的映射。跟其他的框架或者对象映射工具相比,SQL Maps最大的优势是简单。它需要学习的东西很少,在连接表或复杂查询时也不需要复杂的scheme(怎么翻complex scheme?),使用SQL Maps, 你可以自由的使用SQL语句。
Data Access Objects (DAO)
当我们开发灵活的JAVA应用时,有一个好主意就是通过一个通用API层把实体操作的细节封装起来。Data Access Objects允许你通过一个简单接口的来操作数据,从而隐藏了实现的细节。使用DAO,你可以动态配置你的应用程序来访问不同的实体存储机制。如果你有一个复杂的应用需要用到几个不同的数据库,DAO可以让你建立一个一致的API,让系统的其他部分来调用。
Utilities
iBATIS Database Layer包含一系列的有用的工具,比如SimpleDataSource,JDBC DataSource 2.0(也包括3.0)的一个轻量级实现。ScriptRunner也提供了从单元测试到自动发布的数据库准备工作。
Examples
跟这个框架一起有一个examples.zip,包含了一系列简单的实例,在http://www.ibatis.com上有更多的例子,包括非常著名的Jpestore, 一个在线商店。(译者注,蓝色天空也有一系列的中文介绍和实例)
About this Document
本文介绍了iBATIS Database Layer最重要的功能,还有一些功用没有写出来,凡是没有写出来的这些,可以认为是不支持或者正在修改。这些功能可能不经过通知就修改了,所以最好不要使用它们。本文将尽可能保持与框架同步。请确认你两者是否匹配。如果有任何问题或错误,请发mail到clinton.begin@ibatis.com。(译者注,也可以在蓝色天空讨论发帖讨论)。
SQL Maps (com.ibatis.db.sqlmap.*)
概念
SQL Map API允许程序员很简单的把JAVA对象映射到PreparedStatement参数或者ResultSets。SQL Maps的机制很简单,提供一个框架,来实现用20%的代码来实现80%JDBC的功能。
How does it work?
SQL Maps提供一个简单的框架,通过XML描述来映射JAVABeans,MAP implementations甚至原始类型的包装(String,Integer等)到JDBC PreparedStatement。想法很简单,基本的步骤如下:
1)提供一个对象作为参数(either a JavaBean, Map or primitive wrapper),The parameter object
will be used setting input values in an update statement, or query values in a where clause (etc.).(感觉不译为好,你说呢?)
2)执行这个映射的statement,这一步是魔术发生的地方。SQL Maps框架将建立一个PreparedStatement实例(instance),使用前面提供的对象的参数,执行statement,然后从ResultSet中建立一个返回对象。
3)如果是Update,则返回有多少行修改了,如果是查询,则返回一个对象或者对象的集合。跟参数一样,返回对象也可以是一个JAVABEAN,MAP或者一个primitive type wrapper。
流程图如下:
ibatis开发人员指南(翻译自ibatis_db_guide-1-2-8)(2)
Fast Track
本篇文章的第一部分将带你走过一系列的“fash Track”,带你浏览一遍SQL maps的简单应用。在walkthrough之后,将有详细的论述。
Fast Track: Preparing to Use SQL Maps
SQL Maps对不好的数据库模型甚至对象模型都有很强的容忍度。尽管如此,还是推荐你使用最佳实践来设计你的的数据库模型和对象模型。通过这样,你将得到更干净的设计和更好的性能。
最简单的开始就是分析你在做的内容,商业模型是什么样的,表结构是什么样的,它们怎么样互相发生关系。第一个例子,我们就简单的实现一个典型的Persion类。
Person.java package examples.domain; //imports implied…. public class Person { private int id; private String firstName; private String lastName; private Date birthDate; private double weightInKilograms; private double heightInMeters; public int getId () { return id; } public void setId (int id) { this.id = id; } //…let’s assume we have the other getters and setters to save space… } |
现在persion对象怎么映射到数据库?SQL Maps并不约束你必须要一个表一个对象或者多个表一个对象这种映射关系。因为你可以自由使用SQL语句,所以约束很小。在这个例子里,我们使用下面简单的表,实现一个表对象一个对象的映射关系。
Person.sql CREATE TABLE PERSON( PER_ID NUMBER (5, 0) NOT NULL, PER_FIRST_NAME VARCHAR (40) NOT NULL, PER_LAST_NAME VARCHAR (40) NOT NULL, PER_BIRTH_DATE DATETIME , PER_WEIGHT_KG NUMBER (4, 2) NOT NULL, PER_HEIGHT_M NUMBER (4, 2) NOT NULL, PRIMARY KEY (PER_ID) ) |
Fast Track: The SQL Map Configuration File
当我们对我们的工作感到很舒适时,最好的开始就是SQL Map的配置文件。这个文件是SQL Map实现的根配置。
配置文件是XML文件,我们用它来配置属性,JDBC DataSources 和 SQL Maps。它给我们一个便利的地方可以集中配置不同的DataSource。这个框架支持iBATIS SimpleDataSource, Jakarta DBCP (Commons),以及其他任何可以通过JNDI context来访问的DataSource。我们在以后将详细讨论这个问题。现在我们用Jakarta DBCP,结构很简单,象上面这个例子,它的配置文件如下。
SqlMapConfigExample.xml
<?xml version="1.0" encoding="UTF-8"?> |
SqlMapConfigExample.properties
# This is just a simple properties file that simplifies automated configuration
|
Fast Track: The SQL Map File(s)
现在我们已经配置好DataSource了,然后就要准备核心配置文件了。我们需要准备一个实际的SQL Map文件来存放SQL语句和以及用作映射的参数对象和结果对象(分别是输入和输出)。
继续我们上面的示例。让我们为Person类和Person表建立映射关系。我们先建立一个标准结构,和一个简单的select说明。
Person.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sql-map PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN" "http://www.ibatis.com/dtd/sql-map.dtd"> <sql-map name="Person"> <mapped-statement name="getPerson" result-class="examples.domain.Person"> SELECT PER_ID as id, PER_FIRST_NAME as firstName, PER_LAST_NAME as lastName, PER_BIRTH_DATE as birthDate, PER_WEIGHT_KG as weightInKilograms, PER_HEIGHT_M as heightInMeters FROM PERSON WHERE PER_ID = #value# </mapped-statement> </sql-map> |
上面的示例显示了一个SQL map的一个最简单的组成。它使用了SQL Maps的一个特性,就是自动根据字段名和JAVABean属性(Map的主键)名建立对应关系。#value#象征着一个输入参数,多情况下,使用"value"意味着我们使用一个基本类型 (e.g. Integer; but we’re not limited to this).
因为非常简单,所以使用这种方法有一些限制。首先不能明确指定每个字段的输入类型。没有办法自动加载相关数据(复杂类型),同时有一些性能影响,因为它使用了ResultSetMetaData。通过使用result-map,我们可以克服所有这些限制。但是现在,简单是我们的目标。同是,以后我们可以随便修改成其他方式(不需要修改java代码)。
多数JAVA程序不仅读取数据,还要更改数据。我们已经看到怎样在Map-statement里使用select 了,那Update,delete和Insert是什么样的?一个好消息,跟select没有什么区别。下面我们就完成一个我们的Person Sql Map,包括一系列的statement用来操作和修改数据。
Person.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sql-map PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN" "http://www.ibatis.com/dtd/sql-map.dtd">
<sql-map name="Person">
<!-- Use primitive wrapper type (e.g. Integer) as parameter and allow results to be auto-mapped results to Person object (JavaBean) properties -->
<mapped-statement name="getPerson" result-class="examples.domain.Person"> SELECT PER_ID as id, PER_FIRST_NAME as firstName, PER_LAST_NAME as lastName, PER_BIRTH_DATE as birthDate, PER_WEIGHT_KG as weightInKilograms, PER_HEIGHT_M as heightInMeters FROM PERSON WHERE PER_ID = #value# </mapped-statement>
<!-- Use Person object (JavaBean) properties as parameters for insert. Each of the parameters in the #hash# symbols is a JavaBeans property. -->
<mapped-statement name="insertPerson" > INSERT INTO PERSON (PER_ID, PER_FIRST_NAME, PER_LAST_NAME, PER_BIRTH_DATE, PER_WEIGHT_KG, PER_HEIGHT_M) VALUES (#id#, #firstName#, #lastName#, #birthDate#, #weightInKilograms#, #heightInMeters#) </mapped-statement>
<!-- Use Person object (JavaBean) properties as parameters for update. Each of the parameters in the #hash# symbols is a JavaBeans property. -->
<mapped-statement name="updatePerson" > UPDATE PERSON SET (PER_ID = PER_FIRST_NAME = #firstName#, PER_LAST_NAME = #lastName#, PER_BIRTH_DATE = #birthDate#, PER_WEIGHT_KG = #weightInKilograms#, PER_HEIGHT_M = #heightInMeters#) WHERE PER_ID = #id# </mapped-statement>
<!-- Use Person object (JavaBean) “id” properties as parameters for delete. Each of the parameters in the #hash# symbols is a JavaBeans property. -->
<mapped-statement name="deletePerson" > DELETE PERSON WHERE PER_ID = #id# </mapped-statement>
</sql-map> |
Fast Track: Programming with the SQL Map Framework
现在我们已经完成了所有的配置和映射,剩下的就是写JAVA代码了。第一步是配置SQL Map。加载我们前面配置好的SQL Map XML文件是很简单的。加载XML以后,就可以在框架里使用资源类。
String resource = “com/ibatis/example/sql-map-config.xml”; Reader reader = Resources.getResourceAsReader (resource); SqlMap sqlMap = XmlSqlMapBuilder.buildSqlMap(reader); |
SQL Map对象是线程安全的,意味着是长期生存的。对于一个运行的系统来说,你只要配置一次。所以它可以很好的成为一个基类的静态对象(比如,一个BASE Dao类),也许你更喜欢集中配置并成为全局可见,你可以把它包装在你自己的工具类中。比如说:
private MyAppSqlConfig { private static final SqlMap sqlMap; static { try { String resource = “com/ibatis/example/sql-map-config.xml”; Reader reader = Resources.getResourceAsReader (resource); sqlMap = XmlSqlMapBuilder.buildSqlMap(reader); } catch (Exception e) { // If you get an error at this point, it matters little what it was. It is going to be // unrecoverable and we will want the app to blow up good so we are aware of the // problem. You should always log such errors and re-throw them in such a way that // you can be made immediately aware of the problem. e.printStackTrace(); throw new RuntimeException (“Error initializing MyAppSqlConfig class. Cause: ” + e); } } public static getSqlMapInstance () { return sqlMap; } } |
从数据库读取对象
现在SQL Map实例已经完成初始化,并且很容易访问,我们可以使用它了。首先我们用它从数据库中取得一个Person对象。(举例,我们假设数据库中有10条记录,PER_ID分别从是1到10)
为了从数据库中取得一个Person对象,我们需要SQL Map实例,mapped statement的名称以及PER_ID号,让我们读取#5。
… SqlMap sqlMap = MyAppSqlMapConfig.getSqlMapInstance(); // as coded above … Integer personPk = new Integer(5); Person person = (Person) sqlMap.executeQueryForObject (“getPerson”, personPk); … |
把对象写到数据库中
现在我们已经从数据库取得一个对象,让我们修改一些值,我们将修改身高和体重。
… person.setHeightInMeters(1.83); // person as read from the database above person.setWeightInKilograms(86.36); … sqlMap.executeUpdate(“updatePerson”, person); … |
如果我们要删除一个对象,也一样的简单。
… sqlMap.executeUpdate(“deletePerson”, person); … |
同样的,新插入一个对象也类似。
Person newPerson = new Person(); newPerson.setId(11); // you would normally get the ID from a sequence or custom table newPerson.setFirstName(“Clinton”); newPerson.setLastName(“Begin”); newPerson.setBirthDate (null); newPerson.setHeightInMeters(1.83); newPerson.setWeightInKilograms(86.36); … sqlMap.executeUpdate (“insertPerson”, newPerson); … |
End of Fast Track(结束语)
This is the end of the quick walkthrough. The next several sections will discuss the features of the
SqlMap framework in more detail.
ibatis开发人员指南(翻译自ibatis_db_guide-1-2-8)(3)
The SQL Map XML Configuration File
(http://www.ibatis.com/dtd/sql-map-config.dtd)
SQL Map是一个核心的XML文件来配置的。这个文件包含了配置的细节和DataSources(数据源),Sql Map和其他选项,比如线程管理。以下是一个SqlMap的配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sql-map-config PUBLIC "-//iBATIS.com//DTD SQL Map Config 1.0//EN" "http://www.ibatis.com/dtd/sql-map-config.dtd">
<!-- Always ensure to use the correct XML header as above! --> <sql-map-config>
<!-- The properties (name=value) in the file specified here can be used placeholders in this config file (e.g. "${driver}". The file is relative to the classpath and is completely optional. --> <properties resource="examples/sqlmap/maps/SqlMapConfigExample.properties" />
<!-- These settings control SqlMap configuration details, primarily to do with transaction management. They are all optional (more detail later in this document). --> <settings maxExecute="300" maxExecutePerConnection="1" maxTransactions="10" statementCacheSize="75" useGlobalTransactions="false" useBeansMetaClasses="true" />
<!-- Configure a datasource to use with this SQL Map using Jakarta DBCP notice the use of the properties from the above resource --> <datasource name="basic" default = "true" factory-class="com.ibatis.db.sqlmap.datasource.DbcpDataSourceFactory"> <property name="JDBC.Driver" value="${driver}"/> <property name="JDBC.ConnectionURL" value="${url}"/> <property name="JDBC.Username" value="${username}"/> <property name="JDBC.Password" value="${password}"/> <property name="Pool.MaximumActiveConnections" value="10"/> <property name="Pool.MaximumIdleConnections" value="5"/> <property name="Pool.MaximumWait" value="60000"/> </datasource>
<!-- Identify all SQL Map XML files to be loaded by this SQL map. Notice the paths are relative to the classpath. --> <sql-map resource="examples/sqlmap/maps/Person.xml" /> <sql-map resource="examples/sqlmap/maps/Account.xml" /> <sql-map resource="examples/sqlmap/maps/Order.xml" /> <sql-map resource="examples/sqlmap/maps/LineItem.xml" /> <sql-map resource="examples/sqlmap/maps/Product.xml" /> </sql-map-config>
|
紫龙,于 <script language="JavaScript" type="text/JavaScript"> document.write (document.lastModified); </script> 12/22/2003 17:15:53
蓝色天空版权所有