﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-玩程序的人</title><link>http://www.cnitblog.com/dahutu/</link><description>Treat every thing as usual...</description><language>zh-cn</language><lastBuildDate>Tue, 05 May 2026 04:34:32 GMT</lastBuildDate><pubDate>Tue, 05 May 2026 04:34:32 GMT</pubDate><ttl>60</ttl><item><title>数据库设计经验谈（转载）</title><link>http://www.cnitblog.com/dahutu/archive/2005/11/01/3755.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Mon, 31 Oct 2005 16:32:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/11/01/3755.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3755.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/11/01/3755.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3755.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3755.html</trackback:ping><description><![CDATA[<SPAN id=ArticleContent1_ArticleContent1_lblContent>&nbsp;
<P><SPAN class=Title>数据库设计经验谈(上)</SPAN></P>
<P>一个成功的管理系统，是由：[50% 的业务 + 50% 的软件] 所组成，而 50% 的成功软件又有 [25% 的数据库 + 25% 的程序] 所组成，数据库设计的好坏是一个关键。如果把企业的数据比做生命所必需的血液，那么数据库的设计就是应用中最重要的一部分。有关数据库设计的材料汗牛充栋，大学学位课程里也有专门的讲述。不过，就如我们反复强调的那样，再好的老师也比不过经验的教诲。所以我归纳历年来所走的弯路及体会，并在网上找了些对数据库设计颇有造诣的专业人士给大家传授一些设计数据库的技巧和经验。精选了其中的 60 个最佳技巧，并把这些技巧编写成了本文，为了方便索引其内容划分为 5 个部分：</P>
<P>第 1 部分 - 设计数据库之前<BR>这一部分罗列了 12 个基本技巧，包括命名规范和明确业务需求等。 <BR>第 2 部分 - 设计数据库表<BR>总共 24 个指南性技巧，涵盖表内字段设计以及应该避免的常见问题等。 <BR>第 3 部分 - 选择键<BR>怎么选择键呢？这里有 10 个技巧专门涉及系统生成的主键的正确用法，还有何 时以及如何索引字段以获得最佳性能等。 <BR>第 4 部分 - 保证数据完整性<BR>讨论如何保持数据库的清晰和健壮，如何把有害数据降低到最小程度。 <BR>第 5 部分 - 各种小技巧<BR>不包括在以上 4 个部分中的其他技巧，五花八门，有了它们希望你的数据库开发工作会更轻松一些。 <BR>第 1 部分 - 设计数据库之前<BR>考察现有环境<BR>在设计一个新数据库时，你不但应该仔细研究业务需求而且还要考察现有的系统。大多数数据库项目都不是从头开始建立的；通常，机构内总会存在用来满足特定需求的现有系统（可能没有实现自动计算）。显然，现有系统并不完美，否则你就不必再建立新系统了。但是对旧系统的研究可以让你发现一些可能会忽略的细微问题。一般来说，考察现有系统对你绝对有好处。 <BR>定义标准的对象命名规范<BR>一定要定义数据库对象的命名规范。对数据库表来说，从项目一开始就要确定表名是采用复数还是单数形式。此外还要给表的别名定义简单规则（比方说，如果表名是一个单词，别名就取单词的前 4 个字母；如果表名是两个单词，就各取两个单词的前两个字母组成 4 个字母长的别名；如果表的名字由 3 个单词组成，你不妨从头两个单词中各取一个然后从最后一个单词中再取出两个字母，结果还是组成 4 字母长的别名，其余依次类推）对工作用表来说，表名可以加上前缀 WORK_ 后面附上采用该表的应用程序的名字。表内的列[字段]要针对键采用一整套设计规则。比如，如果键是数字类型，你可以用 _N 作为后缀；如果是字符类型则可以采用 _C 后缀。对列[字段]名应该采用标准的前缀和后缀。再如，假如你的表里有好多“money”字段，你不妨给每个列[字段]增加一个 _M 后缀。还有，日期列[字段]最好以 D_ 作为名字打头。</P>
<P>检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些不同的数据库要素的名称搞糊涂了。假如你坚持统一地命名这些数据库的不同组成部分，至少你应该在这些对象名字的开头用 Table、Query 或者 Report 等前缀加以区别。</P>
<P>如果采用了 Microsoft Access，你可以用 qry、rpt、tbl 和 mod 等符号来标识对象（比如 tbl_Employees）。我在和 SQL Server 打交道的时候还用过 tbl 来索引表，但我用 sp_company （现在用 sp_feft_）标识存储过程，因为在有的时候如果我发现了更好的处理办法往往会保存好几个拷贝。我在实现 SQL Server 2000 时用 udf_ （或者类似的标记）标识我编写的函数。 <BR>工欲善其事, 必先利其器<BR>采用理想的数据库设计工具，比如：SyBase 公司的 PowerDesign，她支持 PB、VB、Delphe 等语言，通过 ODBC 可以连接市面上流行的 30 多个数据库，包括 dBase、FoxPro、VFP、SQL Server 等，今后有机会我将着重介绍 PowerDesign 的使用。 <BR>获取数据模式资源手册<BR>正在寻求示例模式的人可以阅读《数据模式资源手册》一书，该书由 Len Silverston、W. H. Inmon 和 Kent Graziano 编写，是一本值得拥有的最佳数据建模图书。该书包括的章节涵盖多种数据领域，比如人员、机构和工作效能等。其他的你还可以参考：[1]萨师煊　王珊著　数据库系统概论(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 与客户／服务器计算技术从入门到精通　刘建元等译　电子工业出版社，1996、[3]周中元　信息系统建模方法(下)　电子与信息化　1999年第3期，1999 <BR>畅想未来，但不可忘了过去的教训<BR>我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两个目的：首先，你可以清楚地了解应用设计在哪个地方应该更具灵活性以及如何避免性能瓶颈；其次，你知道发生事先没有确定的需求变更时用户将和你一样感到吃惊。</P>
<P>一定要记住过去的经验教训！我们开发人员还应该通过分享自己的体会和经验互相帮助。即使用户认为他们再也不需要什么支持了，我们也应该对他们进行这方面的教育，我们都曾经面临过这样的时刻“当初要是这么做了该多好..”。 <BR>在物理实践之前进行逻辑设计<BR>在深入物理设计之前要先进行逻辑设计。随着大量的 CASE 工具不断涌现出来，你的设计也可以达到相当高的逻辑水准，你通常可以从整体上更好地了解数据库设计所需要的方方面面。 <BR>了解你的业务<BR>在你百分百地确定系统从客户角度满足其需求之前不要在你的 ER（实体关系）模式中加入哪怕一个数据表（怎么，你还没有模式？那请你参看技巧 9）。了解你的企业业务可以在以后的开发阶段节约大量的时间。一旦你明确了业务需求，你就可以自己做出许多决策了。</P>
<P>一旦你认为你已经明确了业务内容，你最好同客户进行一次系统的交流。采用客户的术语并且向他们解释你所想到的和你所听到的。同时还应该用可能、将会和必须等词汇表达出系统的关系基数。这样你就可以让你的客户纠正你自己的理解然后做好下一步的 ER 设计。 <BR>创建数据字典和 ER 图表<BR>一定要花点时间创建 ER 图表和数据字典。其中至少应该包含每个字段的数据类型和在每个表内的主外键。创建 ER 图表和数据字典确实有点费时但对其他开发人员要了解整个设计却是完全必要的。越早创建越能有助于避免今后面临的可能混乱，从而可以让任何了解数据库的人都明确如何从数据库中获得数据。</P>
<P>有一份诸如 ER 图表等最新文档其重要性如何强调都不过分，这对表明表之间关系很有用，而数据字典则说明了每个字段的用途以及任何可能存在的别名。对 SQL 表达式的文档化来说这是完全必要的。 <BR>创建模式<BR>一张图表胜过千言万语：开发人员不仅要阅读和实现它，而且还要用它来帮助自己和用户对话。模式有助于提高协作效能，这样在先期的数据库设计中几乎不可能出现大的问题。模式不必弄的很复杂；甚至可以简单到手写在一张纸上就可以了。只是要保证其上的逻辑关系今后能产生效益。 <BR>从输入输出下手<BR>在定义数据库表和字段需求（输入）时，首先应检查现有的或者已经设计出的报表、查询和视图（输出）以决定为了支持这些输出哪些是必要的表和字段。举个简单的例子：假如客户需要一个报表按照邮政编码排序、分段和求和，你要保证其中包括了单独的邮政编码字段而不要把邮政编码糅进地址字段里。 <BR>报表技巧<BR>要了解用户通常是如何报告数据的：批处理还是在线提交报表？时间间隔是每天、每周、每月、每个季度还是每年？如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很难管理。用户在具有系统生成主键的表内用副键进行检索往往会返回许多重复数据。这样的检索性能比较低而且容易引起混乱。 <BR>理解客户需求<BR>看起来这应该是显而易见的事，但需求就是来自客户（这里要从内部和外部客户的角度考虑）。不要依赖用户写下来的需求，真正的需求在客户的脑袋里。你要让客户解释其需求，而且随着开发的继续，还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理是：“只有我看见了我才知道我想要的是什么”必然会导致大量的返工，因为数据库没有达到客户从来没有写下来的需求标准。而更糟的是你对他们需求的解释只属于你自己，而且可能是完全错误的。 <BR>第 2 部分 - 设计表和字段<BR>检查各种变化<BR>我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说，姓氏就是如此（注意是西方人的姓氏，比如女性结婚后从夫姓等）。所以，在建立系统存储客户信息时，我倾向于在单独的一个数据表里存储姓氏字段，而且还附加起始日和终止日等字段，这样就可以跟踪这一数据条目的变化。 <BR>采用有意义的字段名<BR>有一回我参加开发过一个项目，其中有从其他程序员那里继承的程序，那个程序员喜欢用屏幕上显示数据指示用语命名字段，这也不赖，但不幸的是，她还喜欢用一些奇怪的命名法，其命名采用了匈牙利命名和控制序号的组合形式，比如 cbo1、txt2、txt2_b 等等。<BR>除非你在使用只面向你的缩写字段名的系统，否则请尽可能地把字段描述的清楚些。当然，也别做过头了，比如 Customer_Shipping_Address_Street_Line_1，虽然很富有说明性，但没人愿意键入这么长的名字，具体尺度就在你的把握中。 <BR>采用前缀命名<BR>如果多个表里有好多同一类型的字段（比如 FirstName），你不妨用特定表的前缀（比如 CusLastName）来帮助你标识字段。</P>
<P>时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。 <BR>标准化和数据驱动<BR>数据的标准化不仅方便了自己而且也方便了其他人。比方说，假如你的用户界面要访问外部数据源（文件、XML 文档、其他数据库等），你不妨把相应的连接和路径信息存储在用户界面支持表里。还有，如果用户界面执行工作流之类的任务（发送邮件、打印信笺、修改记录状态等），那么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力，但如果这些过程采用数据驱动而非硬编码的方式，那么策略变更和维护都会方便得多。事实上，如果过程是数据驱动的，你就可以把相当大的责任推给用户，由用户来维护自己的工作流过程。 <BR>标准化不能过头<BR>对那些不熟悉标准化一词（normalization）的人而言，标准化可以保证表内的字段都是最基础的要素，而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式，但 Third Normal Form（3NF）通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说，3NF 规定：<BR>* 表内的每一个值都只能被表达一次。<BR>* 表内的每一行都应该被唯一的标识（有唯一键）。<BR>* 表内不应该存储依赖于其他键的非键信息。<BR>遵守 3NF 标准的数据库具有以下特点：有一组表专门存放通过键连接起来的关联数据。比方说，某个存放客户及其有关定单的 3NF 数据库就可能有两个表：Customer 和 Order。Order 表不包含定单关联客户的任何信息，但表内会存放一个键值，该键指向 Customer 表里包含该客户信息的那一行。<BR>更高层次的标准化也有，但更标准是否就一定更好呢？答案是不一定。事实上，对某些项目来说，甚至就连 3NF 都可能给数据库引入太高的复杂性。</P>
<P>为了效率的缘故，对表不进行标准化有时也是必要的，这样的例子很多。曾经有个开发餐饮分析软件的活就是用非标准化表把查询时间从平均 40 秒降低到了两秒左右。虽然我不得不这么做，但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表出了问题重新产生非标准化的表是完全可能的。 <BR>Microsoft Visual FoxPro 报表技巧<BR>如果你正在使用 Microsoft Visual FoxPro，你可以用对用户友好的字段名来代替编号的名称：比如用 Customer Name 代替 txtCNaM。这样，当你用向导程序 [Wizards，台湾人称为‘精灵’] 创建表单和报表时，其名字会让那些不是程序员的人更容易阅读。 <BR>不活跃或者不采用的指示符<BR>增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么人，这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用数据时所面临的一些问题，比如，某些记录可能不再为他们所用，再删除的时候可以起到一定的防范作用。 <BR>使用角色实体定义属于某类别的列[字段]<BR>在需要对属于特定类别或者具有特定角色的事物做定义时，可以用角色实体来创建特定的时间关联关系，从而可以实现自我文档化。<BR>这里的含义不是让 PERSON 实体带有 Title 字段，而是说，为什么不用 PERSON 实体和 PERSON_TYPE 实体来描述人员呢？比方说，当 John Smith, Engineer 提升为 John Smith, Director 乃至最后爬到 John Smith, CIO 的高位，而所有你要做的不过是改变两个表 PERSON 和 PERSON_TYPE 之间关系的键值，同时增加一个日期/时间字段来知道变化是何时发生的。这样，你的 PERSON_TYPE 表就包含了所有 PERSON 的可能类型，比如 Associate、Engineer、Director、CIO 或者 CEO 等。<BR>还有个替代办法就是改变 PERSON 记录来反映新头衔的变化，不过这样一来在时间上无法跟踪个人所处位置的具体时间。 <BR>采用常用实体命名机构数据<BR>组织数据的最简单办法就是采用常用名字，比如：PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实体时，你就得到了自己用的特殊版本。开始的时候采用一般术语的主要原因在于所有的具体用户都能对抽象事物具体化。<BR>有了这些抽象表示，你就可以在第 2 级标识中采用自己的特殊名称，比如，PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同样的，ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最后 ADDRESS 可以具体为 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。<BR>采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵活性，同时这样做还可以显著降低数据存储所需的冗余量。 <BR>用户来自世界各地<BR>在设计用到网络或者具有其他国际特性的数据库时，一定要记住大多数国家都有不同的字段格式，比如邮政编码等，有些国家，比如新西兰就没有邮政编码一说。 <BR>数据重复需要采用分立的数据表<BR>如果你发现自己在重复输入数据，请创建新表和新的关系。 <BR>每个表中都应该添加的 3 个有用的字段<BR>* dRecordCreationDate，在 VB 下默认是 Now()，而在 SQL Server 下默认为 GETDATE()<BR>* sRecordCreator，在 SQL Server 下默认为 NOT NULL DEFAULT USER<BR>* nRecordVersion，记录的版本标记；有助于准确说明记录中出现 null 数据或者丢失数据的原因 <BR>对地址和电话采用多个字段<BR>描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的灵活性。还有，电话号码和邮件地址最好拥有自己的数据表，其间具有自身的类型和标记类别。</P>
<P>过分标准化可要小心，这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到最佳状态，但是如果需要经常访问这类信息，或许在其父表中存放“首选”信息（比如 Customer 等）更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。 <BR>使用多个名称字段<BR>我觉得很吃惊，许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才会这么做，但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理，然后在查询的时候再把他们组合起来。</P>
<P>我最常用的是在同一表中创建一个计算列[字段]，通过它可以自动地连接标准化后的字段，这样数据变动的时候它也跟着变。不过，这样做在采用建模软件时得很机灵才行。总之，采用连接字段的方式可以有效的隔离用户应用和开发人员界面。 <BR>提防大小写混用的对象名和特殊字符<BR>过去最令我恼火的事情之一就是数据库里有大小写混用的对象名，比如 CustomerData。这一问题从 Access 到 Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法，结果还不得不手工修改名字。想想看，这种数据库/应用程序能混到采用更强大数据库的那一天吗？采用全部大写而且包含下划符的名字具有更好的可读性（CUSTOMER_DATA），绝对不要在对象名的字符之间留空格。 <BR>小心保留词<BR>要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突，比如，最近我编写的一个 ODBC 连接程序里有个表，其中就用了 DESC 作为说明字段名。后果可想而知！DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用，但我得到的却是一大堆毫无用处的信息。 <BR>保持字段名和类型的一致性<BR>在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做“agreement_number”，你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里是整数，那在另一个表里可就别变成字符型了。记住，你干完自己的活了，其他人还要用你的数据库呢。 <BR>仔细选择数字类型<BR>在 SQL 中使用 smallint 和 tinyint 类型要特别小心，比如，假如你想看看月销售总额，你的总额字段类型是 smallint，那么，如果总额超过了 $32,767 你就不能进行计算操作了。 <BR>删除标记<BR>在表中包含一个“删除标记”字段，这样就可以把行标记为删除。在关系数据库里不要单独删除某一行；最好采用清除数据程序而且要仔细维护索引整体性。 <BR>避免使用触发器<BR>触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采用触发器，你最好集中对它文档化。 <BR>包含版本机制<BR>建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要求。时间一长，用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检查新字段或者索引来确定数据库结构的版本，但我发现把版本信息直接存放到数据库中不更为方便吗？。 <BR>给文本字段留足余量<BR>ID 类型的文本字段，比如客户 ID 或定单号等等都应该设置得比一般想象更大，因为时间不长你多半就会因为要添加额外的字符而难堪不已。比方说，假设你的客户 ID 为 10 位数长。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗？是有一点，但也没你想象的那么多：一个字段加长 3 个字符在有 1 百万条记录，再加上一点索引的情况下才不过让整个数据库多占据 3MB 的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模的增长了。身份证的号码从 15 位变成 18 位就是最好和最惨痛的例子。 <BR>列[字段]命名技巧<BR>我们发现，假如你给每个表的列[字段]名都采用统一的前缀，那么在编写 SQL 表达式的时候会得到大大的简化。这样做也确实有缺点，比如破坏了自动表连接工具的作用，后者把公共列[字段]名同某些数据库联系起来，不过就连这些工具有时不也连接错误嘛。举个简单的例子，假设有两个表：<BR>Customer 和 Order。Customer 表的前缀是 cu_，所以该表内的子段名如下：cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前缀是 or_，所以子段名是：<BR>or_order_id、or_cust_name_id、or_quantity 和 or_description 等。<BR>这样从数据库中选出全部数据的 SQL 语句可以写成如下所示：<BR>Select * From Customer, Order Where cu_surname = "MYNAME" ;<BR>and cu_name_id = or_cust_name_id and or_quantity = 1<BR>在没有这些前缀的情况下则写成这个样子（用别名来区分）：<BR>Select * From Customer, Order Where Customer.surname = "MYNAME" ;<BR>and Customer.name_id = Order.cust_name_id and Order.quantity = 1<BR>第 1 个 SQL 语句没少键入多少字符。但如果查询涉及到 5 个表乃至更多的列[字段]你就知道这个技巧多有用了</P></SPAN><img src ="http://www.cnitblog.com/dahutu/aggbug/3755.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-11-01 00:32 <a href="http://www.cnitblog.com/dahutu/archive/2005/11/01/3755.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>改变无数人人生的32句话（转载）</title><link>http://www.cnitblog.com/dahutu/archive/2005/10/28/3649.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Fri, 28 Oct 2005 09:22:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/10/28/3649.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3649.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/10/28/3649.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3649.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3649.html</trackback:ping><description><![CDATA[1、大多数人想要改造这个世界，但却罕有人想改造自己。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;2、积极的人在每一次忧患中都看到一个机会， 而消极的人则在每个机会都看到某种忧患。 <BR><BR>
<DIV id=newsdetail-content-left><!--小广告开始--><A href="http://info.china.alibaba.com/news/subject/v5003013-s5014500.html" target=_blank><IMG height=160 src="http://img.china.alibaba.com/news/upload/5003013/news/2005/10/21/zhonggao_1129910674814.jpg" width=261 border=0></A> <!--小广告结束--></DIV>
<P></P>
<P>3、莫找借口失败，只找理由成功。(不为失败找理由，要为成功找方法) <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;4、伟人之所以伟大，是因为他与别人共处逆境时，别人失去了信心，他却下决心实现自己的目标。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;5、世上没有绝望的处境，只有对处境绝望的人。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;6、当你感到悲哀痛苦时，最好是去学些什么东西。学习会使你永远立于不败之地。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;7、世界上那些最容易的事情中，拖延时间最不费力。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;8、人之所以能，是相信能。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;9、一个有信念者所开发出的力量，大于99个只有兴趣者。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;10、每一发奋努力的背后，必有加倍的赏赐。<BR><BR>　　<FONT color=red>相关阅读：</FONT><A href="http://info.china.alibaba.com/news/detail/v5003013-d5570604.html" target=_blank>贯穿你一生的七个忠告</A>　　<A href="http://info.china.alibaba.com/news/detail/v5003013-d5568273.html" target_blank>人生失败的31种致命原因</A><BR><BR>　　11、人生伟业的建立 ，不在能知，乃在能行。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;12、任何的限制，都是从自己的内心开始的。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;13、含泪播种的人一定能含笑收获。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;14、欲望以提升热忱，毅力以磨平高山。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;15、一个能从别人的观念来看事情，能了解别人心灵活动的人永远不必为自己的前途担心。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;16、一个人最大的破产是绝望，最大的资产是希望。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;17、不要等待机会，而要创造机会。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;18、如果寒暄只是打个招呼就了事的话，那与猴子的呼叫声有什么不同呢？ 事实上，正确的寒暄必须在短短一句话中明显地表露出你对他的关怀。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;19、昨晚多几分钟的准备，今天少几小时的麻烦。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;20、做对的事情比把事情做对重要。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;21、人格的完善是本，财富的确立是末。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;22、没有一种不通过蔑视、忍受和奋斗就可以征服的命运。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;23、行动是治愈恐惧的良药，而犹豫、拖延将不断滋养恐惧。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;24、没有天生的信心，只有不断培养的信心。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;25、只有一条路不能选择——那就是放弃的路；只有一条路不能拒绝——那就是成长的路。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;26、人性最可怜的就是：我们总是梦想着天边的一座奇妙的玫瑰园，而不去欣赏今天就开在我们窗口的玫瑰。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;27、征服畏惧、建立自信的最快最确实的方法，就是去做你害怕的事，直到你获得成功的经验。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;28、失败是什么？没有什么，只是更走近成功一步；成功是什么？就是走过了所有通向失败的路，只剩下一条路，那就是成功的路。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;29、让我们将事前的忧虑，换为事前的思考和计划吧！ <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;30、再长的路，一步步也能走完，再短的路，不迈开双脚也无法到达。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;31、任何业绩的质变都来自于量变的积累。 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;32、成功不是将来才有的，而是从决定去做的那一刻起，持续累积而成</P><img src ="http://www.cnitblog.com/dahutu/aggbug/3649.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-10-28 17:22 <a href="http://www.cnitblog.com/dahutu/archive/2005/10/28/3649.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>61条面向对象设计的经验原则 （转载）</title><link>http://www.cnitblog.com/dahutu/archive/2005/10/28/3647.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Fri, 28 Oct 2005 09:03:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/10/28/3647.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3647.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/10/28/3647.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3647.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3647.html</trackback:ping><description><![CDATA[<SPAN class=content>作者：Arthur J.Riel <BR><BR><BR><!-- #EndEditable -->“你不必严格遵守这些原则，违背它们也不会被处以宗教刑罚。但你应当把这些原则看成警铃，若违背了其中的一条，那么警铃就会响起。” </SPAN>
<P class=content>----------Arthur J.Riel<BR><BR>(1)所有数据都应该隐藏在所在的类的内部。</P>
<P class=content>(2)类的使用者必须依赖类的共有接口，但类不能依赖它的使用者。</P>
<P class=content>(3)尽量减少类的协议中的消息。</P>
<P class=content>(4)实现所有类都理解的最基本公有接口[例如，拷贝操作(深拷贝和浅拷贝)、相等性判断、正确输出内容、从ASCII描述解析等等]。</P>
<P class=content>(5)不要把实现细节(例如放置共用代码的私有函数)放到类的公有接口中。<BR>如果类的两个方法有一段公共代码，那么就可以创建一个防止这些公共代码的私有函数。 </P>
<P class=content>(6)不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。</P>
<P class=content>(7)类之间应该零耦合，或者只有导出耦合关系。也即，一个类要么同另一个类毫无关系，要么只使用另一个类的公有接口中的操作。</P>
<P class=content>(8)类应该只表示一个关键抽象。<BR>包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包影响，则将对包中的所有类产生影响，而对其他的包不造成任何影响 . </P>
<P class=content>(9)把相关的数据和行为集中放置。<BR>设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。这种类型的行为暗示着这条经验原则被违反了。 </P>
<P class=content>(10)把不相关的信息放在另一个类中(也即：互不沟通的行为)。<BR>朝着稳定的方向进行依赖. </P>
<P class=content>(11)确保你为之建模的抽象概念是类，而不只是对象扮演的角色。</P>
<P class=content>(12)在水平方向上尽可能统一地分布系统功能，也即：按照设计，顶层类应当统一地共享工作。</P>
<P class=content>(13)在你的系统中不要创建全能类/对象。对名字包含Driver、Manager、System、Susystem的类要特别多加小心。<BR>规划一个接口而不是实现一个接口。 </P>
<P class=content>(14)对公共接口中定义了大量访问方法的类多加小心。大量访问方法意味着相关数据和行为没有集中存放。</P>
<P class=content>(15)对包含太多互不沟通的行为的类多加小心。<BR>这个问题的另一表现是在你的应用程序中的类的公有接口中创建了很多的get和set函数。 </P>
<P class=content>(16)在由同用户界面交互的面向对象模型构成的应用程序中，模型不应该依赖于界面，界面则应当依赖于模型。</P>
<P class=content>(17)尽可能地按照现实世界建模(我们常常为了遵守系统功能分布原则、避免全能类原则以及集中放置相关数据和行为的原则而违背这条原则) 。</P>
<P class=content>(18)从你的设计中去除不需要的类。<BR>一般来说，我们会把这个类降级成一个属性。 </P>
<P class=content>(19)去除系统外的类。<BR>系统外的类的特点是，抽象地看它们只往系统领域发送消息但并不接受系统领域内其他类发出的消息。 </P>
<P class=content>(20)不要把操作变成类。质疑任何名字是动词或者派生自动词的类，特别是只有一个有意义行为的类。考虑一下那个有意义的行为是否应当迁移到已经存在或者尚未发现的某个类中。</P>
<P class=content>(21)我们在创建应用程序的分析模型时常常引入代理类。在设计阶段，我们常会发现很多代理没有用的，应当去除。</P>
<P class=content>(22)尽量减少类的协作者的数量。<BR>一个类用到的其他类的数目应当尽量少。 </P>
<P class=content>(23)尽量减少类和协作者之间传递的消息的数量。</P>
<P class=content>(24)尽量减少类和协作者之间的协作量，也即：减少类和协作者之间传递的不同消息的数量。</P>
<P class=content>(25)尽量减少类的扇出，也即：减少类定义的消息数和发送的消息数的乘积。</P>
<P class=content>(26)如果类包含另一个类的对象，那么包含类应当给被包含的对象发送消息。也即：包含关系总是意味着使用关系。</P>
<P class=content>(27)类中定义的大多数方法都应当在大多数时间里使用大多数数据成员。</P>
<P class=content>(28)类包含的对象数目不应当超过开发者短期记忆的容量。这个数目常常是6。<BR>当类包含多于6个数据成员时，可以把逻辑相关的数据成员划分为一组，然后用一个新的包含类去包含这一组成员。 </P>
<P class=content>(29)让系统功能在窄而深的继承体系中垂直分布。</P>
<P class=content>(30)在实现语义约束时，最好根据类定义来实现。这常常会导致类泛滥成灾，在这种情况下，约束应当在类的行为中实现，通常是在构造函数中实现，但不是必须如此。</P>
<P class=content>(31)在类的构造函数中实现语义约束时，把约束测试放在构造函数领域所允许的尽量深的包含层次中。</P>
<P class=content>(32)约束所依赖的语义信息如果经常改变，那么最好放在一个集中式的第3方对象中。</P>
<P class=content>(33)约束所依赖的语义信息如果很少改变，那么最好分布在约束所涉及的各个类中。</P>
<P class=content>(34)类必须知道它包含什么，但是不能知道谁包含它。</P>
<P class=content>(35)共享字面范围(也就是被同一个类所包含)的对象相互之间不应当有使用关系。</P>
<P class=content>(36)继承只应被用来为特化层次结构建模。</P>
<P class=content>(37)派生类必须知道基类，基类不应该知道关于它们的派生类的任何信息。</P>
<P class=content>(38)基类中的所有数据都应当是私有的，不要使用保护数据。<BR>类的设计者永远都不应该把类的使用者不需要的东西放在公有接口中。 </P>
<P class=content>(39)在理论上，继承层次体系应当深一点，越深越好。</P>
<P class=content>(40)在实践中，继承层次体系的深度不应当超出一个普通人的短期记忆能力。一个广为接受的深度值是6。</P>
<P class=content>(41)所有的抽象类都应当是基类。</P>
<P class=content>(42)所有的基类都应当是抽象类。</P>
<P class=content>(43)把数据、行为和/或接口的共性尽可能地放到继承层次体系的高端。</P>
<P class=content>(44)如果两个或更多个类共享公共数据(但没有公共行为)，那么应当把公共数据放在一个类中，每个共享这个数据的类都包含这个类。</P>
<P class=content>(45)如果两个或更多个类有共同的数据和行为(就是方法)，那么这些类的每一个都应当从一个表示了这些数据和方法的公共基类继承。</P>
<P class=content>(46)如果两个或更多个类共享公共接口(指的是消息，而不是方法)，那么只有他们需要被多态地使用时，他们才应当从一个公共基类继承。</P>
<P class=content>(47)对对象类型的显示的分情况分析一般是错误的。在大多数这样的情况下，设计者应当使用多态。</P>
<P class=content>(48)对属性值的显示的分情况分析常常是错误的。类应当解耦合成一个继承层次结构，每个属性值都被变换成一个派生类。</P>
<P class=content>(49)不要通过继承关系来为类的动态语义建模。试图用静态语义关系来为动态语义建模会导致在运行时切换类型。</P>
<P class=content>(50)不要把类的对象变成派生类。对任何只有一个实例的派生类都要多加小心。</P>
<P class=content>(51)如果你觉得需要在运行时刻创建新的类，那么退后一步以认清你要创建的是对象。现在，把这些对象概括成一个类。</P>
<P class=content>(52)在派生类中用空方法(也就是什么也不做的方法)来覆写基类中的方法应当是非法的。</P>
<P class=content>(53)不要把可选包含同对继承的需要相混淆。把可选包含建模成继承会带来泛滥成灾的类。</P>
<P class=content>(54)在创建继承层次时，试着创建可复用的框架，而不是可复用的组件。</P>
<P class=content>(55)如果你在设计中使用了多重继承，先假设你犯了错误。如果没犯错误，你需要设法证明。</P>
<P class=content>(56)只要在面向对象设计中用到了继承，问自己两个问题：(1)派生类是否是它继承的那个东西的一个特殊类型？(2)基类是不是派生类的一部分？ </P>
<P class=content>(57)如果你在一个面向对象设计中发现多重继承关系，确保没有哪个基类实际上是另一个基类的派生类。</P>
<P class=content>(58)在面向对象设计中如果你需要在包含关系和关联关系间作出选择，请选择包含关系。</P>
<P class=content>(59)不要把全局数据或全局函数用于类的对象的薄记工作。应当使用类变量或类方法。</P>
<P class=content>(60)面向对象设计者不应当让物理设计准则来破坏他们的逻辑设计。但是，在对逻辑设计作出决策的过程中我们经常用到物理设计准则。</P>
<P class=content>(61)不要绕开公共接口去修改对象的状态。</P><img src ="http://www.cnitblog.com/dahutu/aggbug/3647.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-10-28 17:03 <a href="http://www.cnitblog.com/dahutu/archive/2005/10/28/3647.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用 Hibernate 和 Spring 开发事务持久层（转载）</title><link>http://www.cnitblog.com/dahutu/archive/2005/10/28/3646.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Fri, 28 Oct 2005 08:38:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/10/28/3646.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3646.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/10/28/3646.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3646.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3646.html</trackback:ping><description><![CDATA[来自：IBM 作者：Richard <BR><!-- #EndEditable -->
<TABLE height=65 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=content height=65><!-- #BeginEditable "3" -->
<TABLE width=760 align=center>
<TBODY>
<TR>
<TD class=content>
<TABLE style="BORDER-TOP: #ffffff 8px solid" cellSpacing=0 cellPadding=2 width=678 border=0>
<TBODY>
<TR>
<TD width=672></TD></TR></TBODY></TABLE>
<TABLE class=p11 cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
<TBODY>
<TR>
<TD class=content style="WORD-WRAP: break-word">
<P>当您自以为已经了解了所有开发工具时，肯定又会冒出一个新的工具。在本文中，developerWorks 的固定撰稿人 Rick Hightower 用一个真实世界的例子向您介绍两个最激动人心的企业新技术。Hibernate 是一个对象关系映射工具，而 Spring 是一个 AOP 框架和 IOC 容器。Rick 介绍了如何结合这两者，为企业应用程序构建一个事务持久层。 </P>
<P>如果关心开发人员的最新热点，那么您可能听说过 IOC （控制倒置，Inversion of Control）容器和 AOP （面向方面编程）。不过，像许多开发人员一样，您可能不清楚在自己的开发工作中如何使用这些技术。在本文中，通过具体介绍使用 Hibernate 和 Spring 在企业应用程序中构建一个事务持久层，您会认识到这些技术。</P>
<P>Hibernate 是 Java 平台上的一种流行的、容易使用的开放源代码对象关系（OR）映射框架。Spring 是一个 AOP 框架和 IOC 容器。这两种技术一起提供了本文中介绍的开发工作的基础。将使用 Hibernate 把一些持久性对象映射到关系数据库中，用 Spring 使 Hibernate 更容易使用并提供声明性事务支持。由于为示例类编写测试代码时使用了 DbUnit，我还附带介绍了一点 TDD （测试驱动的开发）的内容。</P>
<P>注意，本文假定读者熟悉 Java 平台上的企业开发，包括 JDBC、OR 映射内容、J2EE 设计模式如 DAO，以及声明性事务支持，如 Enterprise JavaBean （EJB）技术所提供的事务支持。理解这里的讨论不需要成为这些技术的专家，也不需要熟悉 AOP、IOC 或者 TDD，因为在本文中对这三者都做了介绍。</P>
<P>我将首先介绍两种开发技术，然后分析例子。</P>
<P><STRONG>Hibernate 简介</STRONG></P>
<P>Hibernate 是 Java 平台上的一种全功能的、开放源代码 OR 映射框架。Hibernate 在许多方面类似于 EJB CMP CMR （容器管理的持久性/容器管理的关系）和 JDO（Java Data Objects）。与 JDO 不同，Hibernate 完全着眼于关系数据库的 OR 映射，并且包括比大多数商业产品更多的功能。大多数 EJB CMP CMR 解决方案使用代码生成实现持久性代码，而 JDO 使用字节码修饰。与之相反，Hibernate 使用反射和运行时字节码生成，使它对于最终用户几乎是透明的（以前 Hibernate 的实现只使用反射，它有助于调试，当前版本保留了这种选项）。</P>
<P>移植基于 Hibernate 的应用程序</P>
<P>如果应用程序必须在多个 RDBMS 系统上运行 ，那么基于 Hibernate 的应用程序可以毫不费力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和许多其他数据库。我最近甚至将一个应用程序从 MySQL 移植到 Hibernate 没有很好支持的 Firebird，而这种移植是很容易的。<BR>Hibernate 可以模拟继承（有几种方式）、关联（一对一或者一对多、containment 和 aggregation）和 composition。我将在本文中讨论每种关系类型的几个例子。</P>
<P>Hibernate 提供了一种称为 Hibernate Query Language （HQL） 的 查询语言，它类似于 JDO 的 JDOQL 和 EJB 的 EJB QL，尽管它更接近于前者。但是 Hibernate 没有就此止步：它还可以进行直接的 SQL 查询和/或使用 object criteria 很容易地在运行时构成查询条件。在本文的例子中我将只使用 HQL。</P>
<P>与 EJB CMP CMR 不同，Hibernate 像 JDO 一样可以在 J2EE 容器内部或者外部工作，这可以让那些进行 TDD 和敏捷开发的人受益。</P>
<P><STRONG>Spring 简介</STRONG></P>
<P>AOP 专家 Nicholas Lesiecki 第一次向我解释 AOP 时，他说的我一个词也没理解，我觉得就像第一次考虑使用 IOC 容器的可能性时一样。每一种技术的概念基础本身就需要很好地消化，每一种技术所使用的各种各样的缩写让事情更糟了——特别是其中许多术语与我们已经使用的根本不一样了。</P>
<P>像许多技术一样，理解这两种技术的实际使用比学习理论更容易。经过自己对 AOP 和 IOC 容器实现（即 XWork、PicoContainer 和 Spring）的分析，我发现这些技术可以帮助我获得功能，而不会在多框架中添加基于代码的依赖性。它们都将成为我后面开发项目的一部分。</P>
<P>简单地说，AOP 让开发人员可以创建非行为性的关注点，称为横切关注点，并将它们插入到应用程序代码中。使用 AOP 后，公共服务（比如日志、持久性、事务等）就可以分解成方面并应用到域对象上，同时不会增加域对象的对象模型的复杂性。</P>
<P>IOC 允许创建一个可以构造对象的应用环境，然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的，IOC 就像反过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素（singleton）和直接构造（straight construction），每一个对象都是用其协作对象构造的。因此是由容器管理协作对象（collaborator）。</P>
<P>Spring 既是一个 AOP 框架、也是一个 IOC 容器。我记得 Grady Booch 说过，对象最好的地方是可以替换它们，而 Spring 最好的地方是它有助于您替换它们。有了 Spring，只要用 JavaBean 属性和配置文件加入依赖性（协作对象）。然后可以很容易地在需要时替换具有类似接口的协作对象。</P>
<P>Spring 为 IOC 容器和 AOP 提供了很好的入口（on-ramp）。因此，不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是将要用 AOP 为示例应用程序声明式地添加事务支持，与使用 EJB 技术时的方式基本相同。</P>
<P><STRONG>具体到业务</STRONG></P>
<P>在本文的其余部分，所有的讨论都将基于一个实际的例子。起点是一个企业应用程序，要为它实现一个事务持久层。持久层是一个对象关系数据库，它包括像 User、User Group、Roles 和 ContactInfo 这些熟悉的抽象。</P>
<P>在深入到数据库的要素——查询和事务管理——之前，需要建立它的基础：对象关系映射。我将用 Hibernate 设置它，并只使用一点 Spring。</P>
<P><STRONG>用 Hibernate 进行 OR 映射</STRONG></P>
<P>Hibernate 使用 XML (*.hbm.xml) 文件将 Java 类映射到表，将 JavaBean 属性映射到数据库表。幸运的是，有一组 XDoclet 标签支持 Hibernate 开发，这使得创建所需要的 *.hbm.xml 文件更容易了。清单 1 中的代码将一个 Java 类映射到数据库表。</P>
<P>清单 1. 将 Java 类映射到 DB 表<BR>[User.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @hibernate.class table="TBL_USER"<BR>&nbsp;* ..<BR>&nbsp;* ..<BR>&nbsp;* ...<BR>&nbsp;*/<BR>public class User {<BR><BR>&nbsp;private Long id = new Long(-1);<BR>&nbsp;private String email;<BR>&nbsp;private String password;<BR>&nbsp;<BR>&nbsp;.<BR>&nbsp;.<BR>&nbsp;.<BR><BR>&nbsp;/**<BR>&nbsp; * @return<BR>&nbsp; * @hibernate.id column="PK_USER_ID"<BR>&nbsp; * unsaved-value="-1"<BR>&nbsp; * generator-class="native"&nbsp;<BR>&nbsp; */<BR>&nbsp;public Long getId() {<BR>&nbsp;&nbsp;return id;<BR>&nbsp;}<BR><BR>&nbsp;...<BR><BR>&nbsp;/**<BR>&nbsp; * @hibernate.property column="VC_EMAIL"<BR>&nbsp; * type="string"<BR>&nbsp; * update="false"<BR>&nbsp; * insert="true"<BR>&nbsp; * unique="true"<BR>&nbsp; * not-null="true"<BR>&nbsp; * length="82"<BR>&nbsp; * @return<BR>&nbsp; */<BR>&nbsp;public String getEmail() {<BR>&nbsp;&nbsp;return email;<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * @hibernate.property column="VC_PASSWORD"<BR>&nbsp; * type="string"<BR>&nbsp; * update="false"<BR>&nbsp; * insert="true"<BR>&nbsp; * unique="true"<BR>&nbsp; * not-null="true"<BR>&nbsp; * length="20"<BR>&nbsp; * @return<BR>&nbsp; */<BR>&nbsp;public String getPassword() {<BR>&nbsp;&nbsp;return password;<BR>&nbsp;}<BR><BR>&nbsp;...<BR>&nbsp;...<BR>&nbsp;...<BR>}</P>
<P>可以看到，@hibernate.class table="TBL_USER" 标签将 User 映射到 TBL_USER 表。@hibernate.property column="VC_PASSWORD" 将 JavaBean 属性 password 映射到 VC_PASSWORD 列。@hibernate.id column="PK_USER_ID" 标签声明id 属性是主键，它将使用本机（generator-class="native"）数据库机制生成键（例如，Oracle sequences 和 SQL Server Identity 键）。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主键获得策略，不过我更愿意使用 native。type 和 length 属性用于从 Hibernate *.hbm.xml OR 映射文件生成表。这些 final 属性是可选的，因为使用的可能不是 green-field 数据库。在这个例子中，已经有数据库了，所以不需要额外的属性。（green-field 应用程序 是一个新的应用程序， green-field 数据 是新应用程序的一个新数据库。不会经常开发一个全新的应用程序，不过偶尔有一两次也不错）。</P>
<P>看过了表如何映射到类以及列如何映射到 JavaBean 属性，该使用 Hibernate 在 OR 数据库中设置一些关系了。</P>
<P><STRONG>设置对象关系</STRONG></P>
<P>在本节中，我将只触及 Hibernate 提供的设置对象间关系的选项的一小部分。首先设置像 User、User Group、Roles 和 ContactInfo 这些类之间的关系。其中一些关系如图 1 所示，这是数据库的验证对象模型。</P>
<P align=center><IMG height=286 src="http://www.uml.org.cn/zjjs/images/20041223180912100.jpg" width=600><BR>图 1. 关系的图示</P>
<P>如您所见，在上述抽象中存在各种各样的关系。User 与 ContactInfo 有一对一关系。ContactInfo 的生命周期与 User 相同（用数据库的术语，UML 中的组成 aka 级联删除）。如果删除 User，则相应的 ContactInfo 也会删除。在 Users 与 Roles 之间存在多对多关系（即与独立生命周期相关联）。在 Groups 与 Users 之间存在一对多关系，因为组有许多用户。用户可以存在于组外，即是 aggregation 而不是 composition （用数据库的说法，在 Groups 和 Users 之间没有级联删除关系）。此外，User 和 Employee 有子类关系，就是说，Employee 的类型为 User。表 1 显示了如何用 XDoclet 标签创建一些不同类型的对象关系。</P>
<DIV align=center>表 1. 用 XDoclet 创建对象关系 </DIV>
<TABLE class=content cellSpacing=1 cellPadding=1 width=690 align=center bgColor=#999999 border=0>
<TBODY>
<TR bgColor=#ffffff>
<TD align=middle height=25><STRONG>关系</STRONG></TD>
<TD align=middle height=25><STRONG>Java/XDoclet</STRONG></TD>
<TD align=middle height=25><STRONG>SQL DDL（由 Hibernate Schema Export 生成的 MySQL）</STRONG></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>组包含用户</STRONG><BR>一对多<BR>Aggregation<BR>双向<BR>(Group&lt;--&gt;Users)</TD>
<TD height=25>[Group.java]<BR>/**<BR>*<BR>* @return<BR>*<BR>* @hibernate.bag name="users"<BR>* cascade="save-update"<BR>* lazy="true"<BR>* inverse="true"<BR>*<BR>* @hibernate.collection-key<BR>* column="FK_GROUP_ID"<BR>*<BR>* @hibernate.collection-one-to-many<BR>* class="net.sf.hibernateExamples.User"<BR>*/<BR>public List getUsers() {<BR>return users;<BR>} 
<P>[User.java]<BR>/**<BR>* @hibernate.many-to-one<BR>* column="FK_GROUP_ID"<BR>* class="net.sf.hibernateExamples.Group"<BR>*/<BR>public Group getGroup() {<BR>return group;<BR>}</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>) 
<P><BR>create table TBL_GROUP (<BR>PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>VC_DESCRIPTION VARCHAR(255),<BR>VC_NAME VARCHAR(40) unique,<BR>primary key (PK_GROUP_ID)<BR>)</P>
<P>alter table TBL_USER add index (FK_GROUP_ID),<BR>add constraint FK_111 foreign key (FK_GROUP_ID)<BR>references TBL_GROUP (PK_GROUP_ID)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>用户有联系信息</STRONG><BR>一对一<BR>Composition<BR>单向<BR>(User--&gt;ContactInfo)</TD>
<TD height=25>[User.java]<BR>/**<BR>* @return<BR>*<BR>* @hibernate.one-to-one cascade="all"<BR>*<BR>*/<BR>public ContactInfo getContactInfo() {<BR>return contactInfo;<BR>} 
<P>[ContactInfo.java]<BR>(Nothing to see here. Unidirectional!)</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>) 
<P>create table TBL_CONTACT_INFO (<BR>PK_CONTACT_INFO_ID BIGINT not null,<BR>...<BR>...<BR>...<BR>primary key (PK_CONTACT_INFO_ID)<BR>)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>用户与角色关联</STRONG><BR>多对多<BR>Association<BR>单向<BR>(Users--&gt;Roles)</TD>
<TD height=25>[User.java]<BR>/**<BR>* @return<BR>* @hibernate.bag<BR>* table="TBL_JOIN_USER_ROLE"<BR>* cascade="all"<BR>* inverse="true"<BR>*<BR>* @hibernate.collection-key<BR>* column="FK_USER_ID"<BR>*<BR>* @hibernate.collection-many-to-many<BR>* class="net.sf.hibernateExamples.Role"<BR>* column="FK_ROLE_ID"<BR>*<BR>*/<BR>public List getRoles() {<BR>return roles;<BR>} 
<P>[Role.java]<BR>Nothing to see here. Unidirectional!</P></TD>
<TD height=25>create table TBL_ROLE (<BR>PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>VC_DESCRIPTION VARCHAR(200),<BR>VC_NAME VARCHAR(20),<BR>primary key (PK_ROLE_ID)<BR>) 
<P>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>)</P>
<P>create table TBL_JOIN_USER_ROLE (<BR>FK_USER_ID BIGINT not null,<BR>FK_ROLE_ID BIGINT not null<BR>)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>雇员是用户</STRONG><BR>Inheritance<BR>用户<BR>雇员</TD>
<TD height=25>[User.java]<BR>/**<BR>* @hibernate.class table="TBL_USER"<BR>* discriminator-value="2"<BR>* @hibernate.discriminator column="USER_TYPE"<BR>*<BR>...<BR>...<BR>...<BR>*/<BR>public class User { 
<P>[Employee.java]<BR>/**<BR>* @hibernate.subclass discriminator-value = "1"<BR>*/<BR>public class Employee extends User{</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>)</TD></TR></TBODY></TABLE>
<P><STRONG>Hibernate 中的查询</STRONG></P>
<P>Hibernate 有三种类型的查询：</P>☆ Criteria, object composition<BR>☆ SQL<BR>☆ HQL 
<P>在下面的例子中将只使用 HQL。本节还要使用 Spring，用它的 AOP-driven HibernateTemplate 简化 Hibernate 会话的处理。在本节将开发一个 DAO（Data Access Object）。要了解更多关于 DAO 的内容，请参阅 参考资料。</P>
<P>清单 2 展示了两个方法：一个使用 HQL 查询的组查询，另一个是后面接一个操作的组查询。注意在第二个方法中，Spring HibernateTemplate 是如何简化会话管理的。</P>
<P>清单 2. 使用查询</P>
<P style="BACKGROUND: #eeeeee">import net.sf.hibernate.HibernateException;<BR>import net.sf.hibernate.Session;<BR>import net.sf.hibernate.Query;<BR>import org.springframework.orm.hibernate.HibernateCallback;<BR>import org.springframework.orm.hibernate.support.HibernateDaoSupport;<BR><BR>/**<BR>&nbsp;* @author Richard Hightower<BR>&nbsp;* ArcMind Inc. http://www.arc-mind.com<BR>&nbsp;*/<BR>public class UserDAO extends HibernateDaoSupport{<BR><BR>&nbsp; .<BR>&nbsp; .<BR>&nbsp; .<BR><BR>&nbsp; /**<BR>&nbsp;* Demonstrates looking up a group with a HQL query<BR>&nbsp;* @param email<BR>&nbsp;* @return<BR>&nbsp;*/&nbsp;<BR>&nbsp;public Group findGroupByName(String name) {<BR>&nbsp;&nbsp;&nbsp; return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);<BR>&nbsp;}<BR>&nbsp;<BR>&nbsp;/**<BR>&nbsp; * Demonstrates looking up a group and forcing it to populate users (relationship was lazy)<BR>&nbsp; * @param email<BR>&nbsp; * @return<BR>&nbsp; */&nbsp;<BR>&nbsp;public Group findPopulatedGroupByName(final String name) {<BR>&nbsp;&nbsp;&nbsp; HibernateCallback callback = new HibernateCallback(){<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp; public Object doInHibernate(Session session) throws HibernateException, SQLException {<BR>&nbsp;&nbsp;&nbsp;Group group =null;<BR>&nbsp;&nbsp;&nbsp;String query = "from Group g where g.name=?";<BR>&nbsp;&nbsp;&nbsp;Query queryObject = getHibernateTemplate().createQuery(session, query);<BR>&nbsp;&nbsp;&nbsp;queryObject.setParameter(0, name);<BR>&nbsp;&nbsp;&nbsp;group = (Group) queryObject.list().get(0);<BR>&nbsp;&nbsp;&nbsp;group.getUsers().size();//force load<BR>&nbsp;&nbsp;&nbsp;return group;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;};<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;return (Group) getHibernateTemplate().execute(callback);<BR>&nbsp;}<BR>&nbsp; .<BR>&nbsp; .<BR>&nbsp; .<BR>}&nbsp;&nbsp;&nbsp;</P>
<P>您可能会注意到第二个方法比第一个方法复杂得多，因为它强迫加载 users 集合。因为 Group-&gt;Users 之间的关系设置为 lazy initialize（即表 2 中 lazy="true"），组对象需要一个活跃的会话以查询用户。在定义 Group 和 Users 之间关系时设置这个属性为 lazy="false"，则不需要第二个方法。在这种情况下，可能使用第一种方法 (findGroupByName) 列出组，用第二种方法（findPopulatedGroupByName）查看组细节。</P>
<P><STRONG>Spring IOC 和 Hibernate</STRONG></P>
<P>使用 Spring 时，在 J2EE 容器内和容器外工作一样容易。比如在最近的项目中，我在 Eclipse 中，使用 HSQL 和本地数据库对使用 Hibernate 事务管理器的 Hypersonic SQL 数据库进行持久性单元测试。然后，在部署到 J2EE 服务器时，将持久层转换为使用 J2EE 数据源（通过 JNDI）、JTA 事务和使用 FireBird （一个开放源代码版本的 Interbase）。这是用 Spring 作为 IOC 容器完成的。</P>
<P>从清单 3 中可以看出，Spring 允许加入依赖性。注意清单中应用程序上下文文件是如何配置 dataSource 的。dataSource 传递给 sessionFactory，sessionFactory 传递给 UserDAO。</P>
<P>清单 3. Spring IOC 和 Hibernate</P>
<P style="BACKGROUND: #eeeeee">&lt;beans&gt;<BR><BR>&nbsp;&lt;!-- Datasource that works in any application server<BR>&nbsp;&nbsp;You could easily use J2EE data source instead if this were<BR>&nbsp;&nbsp;running inside of a J2EE container.<BR>&nbsp; --&gt;<BR>&nbsp;&lt;bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"&gt;<BR>&nbsp;&nbsp;&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/mysql&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="password"&gt;&lt;value&gt;&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR><BR>&nbsp;&lt;!-- Hibernate SessionFactory --&gt;<BR>&nbsp;&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean"&gt;<BR>&nbsp;&nbsp;&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;&lt;!-- Must references all OR mapping files. --&gt;<BR>&nbsp;&nbsp;&lt;property name="mappingResources"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;list&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/User.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/Group.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/Role.hbm.xml&lt;/value&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/ContactInfo.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;/list&gt;<BR>&nbsp;&nbsp;&lt;/property&gt;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;&lt;!-- Set the type of database; changing this one property will port this to Oracle,<BR>&nbsp;&nbsp;&nbsp; MS SQL etc. --&gt;<BR>&nbsp;&nbsp;&lt;property name="hibernateProperties"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="hibernate.dialect"&gt;net.sf.hibernate.dialect.MySQLDialect&lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;/props&gt;<BR>&nbsp;&nbsp;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR>&nbsp;<BR>&nbsp;&lt;!-- Pass the session factory to our UserDAO --&gt;<BR>&nbsp;&lt;bean id="userDAO" class="net.sf.hibernateExamples.UserDAO"&gt;<BR>&nbsp;&nbsp;&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR>&nbsp;<BR>&lt;/beans&gt;&nbsp;&nbsp;&nbsp;</P>
<P>设置了 UserDAO 后，下一步就是定义并使用更多的查询以展示可以完成的操作。Hibernate 可以用预定义查询将查询存储到源代码之外，如清单 4 所示。</P>
<P>清单 4. 预定义查询<BR>[User.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @author Richard Hightower<BR>&nbsp;* ArcMind Inc. http://www.arc-mind.com<BR>&nbsp;* @hibernate.class table="TBL_USER" discriminator-value="2"<BR>&nbsp;* @hibernate.discriminator column="USER_TYPE"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="AllUsers" query="from User user order by user.email asc"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="OverheadStaff"<BR>&nbsp;* query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="CriticalStaff"<BR>&nbsp;* query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="GetUsersInAGroup"<BR>&nbsp;* query="select user from Group g join g.users user"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="GetUsersNotInAGroup"<BR>&nbsp;* query="select user from User user where user.group is null"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="UsersBySalaryGreaterThan"<BR>&nbsp;* query="from User user inner join user.contactInfo info where info.salary &gt; ?1"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="UsersBySalaryBetween"<BR>&nbsp;* query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="UsersByLastNameLike"<BR>&nbsp;* query="from User user join user.contactInfo info where info.lastName like ?1"<BR>&nbsp;*<BR>&nbsp;* @hibernate.query name="GetEmailsOfUsers"<BR>&nbsp;* query="select user.email from Group g join g.users as user where g.name = ?1"<BR>&nbsp;*<BR>&nbsp;*/<BR>public class User {<BR>&nbsp;&nbsp;&nbsp;.<BR>&nbsp;&nbsp;&nbsp;.<BR>&nbsp;&nbsp;&nbsp;.&nbsp;&nbsp;&nbsp;</P>
<P>上述代码定义了几个预定义查询。预定义查询 是存储在 *.hbm.xml 文件中的查询。在清单 5 中，可以看到如何执行预定义查询。</P>
<P>清单 5. 使用预定义查询<BR>[UserDAO.java]</P>
<P style="BACKGROUND: #eeeeee">&nbsp;/**<BR>&nbsp; * Demonstrates a query that returns a String.<BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;public String[] getUserEmailsInGroup(String groupName){<BR>&nbsp;&nbsp;List emailList =<BR>&nbsp;&nbsp;getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers");<BR>&nbsp;&nbsp;return (String [])<BR>&nbsp;&nbsp;&nbsp;emailList.toArray(new String[emailList.size()]);<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * Demonstrates a query that returns a list of Users<BR>&nbsp; *<BR>&nbsp; * @return A list of emails of all of the users in the authentication system.<BR>&nbsp; *<BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;public List getUsers(){<BR>&nbsp;&nbsp;return getHibernateTemplate().findByNamedQuery("AllUsers");<BR>&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /**<BR>&nbsp;* Demonstrates passing a single argument to a query.<BR>&nbsp;*<BR>&nbsp;* @return A list of UserValue objects.<BR>&nbsp;*<BR>&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;public List getUsersBySalary(float salary){<BR>&nbsp;&nbsp;&nbsp; return getHibernateTemplate()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .findByNamedQuery("UsersBySalaryGreaterThan",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new Float(salary));<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * Demonstrates passing multiple arguments to a query<BR>&nbsp; *<BR>&nbsp; * @return A list of UserValue objects.<BR>&nbsp; *<BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;public List getUsersBySalaryRange(float start, float stop){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return getHibernateTemplate()<BR>&nbsp;&nbsp; .findByNamedQuery("UsersBySalaryBetween",<BR>&nbsp;&nbsp;&nbsp;new Object[] {new Float(start), new Float(stop)});<BR>&nbsp;}</P>
<P>查询进行时，可以在持久层中加上最后一层：使用 Spring 的事务管理。</P>
<P><STRONG>用 Spring 管理事务</STRONG></P>
<P>Spring 可以声明式地管理事务。例如，UserDAO.addUser 方法当前不是在单个事务中执行的。因此，组中的每一个用户都插入到自己的事务中，如清单 6 所示。</P>
<P>清单 6. 添加一组用户<BR>[UserDAO.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @param group<BR>&nbsp;*/<BR>public void addGroup(Group group) {<BR>&nbsp;getHibernateTemplate().save(group);<BR>&nbsp;<BR>}</P>
<P>[UserDAOTest.java]</P>
<P style="BACKGROUND: #eeeeee">public void testAddGroupOfUsers(){<BR>&nbsp;Group group = new Group();<BR>&nbsp;<BR>&nbsp;for (int index=0; index &lt; 10; index++){<BR>&nbsp;&nbsp;User user = new User();<BR>&nbsp;&nbsp;user.setEmail("rick"+index+"@foobar.com" );<BR>&nbsp;&nbsp;user.setPassword("foobar");<BR>&nbsp;&nbsp;group.addUser(user);&nbsp;<BR>&nbsp;}<BR>&nbsp;<BR>&nbsp;group.setName("testGroup");<BR>&nbsp;<BR>&nbsp;userDAO.addGroup(group);<BR>&nbsp;assertNotNull(group.getId());<BR>&nbsp;<BR>&nbsp;Group group2 = userDAO.findPopulatedGroupByName("testGroup");<BR>&nbsp;<BR>&nbsp;assertEquals("testGroup",group2.getName());<BR>&nbsp;assertEquals(10, group2.getUsers().size());<BR>&nbsp;String email = ((User)group2.getUsers().get(0)).getEmail();<BR>&nbsp;assertEquals("rick0@foobar.com", email);<BR><BR>}</P>
<P>不建议使用上述解决方案，因为每一个 User 都要在自己的事务中插入到数据库中。如果出现问题，那么只能添加部分用户。如果希望保留 ACID 属性（即保证所有都发生或者所有都不发生），可以通过程序进行事务管理，但是它很快就会变得一团糟了。相反，应使用 Spring 的 AOP 来支持声明式的事务，如清单 7 所示。</P>
<P>清单 7. 声明式管理事务<BR>[applicationContext.xml]</P>
<P style="BACKGROUND: #eeeeee">&lt;!-- Pass the session factory to our UserDAO --&gt;<BR>&nbsp;&nbsp; &lt;bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl"&gt;<BR>&nbsp;&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp; &lt;/bean&gt;<BR>&nbsp;<BR>&nbsp;&nbsp; &lt;bean id="transactionManager"<BR>&nbsp;&nbsp; &nbsp;class="org.springframework.orm.hibernate.HibernateTransactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sessionFactory"&gt;&lt;ref bean="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp; &lt;/bean&gt;<BR><BR>&lt;bean id="userDAO"<BR>&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&lt;property name="transactionManager"&gt;&lt;ref local="transactionManager"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;property name="target"&gt;&lt;ref local="userDAOTarget"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;prop key="addGroup"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;<BR>&nbsp;&nbsp;&lt;/props&gt;<BR>&nbsp;&lt;/property&gt;<BR>&lt;/bean&gt;</P>
<P>注意在准备清单 7 的代码时，我重新改写了 UserDAO 并提取了其接口。这个接口现在是 UserDAO，它的实现类是 UserDAOImpl。这样清单 7 中的事务代码就使用了带有事务属性 (PROPAGATION_REQUIRED) 的 UserDAO.addGroup() 方法。现在只要底层数据库支持，就可以在一个事务中添加所有用户。</P>
<P><STRONG>结束语</STRONG></P>
<P>在本文中，介绍了如何使用 Hibernate 和 Spring 实现一个事务持久层。Hibernate 是一种先进的 OR 映射工具，而 Spring 是一个 AOP 框架和 IOC 容器。这两种技术的综合使用，使得开发人员可以编写媲美数据库厂商的代码，它可以在 J2EE 容器中运行，也可以单独运行。使用了 DbUnit （JUnit 的扩展）构建和测试本文中例子的所有代码，虽然这不是讨论的重点。</P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.cnitblog.com/dahutu/aggbug/3646.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-10-28 16:38 <a href="http://www.cnitblog.com/dahutu/archive/2005/10/28/3646.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA异常处理的陋习展播（转载）</title><link>http://www.cnitblog.com/dahutu/archive/2005/10/26/3567.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Wed, 26 Oct 2005 01:20:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/10/26/3567.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3567.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/10/26/3567.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3567.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3567.html</trackback:ping><description><![CDATA[<P>　　你觉得自己是一个Java专家吗？是否肯定自己已经全面掌握了Java的异常处理机制？在下面这段代码中，你能够迅速找出异常处理的六个问题吗？ </P>
<P>　　1 OutputStreamWriter out = ... </P>
<P>　　2 java.sql.Connection conn = ... </P>
<P>　　3 try { // ⑸ </P>
<P>　　4 　Statement stat = conn.createStatement(); </P>
<P>　　5 　ResultSet rs = stat.executeQuery( </P>
<P>　　6 　　"select uid, name from user"); </P>
<P>　　7 　while (rs.next()) </P>
<P>　　8 　{ </P>
<P>　　9 　　out.println("ID：" + rs.getString("uid") // ⑹ </P>
<P>　　10 　　　"，姓名：" + rs.getString("name")); </P>
<P>　　11 　} </P>
<P>　　12 　conn.close(); // ⑶ </P>
<P>　　13 　out.close(); </P>
<P>　　14 } </P>
<P>　　15 catch(Exception ex) // ⑵ </P>
<P>　　16 { </P>
<P>　　17 　ex.printStackTrace(); //⑴，⑷ </P>
<P>　　18 } </P>
<P>　　作为一个Java程序员，你至少应该能够找出两个问题。但是，如果你不能找出全部六个问题，请继续阅读本文。 </P>
<P>　　本文讨论的不是Java异常处理的一般性原则，因为这些原则已经被大多数人熟知。我们要做的是分析各种可称为“反例”（anti-pattern）的违背优秀编码规范的常见坏习惯，帮助读者熟悉这些典型的反面例子，从而能够在实际工作中敏锐地察觉和避免这些问题。 </P>
<P>　　反例之一：丢弃异常 </P>
<P>　　代码：15行-18行。 </P>
<P>　　这段代码捕获了异常却不作任何处理，可以算得上Java编程中的杀手。从问题出现的频繁程度和祸害程度来看，它也许可以和C/C++程序的一个恶名远播的问题相提并论??不检查缓冲区是否已满。如果你看到了这种丢弃（而不是抛出）异常的情况，可以百分之九十九地肯定代码存在问题（在极少数情况下，这段代码有存在的理由，但最好加上完整的注释，以免引起别人误解）。 </P>
<P>　　这段代码的错误在于，异常（几乎）总是意味着某些事情不对劲了，或者说至少发生了某些不寻常的事情，我们不应该对程序发出的求救信号保持沉默和无动于衷。调用一下printStackTrace算不上“处理异常”。不错，调用printStackTrace对调试程序有帮助，但程序调试阶段结束之后，printStackTrace就不应再在异常处理模块中担负主要责任了。 </P>
<P>　　丢弃异常的情形非常普遍。打开JDK的ThreadDeath类的文档，可以看到下面这段说明：“特别地，虽然出现ThreadDeath是一种‘正常的情形’，但ThreadDeath类是Error而不是Exception的子类，因为许多应用会捕获所有的Exception然后丢弃它不再理睬。”这段话的意思是，虽然ThreadDeath代表的是一种普通的问题，但鉴于许多应用会试图捕获所有异常然后不予以适当的处理，所以JDK把ThreadDeath定义成了Error的子类，因为Error类代表的是一般的应用不应该去捕获的严重问题。可见，丢弃异常这一坏习惯是如此常见，它甚至已经影响到了Java本身的设计。 </P>
<P>　　那么，应该怎样改正呢？主要有四个选择： </P>
<P>　　1、处理异常。针对该异常采取一些行动，例如修正问题、提醒某个人或进行其他一些处理，要根据具体的情形确定应该采取的动作。再次说明，调用printStackTrace算不上已经“处理好了异常”。 </P>
<P>　　2、重新抛出异常。处理异常的代码在分析异常之后，认为自己不能处理它，重新抛出异常也不失为一种选择。 </P>
<P>　　3、把该异常转换成另一种异常。大多数情况下，这是指把一个低级的异常转换成应用级的异常（其含义更容易被用户了解的异常）。 </P>
<P>　　4、不要捕获异常。 </P>
<P>　　结论一：既然捕获了异常，就要对它进行适当的处理。不要捕获异常之后又把它丢弃，不予理睬。 </P>
<P>　　反例之二：不指定具体的异常 </P>
<P>　　代码：15行。 </P>
<P>　　许多时候人们会被这样一种“美妙的”想法吸引：用一个catch语句捕获所有的异常。最常见的情形就是使用catch(Exception ex)语句。但实际上，在绝大多数情况下，这种做法不值得提倡。为什么呢？ </P>
<P>　　要理解其原因，我们必须回顾一下catch语句的用途。catch语句表示我们预期会出现某种异常，而且希望能够处理该异常。异常类的作用就是告诉Java编译器我们想要处理的是哪一种异常。由于绝大多数异常都直接或间接从java.lang.Exception派生，catch(Exception ex)就相当于说我们想要处理几乎所有的异常。 </P>
<P>　　再来看看前面的代码例子。我们真正想要捕获的异常是什么呢？最明显的一个是SQLException，这是JDBC操作中常见的异常。另一个可能的异常是IOException，因为它要操作OutputStreamWriter。显然，在同一个catch块中处理这两种截然不同的异常是不合适的。如果用两个catch块分别捕获SQLException和IOException就要好多了。这就是说，catch语句应当尽量指定具体的异常类型，而不应该指定涵盖范围太广的Exception类。 </P>
<P>　　另一方面，除了这两个特定的异常，还有其他许多异常也可能出现。例如，如果由于某种原因，executeQuery返回了null，该怎么办？答案是让它们继续抛出，即不必捕获也不必处理。实际上，我们不能也不应该去捕获可能出现的所有异常，程序的其他地方还有捕获异常的机会??直至最后由JVM处理。 </P>
<P>　　结论二：在catch语句中尽可能指定具体的异常类型，必要时使用多个catch。不要试图处理所有可能出现的异常。 </P>
<P>　　反例之三：占用资源不释放 </P>
<P>　　代码：3行-14行。 </P>
<P>　　异常改变了程序正常的执行流程。这个道理虽然简单，却常常被人们忽视。如果程序用到了文件、Socket、JDBC连接之类的资源，即使遇到了异常，也要正确释放占用的资源。为此，Java提供了一个简化这类操作的关键词finally。 </P>
<P>　　finally是样好东西：不管是否出现了异常，Finally保证在try/catch/finally块结束之前，执行清理任务的代码总是有机会执行。遗憾的是有些人却不习惯使用finally。 </P>
<P>　　当然，编写finally块应当多加小心，特别是要注意在finally块之内抛出的异常??这是执行清理任务的最后机会，尽量不要再有难以处理的错误。 </P>
<P>　　结论三：保证所有资源都被正确释放。充分运用finally关键词。</P>
<P>　　反例之四：不说明异常的详细信息 </P>
<P>　　代码：3行-18行。 </P>
<P>　　仔细观察这段代码：如果循环内部出现了异常，会发生什么事情？我们可以得到足够的信息判断循环内部出错的原因吗？不能。我们只能知道当前正在处理的类发生了某种错误，但却不能获得任何信息判断导致当前错误的原因。 </P>
<P>　　printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程，但只提供了一些最基本的信息，未能说明实际导致错误的原因，同时也不易解读。 </P>
<P>　　因此，在出现异常时，最好能够提供一些文字信息，例如当前正在执行的类、方法和其他状态信息，包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。 </P>
<P>　　结论四：在异常处理模块中提供适量的错误原因信息，组织错误信息使其易于理解和阅读。 </P>
<P>　　反例之五：过于庞大的try块 </P>
<P>　　代码：3行-14行。 </P>
<P>　　经常可以看到有人把大量的代码放入单个try块，实际上这不是好习惯。这种现象之所以常见，原因就在于有些人图省事，不愿花时间分析一大块代码中哪几行代码会抛出异常、异常的具体类型是什么。把大量的语句装入单个巨大的try块就象是出门旅游时把所有日常用品塞入一个大箱子，虽然东西是带上了，但要找出来可不容易。 </P>
<P>　　一些新手常常把大量的代码放入单个try块，然后再在catch语句中声明Exception，而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难，因为一大段代码中有太多的地方可能抛出Exception。 </P>
<P>　　结论五：尽量减小try块的体积。 </P>
<P>　　反例之六：输出数据不完整 </P>
<P>　　代码：7行-11行。 </P>
<P>　　不完整的数据是Java程序的隐形杀手。仔细观察这段代码，考虑一下如果循环的中间抛出了异常，会发生什么事情。循环的执行当然是要被打断的，其次，catch块会执行??就这些，再也没有其他动作了。已经输出的数据怎么办？使用这些数据的人或设备将收到一份不完整的（因而也是错误的）数据，却得不到任何有关这份数据是否完整的提示。对于有些系统来说，数据不完整可能比系统停止运行带来更大的损失。 </P>
<P>　　较为理想的处置办法是向输出设备写一些信息，声明数据的不完整性；另一种可能有效的办法是，先缓冲要输出的数据，准备好全部数据之后再一次性输出。 </P>
<P>　　结论六：全面考虑可能出现的异常以及这些异常对执行流程的影响。 </P>
<P>　　改写后的代码 </P>
<P>　　根据上面的讨论，下面给出改写后的代码。也许有人会说它稍微有点?嗦，但是它有了比较完备的异常处理机制。 </P>
<P>　　OutputStreamWriter out = ... </P>
<P>　　java.sql.Connection conn = ... </P>
<P>　　try { </P>
<P>　　　Statement stat = conn.createStatement(); </P>
<P>　　　ResultSet rs = stat.executeQuery( </P>
<P>　　　　"select uid, name from user"); </P>
<P>　　　while (rs.next()) </P>
<P>　　　{ </P>
<P>　　　　out.println("ID：" + rs.getString("uid") + "，姓名: " + rs.getString("name")); </P>
<P>　　　} </P>
<P>　　} </P>
<P>　　catch(SQLException sqlex) </P>
<P>　　{ </P>
<P>　　　out.println("警告：数据不完整"); </P>
<P>　　　throw new ApplicationException("读取数据时出现SQL错误", sqlex); </P>
<P>　　} </P>
<P>　　catch(IOException ioex) </P>
<P>　　{ </P>
<P>　　　throw new ApplicationException("写入数据时出现IO错误", ioex); </P>
<P>　　} </P>
<P>　　finally </P>
<P>　　{ </P>
<P>　　　if (conn != null) { </P>
<P>　　　　try { </P>
<P>　　　　　conn.close(); </P>
<P>　　　　} </P>
<P>　　　　catch(SQLException sqlex2) </P>
<P>　　　　{ </P>
<P>　　　　　System.err(this.getClass().getName() + ".mymethod - 不能关闭数据库连接: " + sqlex2.toString()); </P>
<P>　　　　} </P>
<P>　　　} </P>
<P>　　 </P>
<P>　　　if (out != null) { </P>
<P>　　　　try { </P>
<P>　　　　　out.close(); </P>
<P>　　　　} </P>
<P>　　　　catch(IOException ioex2) </P>
<P>　　　　{ </P>
<P>　　　　　System.err(this.getClass().getName() + ".mymethod - 不能关闭输出文件" + ioex2.toString()); </P>
<P>　　　　} </P>
<P>　　　} </P>
<P>　　} </P>
<P>　　本文的结论不是放之四海皆准的教条，有时常识和经验才是最好的老师。如果你对自己的做法没有百分之百的信心，务必加上详细、全面的注释。 </P>
<P>　　另一方面，不要笑话这些错误，不妨问问你自己是否真地彻底摆脱了这些坏习惯。即使最有经验的程序员偶尔也会误入歧途，原因很简单，因为它们确确实实带来了“方便”。所有这些反例都可以看作Java编程世界的恶魔，它们美丽动人，无孔不入，时刻诱惑着你。也许有人会认为这些都属于鸡皮蒜毛的小事，不足挂齿，但请记住：勿以恶小而为之，勿以善小而不为。 </P></FONT><img src ="http://www.cnitblog.com/dahutu/aggbug/3567.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-10-26 09:20 <a href="http://www.cnitblog.com/dahutu/archive/2005/10/26/3567.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ANT Build.xml例子</title><link>http://www.cnitblog.com/dahutu/archive/2005/10/24/3525.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Mon, 24 Oct 2005 05:03:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/10/24/3525.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/3525.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/10/24/3525.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/3525.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/3525.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="95%" align=center border=0>
<TBODY>
<TR>
<TD>
<P>&lt;?xml version="1.0"&nbsp; encoding="GB2312" ?&gt;<BR>&lt;!--<BR>&nbsp;&nbsp;&nbsp; =======================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hello-ant 项目 ,学习ant工具的build file.</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 参照ant的jakarta-ant-1.6alpha的build.xml</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Copyright (c) 2002 The Neusoft Software Foundation.&nbsp; All rights<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reserved.</P>
<P>&nbsp;&nbsp;&nbsp; =======================================================================<BR>--&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp; 文档结构为:<BR>&nbsp;&nbsp;&nbsp; &lt;project&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property/&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 全局变量的定义<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property/&gt;...</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;target name="1"&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 任务组(tasks)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;javac&gt;&lt;/javac&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一项javac任务<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;oneTask&gt;&lt;/ontTask&gt;&nbsp;&nbsp; 一项其它任务<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/target&gt;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;target name="2"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;javac&gt;&lt;/javac&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;oneTask&gt;&lt;/ontTask&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/target&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/project&gt;</P>
<P>&nbsp;&nbsp;&nbsp; project代表一个项目，<BR>&nbsp;&nbsp;&nbsp; default:运行到名称为"dist"的target(任务组)<BR>&nbsp;&nbsp;&nbsp; basedir:基准路径。<BR>--&gt;<BR>&lt;project default="dist" basedir="."&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 定义属性（property tasks）<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最好把用到的路径呀，名称呀都在这里定义成全局变量<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例：定义<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="a" value="hello"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 以后就可以这样用它：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="b" value="${a}/b"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 现在:b=="hello/b"<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--主要的系统环境属性--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property environment="env"/&gt;&lt;!--取window,unix...的环境变量--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="java.home" value="${env.JAVA_HOME}"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="ant.home"&nbsp; value="${env.ANT_HOME}"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--主要的app环境属性--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="app.name"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="hello-ant"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="app.jar"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="${app.name}.jar"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="app.copyright" value=" Copyright (c) 2002 The Neusoft Software Foundation.&nbsp; All rights reserved."/&gt;</P>
<P><BR>&nbsp;&nbsp;&nbsp; &lt;!--app中src的属性--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="src.dir"&nbsp;&nbsp;&nbsp; value="src" /&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="src.main"&nbsp;&nbsp; value="${src.dir}/main"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="src.script" value="${src.dir}/script"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--app用到的lib--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="lib.dir" value="lib"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--app的build目录中--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="build.dir"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="build" /&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="build.classes"&nbsp; value="${build.dir}/classes"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="build.docs"&nbsp;&nbsp;&nbsp;&nbsp; value="${build.dir}/docs"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="build.docs.api" value="${build.docs}/api"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="build.lib"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="${build.dir}/lib"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--app的dist (distribution) 目录中--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="dist.dir"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="dist"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="dist.bin"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="${dist.dir}/bin"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="dist.docs"&nbsp;&nbsp;&nbsp;&nbsp; value="${dist.dir}/docs"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="dist.lib"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="${dist.dir}/lib"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--app的docs目录中--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="docs.dir"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value="docs"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;!--<BR>&nbsp;&nbsp;&nbsp; 定义一组路径以后可以通过id重用这组路径 ，例：<BR>&nbsp;&nbsp;&nbsp; &lt;javac srcdir="src/main" destdir="build/classes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;classpath refid="classpath"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/javac&gt;<BR>&nbsp;&nbsp;&nbsp; --&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;path id="classpath"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--本项目只有一个java，用不上classpath，这里只是做个例子--&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;pathelement location="${build.classes}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;pathelement path="${java.home}/lib/tools.jar"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/path&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init 准备目录(File Tasks)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主要的目录结构通常是不会变的，一起生成他们<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;target name="init"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--清除以前目录--&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;delete dir="${build.dir}" failonerror="false" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;delete dir="${dist.dir}"&nbsp; failonerror="false"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--准备目录--&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${build.dir}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${build.classes}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${build.docs}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${build.docs.api}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${build.lib}"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${dist.dir}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${dist.bin}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mkdir dir="${dist.lib}"/&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;/target&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Build the code (Compile Tasks,File Tasks)<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;target name="build" depends="init"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--编译--&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;javac srcdir="${src.main}" destdir="${build.classes}"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;classpath refid="classpath"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/javac&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/target&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 打包文档(Archive Tasks)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Create the project jars: xxx1.jar and xxx2.jar<BR>&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;<BR>&nbsp;&nbsp; &lt;target name="jars" depends="build"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;jar basedir="${build.classes}" jarfile="${build.lib}/${app.jar}"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/target&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Creates the API documentation<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;target name="javadocs"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; depends="jars"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; description="--&gt; creates the API documentation"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--copy docs 手册... --&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;copy todir="${build.docs}"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;fileset dir="${docs.dir}"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/copy&gt;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;javadoc packagenames="hello.ant.*"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sourcepath="${src.main}"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; defaultexcludes="yes"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; destdir="${build.docs.api}"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; author="true"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; version="true"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; use="true"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; windowtitle="Docs API"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;doctitle&gt;&lt;![CDATA[&lt;h1&gt;hello ant Docs API&lt;/h1&gt;]]&gt;&lt;/doctitle&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bottom&gt;&lt;![CDATA[&lt;i&gt;${app.copyright}&lt;/i&gt;]]&gt;&lt;/bottom&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;tag name="todo" scope="all" description="To do:" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/javadoc&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/target&gt;</P>
<P>&lt;!--<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Create the distribution that can run (Archive Tasks)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主要是从各目录中把该copy的copy上<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;<BR>&nbsp;&nbsp; &lt;target name="dist" depends="javadocs"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!--copy bin 执行文件 --&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;copy todir="${dist.bin}"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;fileset dir="${src.script}/"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/copy&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;copy todir="${dist.docs}"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;fileset dir="${build.docs}/"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/copy&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!-- copy lib 文件 --&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;copy todir="${dist.lib}"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;fileset dir="${build.lib}/"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/copy&gt;</P>
<P>&nbsp;&nbsp;&nbsp; &lt;/target&gt;<BR>&lt;!--<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cleans everything(File Tasks)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例如可以删除build中的文件，留给你发挥吧<BR>&nbsp;&nbsp;&nbsp;&nbsp; ===================================================================<BR>--&gt;</P>
<P>&lt;/project&gt;</P></TD></TR></TBODY></TABLE><img src ="http://www.cnitblog.com/dahutu/aggbug/3525.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-10-24 13:03 <a href="http://www.cnitblog.com/dahutu/archive/2005/10/24/3525.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]初学Guitar</title><link>http://www.cnitblog.com/dahutu/archive/2005/07/06/786.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Wed, 06 Jul 2005 13:56:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/07/06/786.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/786.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/07/06/786.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/786.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/786.html</trackback:ping><description><![CDATA[努力ing...<img src ="http://blog.csdn.net/cnxiaowei/aggbug/369831.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.csdn.net/cnxiaowei/archive/2005/05/01/369831.aspx'>http://blog.csdn.net/cnxiaowei/archive/2005/05/01/369831.aspx</a><img src ="http://www.cnitblog.com/dahutu/aggbug/786.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-07-06 21:56 <a href="http://www.cnitblog.com/dahutu/archive/2005/07/06/786.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]抵制日货游行</title><link>http://www.cnitblog.com/dahutu/archive/2005/07/06/787.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Wed, 06 Jul 2005 13:56:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/07/06/787.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/787.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/07/06/787.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/787.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/787.html</trackback:ping><description><![CDATA[抗日！<img src ="http://blog.csdn.net/cnxiaowei/aggbug/352036.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.csdn.net/cnxiaowei/archive/2005/04/18/352036.aspx'>http://blog.csdn.net/cnxiaowei/archive/2005/04/18/352036.aspx</a><img src ="http://www.cnitblog.com/dahutu/aggbug/787.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-07-06 21:56 <a href="http://www.cnitblog.com/dahutu/archive/2005/07/06/787.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]用VC实现发送和接受短信</title><link>http://www.cnitblog.com/dahutu/archive/2005/07/06/788.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Wed, 06 Jul 2005 13:56:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/07/06/788.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/788.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/07/06/788.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/788.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/788.html</trackback:ping><description><![CDATA[用VC实现发送短信程序<img src ="http://blog.csdn.net/cnxiaowei/aggbug/346703.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.csdn.net/cnxiaowei/archive/2005/04/14/346703.aspx'>http://blog.csdn.net/cnxiaowei/archive/2005/04/14/346703.aspx</a><img src ="http://www.cnitblog.com/dahutu/aggbug/788.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-07-06 21:56 <a href="http://www.cnitblog.com/dahutu/archive/2005/07/06/788.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]好几天没学习了......</title><link>http://www.cnitblog.com/dahutu/archive/2005/07/06/789.html</link><dc:creator>玩人的程序</dc:creator><author>玩人的程序</author><pubDate>Wed, 06 Jul 2005 13:56:00 GMT</pubDate><guid>http://www.cnitblog.com/dahutu/archive/2005/07/06/789.html</guid><wfw:comment>http://www.cnitblog.com/dahutu/comments/789.html</wfw:comment><comments>http://www.cnitblog.com/dahutu/archive/2005/07/06/789.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/dahutu/comments/commentRss/789.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/dahutu/services/trackbacks/789.html</trackback:ping><description><![CDATA[...<img src ="http://blog.csdn.net/cnxiaowei/aggbug/346685.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.csdn.net/cnxiaowei/archive/2005/04/14/346685.aspx'>http://blog.csdn.net/cnxiaowei/archive/2005/04/14/346685.aspx</a><img src ="http://www.cnitblog.com/dahutu/aggbug/789.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/dahutu/" target="_blank">玩人的程序</a> 2005-07-06 21:56 <a href="http://www.cnitblog.com/dahutu/archive/2005/07/06/789.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>