﻿<?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>BlogJava-我的小船停在代码的海洋中...-随笔分类-学习</title><link>http://www.blogjava.net/machilansing/category/13688.html</link><description>Lansing--Coding 不是梦
</description><language>zh-cn</language><lastBuildDate>Fri, 13 Jul 2007 09:57:36 GMT</lastBuildDate><pubDate>Fri, 13 Jul 2007 09:57:36 GMT</pubDate><ttl>60</ttl><item><title>关于Linux下的Inode</title><link>http://www.blogjava.net/machilansing/archive/2007/07/13/Inode.html</link><dc:creator>Lansing</dc:creator><author>Lansing</author><pubDate>Fri, 13 Jul 2007 01:54:00 GMT</pubDate><guid>http://www.blogjava.net/machilansing/archive/2007/07/13/Inode.html</guid><wfw:comment>http://www.blogjava.net/machilansing/comments/130004.html</wfw:comment><comments>http://www.blogjava.net/machilansing/archive/2007/07/13/Inode.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/machilansing/comments/commentRss/130004.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/machilansing/services/trackbacks/130004.html</trackback:ping><description><![CDATA[关于inode； <br><br><br>inode 译成中文就是索引节点。每个存储设备或存储设备的分区（存储设备是硬盘、软盘、U盘 ... ... ）被格式化为文件系统后，应该有两部份，一部份是inode，另一部份是Block，Block是用来存储数据用的。而inode呢，就是用来存储这些数据的信息，这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引，所以就有了inode的数值。操作系统根据指令，能通过inode值最快的找到相对应的文件。 <br><br>做个比喻，比如一本书，存储设备或分区就相当于这本书，Block相当于书中的每一页，inode 就相当于这本书前面的目录，一本书有很多的内容，如果想查找某部份的内容，我们可以先查目录，通过目录能最快的找到我们想要看的内容。虽然不太恰当，但还是比较形象。 <br><br>当我们用ls 查看某个目录或文件时，如果加上-i 参数，就可以看到inode节点了；比如我们前面所说的例子； <br><br><br><br>[root@localhost ~]# ls -li lsfile.sh <br>2408949 -rwxr-xr-x 1 root root 7 04-21 12:47 lsfile.sh <br><br>lsfile.sh 的inode值是 2408949 ； 查看一个文件或目录的inode，要通过ls 命令的的 -i参数。 <br><br><br>2.10 inode 相同的文件是硬链接文件； <br><br><br>在Linux 文件系统中，inode值相同的文件是硬链接文件，也就是说，不同的文件名，inode可能是相同的，一个inode值可以对应多个文件。理解链接文件并不难，看看例子就会了。在Linux中，链接文件是通过ln工具来创建的。 <br><br><br>2.11 创建硬链接，硬链接和源文件关系； <br><br><br>用ln 创建文件硬链接的语法： <br><br><br><br># ln 源文件 目标文件 <br><br>下面我们举一个例子，在这个例子中，我们要为sun.txt 创建其硬链接sun002.txt。然后看一下sun.txt和sun002.txt的属性的变化； <br><br><br>[root@localhost ~]# ls -li sun.txt 注：查看sun.txt的属性； <br>2408263 -rw-r--r-- 1 root root 29 04-22 21:02 sun.txt 注：这是sun.txt的属性； <br>[root@localhost ~]# ln sun.txt sun002.txt 注：我们通过ln 来创建sun.txt的硬链接文件sun002.txt <br>[root@localhost ~]# ls -li sun* 注：我们列一下sun.txt 和sun002.txt <br>2408263 -rw-r--r-- 2 root root 29 04-22 21:02 sun002.txt <br>2408263 -rw-r--r-- 2 root root 29 04-22 21:02 sun.txt <br><br>我们可以看到sun.txt在没有创建硬链接文件sun002.txt的时候，其链接个数是1（也就是-rw-r--r--后的那个数值），创建了硬链接sun002.txt创建后，这个值变成了2。也就是说，我们每次为sun.txt创建一个新的硬链接文件后，其硬链接个数都会增加1。 <br><br>inode值相同的文件，他们的关系是互为硬链接的关系。当我们修改其中一个文件的内容时，互为硬链接的文件的内容也会跟着变化。如果我们删除互为硬链接关系的某个文件时，其它的文件并不受影响。比如我们把sun.txt删除后，我们还是一样能看到sun002.txt的内容，并且sun02.txt仍是存在的。 <br><br>可以这么理解，互为硬链接关系的文件，他们好象是克隆体，他们的属性几乎是完全一样； <br><br>下面的例子，我们把sun.txt删除，然后我们看一下sun002.txt 是不是能看到其内容。 <br><br><br><br>[root@localhost ~]# rm -rf sun.txt <br>[root@localhost ~]# more sun002.txt <br><br>注意：硬链接不能为目录创建，只有文件才能创建硬链接。 <br><br><br>2.12 软链接的创建，及软接与源文件的关系； <br><br><br>创建软链接（也被称为符号链接）的语法； <br><br><br><br># ln -s 源文文件或目录 目标文件或目录 <br><br>软链接也叫符号链接，他和硬链接有所不同，软链接文件只是其源文件的一个标记。当我们删除了源文件后，链接文件不能独立存在，虽然仍保留文件名，但我们却不能查看软链接文件的内容了。 <br><br><br><br>[root@localhost ~]# ls -li linuxsir001.txt <br>2408274 -rw-r--r-- 1 root root 29 04-22 21:53 linuxsir001.txt <br>[root@localhost ~]# ln -s linuxsir001.txt linuxsir002.txt <br>[root@localhost ~]# ls -li linuxsir001.txt linuxsir002.txt <br>2408274 -rw-r--r-- 1 root root 29 04-22 21:53 linuxsir001.txt <br>2408795 lrwxrwxrwx 1 root root 15 04-22 21:54 linuxsir002.txt -&gt; linuxsir001.txt <br><br>解释 <br><br>上面的例子，首先我们查看 linuxsir001.txt 的属性，比如inode 、所属文件种类、创建或修改时间等... ...我们来对比一下： <br><br>首先 对比一下节点：两个文件的节点不同； <br>其次 两个文件的归属的种类不同 linuxsir001.txt是-，也就是普通文件，而linuxsir002.txt 是l，它是一个链接文件； <br>第三 两个文件的读写权限不同 linuxsir001.txt 是rw-r--r-- ，而linuxsir002.txt的读写权限是 rwxrwxrwx <br>第三 两者的硬链接个数相同；都是1 <br>第四 两文件的属主和所归属的用户组相同； <br>第五 修改(或访问、创建）时间不同； <br><br>我们还注意到了linuxsir002.txt 后面有一个标记 -&gt;，这表示linuxsir002.txt 是linuxsir001.txt的软链接文件。 <br><br>值得我们注意的是：当我们修改链接文件的内容时，就意味着我们在修改源文件的内容。当然源文件的属性也会发生改变，链接文件的属性并不会发生变化。当我们把源文件删除后，链接文件只存在一个文件名，因为失去了源文件，所以软链接文件也就不存在了。这一点和硬链接是不同的； <br><br><br><br>[root@localhost ~]# rm -rf linuxsir001.txt 注：删除linuxsir001.txt <br>[root@localhost ~]# ls -li linuxsir002.txt 注：查看linuxsir002 的属性； <br>2408795 lrwxrwxrwx 1 root root 15 04-22 21:54 linuxsir002.txt -&gt; linuxsir001.txt <br>[root@localhost ~]# more linuxsir002.txt 注：查看linuxsir002.txt的内容； <br>linuxsir002.txt: 没有那个文件或目录 注：得到提示，linuxsir002.txt不存在。 <br><br>上面的例子告诉我们，如果一个链接文件失去了源，就意味着他已经不存在了； <br><br>我们可以看到软链接文件，其实只是源文件的一个标记，当源文件失去时，他也就是存在了。软链接文件只是占用了inode来存储软链接文件属性等信息，但文件存储是指向源文件的。 <br><br>软件链接，可以为文件或目录都适用。无论是软链接还是硬链接，都可以用rm来删除。rm工具是通用的。</ca><br>参考资料：<a href="http://techcenter.dicder.com/2006/0908/content_185.htm" target=_blank><u><font color=#0000ff>http://techcenter.dicder.com/2006/0908/content_185.htm</font></u></a>
<img src ="http://www.blogjava.net/machilansing/aggbug/130004.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/machilansing/" target="_blank">Lansing</a> 2007-07-13 09:54 <a href="http://www.blogjava.net/machilansing/archive/2007/07/13/Inode.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于Java的全文索引/检索引擎——Lucene</title><link>http://www.blogjava.net/machilansing/archive/2006/08/17/Lucene.html</link><dc:creator>Lansing</dc:creator><author>Lansing</author><pubDate>Thu, 17 Aug 2006 01:08:00 GMT</pubDate><guid>http://www.blogjava.net/machilansing/archive/2006/08/17/Lucene.html</guid><wfw:comment>http://www.blogjava.net/machilansing/comments/64037.html</wfw:comment><comments>http://www.blogjava.net/machilansing/archive/2006/08/17/Lucene.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/machilansing/comments/commentRss/64037.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/machilansing/services/trackbacks/64037.html</trackback:ping><description><![CDATA[
		<p>
				<a name="intro">
						<b>基于Java的全文索引/检索引擎——Lucene</b>
				</a>
		</p>
		<p>Lucene不是一个完整的全文索引应用，而是是一个用Java写的全文索引引擎工具包，它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。</p>
		<p>Lucene的作者：Lucene的贡献者<a href="http://www.nutch.org/blog/cutting.html">Doug Cutting</a>是一位资深全文索引/检索专家，曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者，后在Excite担任高级系统架构设计师，目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。</p>
		<p>Lucene的发展历程：早先发布在作者自己的<a href="http://www.lucene.com/">www.lucene.com</a>，后来发布在<a href="http://sourceforge.net/projects/lucene/">SourceForge</a>，2001年年底成为APACHE基金会jakarta的一个子项目：<a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</a></p>
		<p>已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎，比较著名的有：</p>
		<ul>
				<li>
						<a href="http://www.jivesoftware.com/">J</a>
						<a href="http://www.jivesoftware.com/">ive</a>：WEB论坛系统； 
</li>
				<li>
						<a href="http://eyebrowse.tigris.org/">Eyebrows</a>：邮件列表HTML归档/浏览/查询系统，本文的主要参考文档“<a href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">TheLucene search engine: Powerful, flexible, and free</a>”作者就是EyeBrows系统的主要开发者之一，而EyeBrows已经成为目前APACHE项目的主要邮件列表归档系统。 
</li>
				<li>
						<a href="http://xml.apache.org/cocoon/index.html">Cocoon</a>:基于XML的web发布框架，全文检索部分使用了Lucene 
</li>
				<li>
						<p align="left">
								<a href="http://www.eclipse.org/">Eclipse</a>:基于Java的开放开发平台，帮助部分的全文索引使用了Lucene</p>
				</li>
		</ul>
		<p>对于中文用户来说，最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍，你会了解到由于Lucene良好架构设计，对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。</p>
		<p>
				<b>
						<a name="compare">全文检索的实现机制</a>
				</b>
		</p>
		<p>Lucene的API接口设计的比较通用，输入输出结构都很像数据库的表==&gt;记录==&gt;字段，所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看：可以先把<b>Lucene当成一个支持全文索引的数据库系统</b>。</p>
		<p>比较一下Lucene和数据库：</p>
		<table width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="50%">Lucene</td>
								<td align="middle" width="50%">数据库</td>
						</tr>
						<tr>
								<td width="50%">
										<pre>索引数据源：doc(field1,field2...) doc(field1,field2...)<br />                  \  indexer /<br />                 _____________<br />                | Lucene Index|<br />                --------------<br />                 / searcher \<br /> 结果输出：Hits(doc(field1,field2) doc(field1...))</pre>
								</td>
								<td width="50%">
										<pre> 索引数据源：record(field1,field2...) record(field1..)<br />              \  SQL: insert/<br />               _____________<br />              | DB  Index   |<br />               -------------<br />              / SQL: select \<br />结果输出：results(record(field1,field2..) record(field1...))</pre>
								</td>
						</tr>
						<tr>
								<td width="50%">Document：一个需要进行索引的“单元”<br />一个Document由多个字段组成</td>
								<td width="50%">Record：记录，包含多个字段</td>
						</tr>
						<tr>
								<td width="50%">Field：字段</td>
								<td width="50%">Field：字段</td>
						</tr>
						<tr>
								<td width="50%">Hits：查询结果集，由匹配的Document组成</td>
								<td width="50%">RecordSet：查询结果集，由多个Record组成</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="1">
						<b>全文检索 ≠ like "%keyword%"</b>
				</font>
		</p>
		<p>通常比较厚的书籍后面常常附关键词索引表（比如：北京：12, 34页，上海：3,77页……），它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样，想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高，另外一个原因是它是排好序的。<b>对于检索系统来说核心是一个排序问题</b>。</p>
		<p align="left">由于数据库索引不是为全文索引设计的，因此，<b>使用like "%keyword%"时，数据库索引是不起作用的</b>，在使用like查询时，搜索过程又变成类似于一页页翻书的遍历过程了，所以对于含有模糊查询的数据库服务来说，LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配：like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。</p>
		<p>所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制，将数据源（比如多篇文章）排序顺序存储的同时，有另外一个排好序的关键词列表，用于存储关键词==&gt;文章映射关系，利用这样的映射关系索引：[关键词==&gt;出现关键词的文章编号，出现次数（甚至包括位置：起始偏移量，结束偏移量），出现频率]，检索过程就是把<b>模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程</b>。从而大大提高了多关键词查询的效率，所以，全文检索问题归结到最后是一个排序问题。</p>
		<p>由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题，这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制，并提供了扩展接口，以方便针对不同应用的定制。</p>
		<p>可以通过一下表格对比一下数据库的模糊查询：</p>
		<p>
				<span style="FONT-WEIGHT: bold">
						<table style="WIDTH: 540px; HEIGHT: 592px" height="592" width="540" border="1">
								<tbody>
										<tr>
												<td align="middle" width="9%" height="16">　</td>
												<td align="middle" width="47%" height="16">Lucene全文索引引擎</td>
												<td align="middle" width="40%" height="16">数据库</td>
										</tr>
										<tr>
												<td width="9%" height="48">索引</td>
												<td width="47%" height="48">将数据源中的数据都通过全文索引一一建立反向索引</td>
												<td width="40%" height="48">对于LIKE查询来说，数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配，比有索引的搜索速度要有多个数量级的下降。</td>
										</tr>
										<tr>
												<td width="9%" height="49">匹配效果</td>
												<td width="47%" height="49">通过词元(term)进行匹配，通过语言分析接口的实现，可以实现对中文等非英语的支持。</td>
												<td width="40%" height="49">使用：like "%net%" 会把netherlands也匹配出来，<br />多个关键词的模糊匹配：使用like "%com%net%"：就不能匹配词序颠倒的xxx.net..xxx.com</td>
										</tr>
										<tr>
												<td width="9%" height="32">匹配度</td>
												<td width="47%" height="32">有匹配度算法，将匹配程度（相似度）比较高的结果排在前面。</td>
												<td width="40%" height="32">没有匹配程度的控制：比如有记录中net出现5词和出现1次的，结果是一样的。</td>
										</tr>
										<tr>
												<td width="9%" height="32">结果输出</td>
												<td width="47%" height="32">通过特别的算法，将最匹配度最高的头100条结果输出，结果集是缓冲式的小批量读取的。</td>
												<td width="40%" height="32">返回所有的结果集，在匹配条目非常多的时候（比如上万条）需要大量的内存存放这些临时结果集。</td>
										</tr>
										<tr>
												<td width="9%" height="32">可定制性</td>
												<td width="47%" height="32">通过不同的语言分析接口实现，可以方便的定制出符合应用需要的索引规则（包括对中文的支持）</td>
												<td width="40%" height="32">没有接口或接口复杂，无法定制</td>
										</tr>
										<tr>
												<td width="9%" height="32">结论</td>
												<td width="47%" height="32">高负载的模糊查询应用，需要负责的模糊查询的规则，索引的资料量比较大</td>
												<td width="40%" height="32">使用率低，模糊匹配规则简单或者需要模糊查询的资料量少</td>
										</tr>
								</tbody>
						</table>
						<br />全文检索和数据库应用最大的不同在于：让</span>
				<span style="FONT-WEIGHT: bold">最相关的</span>
				<span style="FONT-WEIGHT: bold">头100条结果满足98%以上用户的需求<br /></span>
				<br />Lucene的创新之处：</p>
		<p>大部分的搜索（数据库）引擎都是用B树结构来维护索引，索引的更新会导致大量的IO操作，Lucene在实现中，对此稍微有所改进：不是维护一个索引文件，而是在扩展索引的时候不断创建新的索引文件，然后定期的把这些新的小索引文件合并到原先的大索引中（针对不同的更新策略，批次的大小可以调整），这样在不影响检索的效率的前提下，提高了索引的效率。</p>
		<p>Lucene和其他一些全文检索系统/应用的比较：</p>
		<table style="WIDTH: 543px; HEIGHT: 617px" width="543" border="1">
				<tbody>
						<tr>
								<td align="middle" width="18%">　</td>
								<td align="middle" width="45%">Lucene</td>
								<td align="middle" width="37%">其他开源全文检索系统</td>
						</tr>
						<tr>
								<td width="18%">增量索引和批量索引</td>
								<td width="45%">可以进行增量的索引(Append)，可以对于大量数据进行批量索引，并且接口设计用于优化批量索引和小批量的增量索引。</td>
								<td width="37%">很多系统只支持批量的索引，有时数据源有一点增加也需要重建索引。</td>
						</tr>
						<tr>
								<td width="18%">数据源</td>
								<td width="45%">Lucene没有定义具体的数据源，而是一个文档的结构，因此可以非常灵活的适应各种应用（只要前端有合适的转换器把数据源转换成相应结构），</td>
								<td width="37%">很多系统只针对网页，缺乏其他格式文档的灵活性。</td>
						</tr>
						<tr>
								<td width="18%">索引内容抓取</td>
								<td width="45%">Lucene的文档是由多个字段组成的，甚至可以控制那些字段需要进行索引，那些字段不需要索引，近一步索引的字段也分为需要分词和不需要分词的类型：<br />   需要进行分词的索引，比如：标题，文章内容字段<br />   不需要进行分词的索引，比如：作者/日期字段</td>
								<td width="37%">缺乏通用性，往往将文档整个索引了</td>
						</tr>
						<tr>
								<td width="18%">语言分析</td>
								<td width="45%">通过语言分析器的不同扩展实现：<br />可以过滤掉不需要的词：an the of 等，<br />西文语法分析：将jumps jumped jumper都归结成jump进行索引/检索<br />非英文支持：对亚洲语言，阿拉伯语言的索引支持</td>
								<td width="37%">缺乏通用接口实现</td>
						</tr>
						<tr>
								<td width="18%">查询分析</td>
								<td width="45%">通过查询分析接口的实现，可以定制自己的查询语法规则：<br />比如： 多个关键词之间的 + - and or关系等</td>
								<td width="37%">　</td>
						</tr>
						<tr>
								<td width="18%">并发访问</td>
								<td width="45%">能够支持多用户的使用</td>
								<td width="37%">　</td>
						</tr>
				</tbody>
		</table>
		<p>　</p>
		<p>
				<b>
						<a name="segment">关于亚洲语言的的切分词问题(Word Segment)</a>
				</b>
		</p>
		<p>对于中文来说，全文索引首先还要解决一个语言分析的问题，对于英文来说，语句中单词之间是天然通过空格分开的，但亚洲语言的中日韩文语句中的字是一个字挨一个，所有，首先要把语句中按“词”进行索引的话，这个词如何切分出来就是一个很大的问题。</p>
		<p>首先，肯定不能用单个字符作(si-gram)为索引单元，否则查“上海”时，不能让含有“海上”也匹配。</p>
		<p>但一句话：“北京天安门”，计算机如何按照中文的语言习惯进行切分呢？<br />“北京 天安门” 还是“北 京 天安门”？让计算机能够按照语言习惯进行切分，往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。</p>
		<p>另外一个解决的办法是采用自动切分算法：将单词按照2元语法(bigram)方式切分出来，比如：<br />"北京天安门" ==&gt; "北京 京天 天安 安门"。</p>
		<p>这样，在查询的时候，无论是查询"北京" 还是查询"天安门"，将查询词组按同样的规则进行切分："北京"，"天安安门"，多个关键词之间按与"and"的关系组合，同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言：韩文，日文都是通用的。</p>
		<p>基于自动切分的最大优点是没有词表维护成本，实现简单，缺点是索引效率低，但对于中小型应用来说，基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多，而对于英文，索引文件一般只有原文件的30%-40%不同，</p>
		<table height="68" width="100%" border="1">
				<tbody>
						<tr>
								<td align="middle" width="11%" height="18">
										<br />
								</td>
								<td align="middle" width="39%" height="18">自动切分</td>
								<td align="middle" width="50%" height="18">词表切分</td>
						</tr>
						<tr>
								<td width="11%" height="16">实现</td>
								<td width="39%" height="16">实现非常简单</td>
								<td width="50%" height="16">实现复杂</td>
						</tr>
						<tr>
								<td width="11%" height="16">查询</td>
								<td width="39%" height="16">增加了查询分析的复杂程度，</td>
								<td width="50%" height="16">适于实现比较复杂的查询语法规则</td>
						</tr>
						<tr>
								<td width="11%" height="16">存储效率</td>
								<td width="39%" height="16">索引冗余大，索引几乎和原文一样大</td>
								<td width="50%" height="16">索引效率高，为原文大小的30％左右</td>
						</tr>
						<tr>
								<td width="11%" height="16">维护成本</td>
								<td width="39%" height="16">无词表维护成本</td>
								<td width="50%" height="16">词表维护成本非常高：中日韩等语言需要分别维护。<br />还需要包括词频统计等内容</td>
						</tr>
						<tr>
								<td width="11%" height="16">适用领域</td>
								<td width="39%" height="16">嵌入式系统：运行环境资源有限<br />分布式系统：无词表同步问题<br />多语言环境：无词表维护成本</td>
								<td width="50%" height="16">对查询和存储效率要求高的专业搜索引擎<br /></td>
						</tr>
				</tbody>
		</table>
		<p>目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法，大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。</p>
		<p>
				<a name="demo">
						<b>安装和使用</b>
				</a>
		</p>
		<p>下载：<a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/</a></p>
		<p>注意：Lucene中的一些比较复杂的词法分析是用JavaCC生成的（JavaCC：JavaCompilerCompiler，纯Java的词法分析生成器），所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器，还需要从<a href="https://javacc.dev.java.net/">https://javacc.dev.java.net/</a>下载javacc。</p>
		<p>lucene的组成结构：对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口</p>
		<table width="100%" border="1">
				<tbody>
						<tr>
								<td width="27%">org.apache.Lucene.search/</td>
								<td width="73%">搜索入口</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.index/</td>
								<td width="73%">索引入口</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.analysis/</td>
								<td width="73%">语言分析器</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.queryParser/</td>
								<td width="73%">查询分析器</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.document/</td>
								<td width="73%">存储结构</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.store/ </td>
								<td width="73%">底层IO/存储结构</td>
						</tr>
						<tr>
								<td width="27%">org.apache.Lucene.util/</td>
								<td width="73%">一些公用的数据结构</td>
						</tr>
				</tbody>
		</table>
		<p>简单的例子演示一下Lucene的使用方法：</p>索引过程：从命令行读取文件名（多个），将文件分路径(path字段)和内容(body字段)2个字段进行存储，并对内容进行全文索引：索引的单位是Document对象，每个Document对象包含多个字段Field对象，针对不同的字段属性和数据输出的需求，对字段还可以选择不同的索引/存储字段规则，列表如下： 
<table border="1"><tbody><tr><th>方法</th><th>切词</th><th>索引</th><th>存储</th><th>用途</th></tr><tr><td>Field.Text(String name, String value)</td><td>Yes</td><td>Yes</td><td>Yes</td><td valign="top">切分词索引并存储，比如：标题，内容字段</td></tr><tr><td>Field.Text(String name, Reader value)</td><td>Yes</td><td>Yes</td><td>No</td><td valign="top">切分词索引不存储，比如：META信息，<br />不用于返回显示，但需要进行检索内容</td></tr><tr><td>Field.Keyword(String name, String value)</td><td>No</td><td>Yes</td><td>Yes</td><td valign="top">不切分索引并存储，比如：日期字段</td></tr><tr><td>Field.UnIndexed(String name, String value)</td><td>No</td><td>No</td><td>Yes</td><td valign="top">不索引，只存储，比如：文件路径</td></tr><tr><td>Field.UnStored(String name, String value)</td><td>Yes</td><td>Yes</td><td>No</td><td valign="top">只全文索引，不存储</td></tr></tbody></table><pre>public class IndexFiles { <br />  //使用方法：: IndexFiles [索引输出目录] [索引的文件列表] ... <br />  public static void main(String[] args) throws Exception {<br />    String indexPath = args[0];<br />    IndexWriter writer;<br />    //用指定的语言分析器构造一个新的写索引器（第3个参数表示是否为追加索引）<br />    writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);<br /><br />    for (int i=1; i&lt;args.length; i++) {<br />      System.out.println("Indexing file " + args[i]);<br />      InputStream is = new FileInputStream(args[i]);<br /><br />      //构造包含2个字段Field的Document对象<br />      //一个是路径path字段，不索引，只存储<br />      //一个是内容body字段，进行全文索引，并存储<br />      Document doc = new Document();<br />      doc.add(Field.UnIndexed("path", args[i]));<br />      doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));<br />      //将文档写入索引<br />      writer.addDocument(doc);<br />      is.close();<br />    };<br />    //关闭写索引器<br />    writer.close();<br />  }<br />}<br />　</pre><p>索引过程中可以看到：</p><ul><li>语言分析器提供了抽象的接口，因此语言分析(Analyser)是可以定制的，虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser，这2个分析器缺省都不支持中文，所以要加入对中文语言的切分规则，需要修改这2个分析器。 
</li><li>Lucene并没有规定数据源的格式，而只提供了一个通用的结构（Document对象）来接受索引的输入，因此输入的数据源可以是：数据库，WORD文档，PDF文档，HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。 
</li><li>对于大批量的数据索引，还可以通过调整IndexerWrite的文件合并频率属性（mergeFactor）来提高批量索引的效率。 </li></ul><p>检索过程和结果显示：</p><p>搜索结果返回的是Hits对象，可以通过它再访问Document==&gt;Field中的内容。</p><p>假设根据body字段进行全文检索，可以将查询结果的path字段和相应查询的匹配度(score)打印出来，</p><pre>public class Search { <br />  public static void main(String[] args) throws Exception {<br />    String indexPath = args[0], queryString = args[1];<br />    //指向索引目录的搜索器<br />    Searcher searcher = new IndexSearcher(indexPath);<br />    //查询解析器：使用和索引同样的语言分析器<br />    Query query = QueryParser.parse(queryString, "body", <br />                              new SimpleAnalyzer());<br />    //搜索结果使用Hits存储<br />    Hits hits = searcher.search(query);<br />    //通过hits可以访问到相应字段的数据和查询的匹配度<br />    for (int i=0; i&lt;hits.length(); i++) {<br />      System.out.println(hits.doc(i).get("path") + "; Score: " + <br />                         hits.score(i));<br />    };<br />  }<br />}</pre>在整个检索过程中，语言分析器，查询分析器，甚至搜索器（Searcher）都是提供了抽象的接口，可以根据需要进行定制。 
<p><b><a name="hacking">Hacking Lucene</a></b></p><p><b>简化的查询分析器</b></p><p>个人感觉lucene成为JAKARTA项目后，画在了太多的时间用于调试日趋复杂QueryParser，而其中大部分是大多数用户并不很熟悉的，目前LUCENE支持的语法：</p><p>Query ::= ( Clause )*<br />Clause ::= ["+", "-"] [&lt;TERM&gt; ":"] ( &lt;TERM&gt; | "(" Query ")")</p><p>中间的逻辑包括：and or + - &amp;&amp;||等符号，而且还有"短语查询"和针对西文的前缀/模糊查询等，个人感觉对于一般应用来说，这些功能有一些华而不实，其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。所以，Lucene早期版本的QueryParser仍是比较好的选择。</p><p><b>添加修改删除指定记录（Document）</b></p><p>Lucene提供了索引的扩展机制，因此索引的动态扩展应该是没有问题的，而指定记录的修改也似乎只能通过记录的删除，然后重新加入实现。如何删除指定的记录呢？删除的方法也很简单，只是需要在索引时根据数据源中的记录ID专门另建索引，然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。</p><p><b>根据某个字段值的排序功能</b></p><p>lucene缺省是按照自己的相关度算法（score）进行结果排序的，但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题，很多原先基于数据库应用都需要除了基于匹配度（score）以外的排序功能。而从全文检索的原理我们可以了解到，任何不基于索引的搜索过程效率都会导致效率非常的低，如果基于其他字段的排序需要在搜索过程中访问存储字段，速度回大大降低，因此非常是不可取的。</p><p>但这里也有一个折中的解决方法：在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数，所以，基于score以外的排序，其实可以通过将数据源预先排好序，然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。</p><p>这里需要修改的是IndexSearcher中的HitCollector过程：</p><pre>...<br />　scorer.score(new HitCollector() {<br />	private float minScore = 0.0f;<br />	public final void collect(int doc, float score) {<br />	  if (score &gt; 0.0f &amp;&amp;			  // ignore zeroed buckets<br />	      (bits==null || bits.get(doc))) {	  // skip docs not in bits<br />	    totalHits[0]++;<br />	    if (score &gt;= minScore) {<br />              /* 原先：Lucene将docID和相应的匹配度score例入结果命中列表中：<br />	       * hq.put(new ScoreDoc(doc, score));	  // update hit queue<br />               * 如果用doc 或 1/doc 代替 score，就实现了根据docID顺排或逆排<br />               * 假设数据源索引时已经按照某个字段排好了序，而结果根据docID排序也就实现了<br />               * 针对某个字段的排序，甚至可以实现更复杂的score和docID的拟合。<br />               */<br />              hq.put(new ScoreDoc(doc, (float) 1/doc )); <br />	      if (hq.size() &gt; nDocs) {		  // if hit queue overfull<br />		hq.pop();			  // remove lowest in hit queue<br />		minScore = ((ScoreDoc)hq.top()).score; // reset minScore<br />	      }<br />	    }<br />	  }<br />	}<br />      }, reader.maxDoc());</pre><p><b>更通用的输入输出接口</b></p><p>虽然lucene没有定义一个确定的输入文档格式，但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口，然后其他数据，比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主，类似实现已经不下4，5个：</p><pre>数据源: WORD       PDF     HTML    DB       other<br />         \          |       |      |         /<br />                       XML中间格式<br />                            |<br />                     Lucene INDEX</pre><p>目前还没有针对MSWord文档的解析器，因为Word文档和基于ASCII的RTF文档不同，需要使用COM对象机制解析。这个是我在Google上查的相关资料：<a href="http://www.intrinsyc.com/products/enterprise_applications.asp">http://www.intrinsyc.com/products/enterprise_applications.asp</a><br />另外一个办法就是把Word文档转换成text：<a href="http://www.winfield.demon.nl/index.html">http://www.winfield.demon.nl/index.html</a><br /></p><p><br /><b>索引过程优化</b></p><p>索引一般分2种情况，一种是小批量的索引扩展，一种是大批量的索引重建。在索引过程中，并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作（文件I/O是一件非常消耗资源的事情）。</p><p>Lucene先在内存中进行索引操作，并根据一定的批量进行文件的写入。这个批次的间隔越大，文件的写入次数越少，但占用内存会很多。反之占用内存少，但文件IO操作频繁，索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验：缺省Indexer是每20条记录索引后写入一次，每将MERGE_FACTOR增加50倍，索引速度可以提高1倍左右。<br /></p><p><span style="FONT-WEIGHT: bold">搜索过程优化<br /></span></p><p><span style="FONT-WEIGHT: bold"></span>lucene支持内存索引：这样的搜索比基于文件的I/O有数量级的速度提升。<br /><a href="http://www.onjava.com/lpt/a/3273">http://www.onjava.com/lpt/a/3273</a><br />而尽可能减少IndexSearcher的创建和对搜索结果的前台的缓存也是必要的。<br /><span style="FONT-WEIGHT: bold"></span></p><p><b></b></p><p>Lucene面向全文检索的优化在于首次索引检索后，并不把所有的记录（Document）具体内容读取出来，而起只将所有结果中匹配度最高的头100条结果（TopDocs）的ID放到结果集缓存中并返回，这里可以比较一下数据库检索：如果是一个10,000条的数据库检索结果集，数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。所以即使检索匹配总数很多，Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的，头100条已经可以满足90%以上的检索需求。<br /></p><p>如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存，并再重新向后抓取。所以如果构造一个Searcher去查1－120条结果，Searcher其实是进行了2次搜索过程：头100条取完后，缓存结果用完，Searcher重新检索再构造一个200条的结果缓存，依此类推，400条缓存，800条缓存。由于每次Searcher对象消失后，这些缓存也访问那不到了，你有可能想将结果记录缓存下来，缓存数尽量保证在100以下以充分利用首次的结果缓存，不让Lucene浪费多次检索，而且可以分级进行结果缓存。<br /></p><p>Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。</p><p><a href="http://sourceforge.net/projects/weblucene/">我的一些尝试</a>：</p><ul><li>支持中文的Tokenizer：这里有2个版本，一个是通过JavaCC生成的，对CJK部分按一个字符一个TOKEN索引，另外一个是从SimpleTokenizer改写的，对英文支持数字和字母TOKEN，对中文按迭代索引。 
</li><li>基于XML数据源的索引器：XMLIndexer，因此所有数据源只要能够按照DTD转换成指定的XML，就可以用XMLIndxer进行索引了。 
</li><li>根据某个字段排序：按记录索引顺序排序结果的搜索器：IndexOrderSearcher，因此如果需要让搜索结果根据某个字段排序，可以让数据源先按某个字段排好序（比如：PriceField），这样索引后，然后在利用这个按记录的ID顺序检索的搜索器，结果就是相当于是那个字段排序的结果了。 </li></ul><p><a name="learn"><b>从Lucene学到更多</b></a></p><p>Luene的确是一个面对对象设计的典范</p><ul><li>所有的问题都通过一个额外抽象层来方便以后的扩展和重用：你可以通过重新实现来达到自己的目的，而对其他模块而不需要； 
</li><li>简单的应用入口Searcher, Indexer，并调用底层一系列组件协同的完成搜索任务； 
</li><li>所有的对象的任务都非常专一：比如搜索过程：QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取，并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高，因此可以通过重新实现而不需要修改其他模块。  
</li><li>除了灵活的应用接口设计，Lucene还提供了一些适合大多数应用的语言分析器实现（SimpleAnalyser,StandardAnalyser），这也是新用户能够很快上手的重要原因之一。 </li></ul><p>这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包，Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。</p><p>此外，通过对Lucene的学习和使用，我也更深刻地理解了为什么很多数据库优化设计中要求，比如：</p><ul><li>尽可能对字段进行索引来提高查询速度，但过多的索引会对数据库表的更新操作变慢，而对结果过多的排序条件，实际上往往也是性能的杀手之一。 
</li><li>很多商业数据库对大批量的数据插入操作会提供一些优化参数，这个作用和索引器的merge_factor的作用是类似的， 
</li><li>20%/80%原则：查的结果多并不等于质量好，尤其对于返回结果集很大，如何优化这头几十条结果的质量往往才是最重要的。 
</li><li>尽可能让应用从数据库中获得比较小的结果集，因为即使对于大型数据库，对结果集的随机访问也是一个非常消耗资源的操作。<br /></li></ul><p>参考资料：</p><p>Apache: Lucene Project<br /><a href="http://jakarta.apache.org/lucene/">http://jakarta.apache.org/lucene/<br /></a>Lucene开发/用户邮件列表归档<br /><a href="http://www.mail-archive.com/lucene-dev@jakarta.apache.org/">Lucene-dev@jakarta.apache.org</a><br /><a href="http://www.mail-archive.com/lucene-user@jakarta.apache.org/">Lucene-user@jakarta.apache.org</a></p><p>The Lucene search engine: Powerful, flexible, and free<br /><a href="http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene_p.html">http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html</a></p><p>Lucene Tutorial<br /><a href="http://www.darksleep.com/puff/lucene/lucene.html">http://www.darksleep.com/puff/lucene/lucene.html</a></p><p>Notes on distributed searching with Lucene<br /><a href="http://home.clara.net/markharwood/lucene/">http://home.clara.net/markharwood/lucene/</a></p><p>中文语言的切分词<br /><a href="http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment">http://www.google.com/search?sourceid=navclient&amp;hl=zh-CN&amp;q=chinese+word+segment</a></p><p>搜索引擎工具介绍<a href="http://searchtools.com/"><br />http://searchtools.com/</a></p><p>Lucene作者Cutting的几篇论文和专利<br /><a href="http://lucene.sourceforge.net/publications.html">http://lucene.sourceforge.net/publications.html</a> </p><p>Lucene的.NET实现：dotLucene<br /><a href="http://sourceforge.net/projects/dotlucene/">http://sourceforge.net/projects/dotlucene/<br /></a></p><p>Lucene作者Cutting的另外一个项目：基于Java的搜索引擎Nutch<br /><a href="http://www.nutch.org/">http://www.nutch.org/</a>   <a href="http://sourceforge.net/projects/nutch/">http://sourceforge.net/projects/nutch/<br /></a></p><p>关于基于词表和N-Gram的切分词比较<br /><a href="http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html">http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html</a><br /><br />2005-01-08 <a href="http://lucene.sourceforge.net/talks/pisa/">Cutting在Pisa大学做的关于Lucene的讲座：非常详细的Lucene架构解说</a></p><img src ="http://www.blogjava.net/machilansing/aggbug/64037.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/machilansing/" target="_blank">Lansing</a> 2006-08-17 09:08 <a href="http://www.blogjava.net/machilansing/archive/2006/08/17/Lucene.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>存储过程简介</title><link>http://www.blogjava.net/machilansing/archive/2006/08/11/62927.html</link><dc:creator>Lansing</dc:creator><author>Lansing</author><pubDate>Fri, 11 Aug 2006 02:25:00 GMT</pubDate><guid>http://www.blogjava.net/machilansing/archive/2006/08/11/62927.html</guid><wfw:comment>http://www.blogjava.net/machilansing/comments/62927.html</wfw:comment><comments>http://www.blogjava.net/machilansing/archive/2006/08/11/62927.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/machilansing/comments/commentRss/62927.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/machilansing/services/trackbacks/62927.html</trackback:ping><description><![CDATA[
		<p>        定义：<br />          将常用的或很复杂的工作，预先用SQL语句写好并用一个指定的名称存储起来,   那么以后要叫数据库提供与已定义好的存储过程的功能相同的服务时,只需调用execute,即可自动完成命令。<br />        讲到这里,可能有人要问：这么说存储过程就是一堆SQL语句而已啊？<br />          Microsoft公司为什么还要添加这个技术呢?<br />        那么存储过程与一般的SQL语句有什么区别呢?<br />        存储过程的优点：<br />          1.存储过程只在创造时进行编译，以后每次执行存储过程都不需再重新编译，而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。<br />          2.当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时），可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。<br />          3.存储过程可以重复使用,可减少数据库开发人员的工作量<br />          4.安全性高,可设定只有某此用户才具有对指定存储过程的使用权<br />        存储过程的种类：<br />          1.系统存储过程：以sp_开头,用来进行系统的各项设定.取得信息.相关管理工作,<br />          如   sp_help就是取得指定对象的相关信息<br />          2.扩展存储过程   以XP_开头,用来调用操作系统提供的功能<br />          exec   master..xp_cmdshell   'ping   10.8.16.1'<br />          3.用户自定义的存储过程,这是我们所指的存储过程<br />          常用格式<br />          Create   procedure   procedue_name<br />          [@parameter   data_type][output]<br />          [with]{recompile|encryption}<br />          as<br />          sql_statement<br />        解释:   <br />        output：表示此参数是可传回的<br />        with   {recompile|encryption}<br />        recompile:表示每次执行此存储过程时都重新编译一次<br />        encryption:所创建的存储过程的内容会被加密<br />        如:<br />          表book的内容如下<br />          编号   书名   价格<br />          001   C语言入门   $30<br />          002   PowerBuilder报表开发   $52<br />          实例1:查询表Book的内容的存储过程<br />          create   proc   query_book<br />          as   <br />          select   *   from   book<br />          go<br />          exec   query_book<br />          实例2:加入一笔记录到表book,并查询此表中所有书籍的总金额<br />          Create   proc   insert_book<br />          @param1   char(10),@param2   varchar(20),@param3   money,@param4   money   output<br />          with   encryption   ---------加密<br />          as<br />          insert   book(编号,书名，价格）   Values(@param1,@param2,@param3)<br />          select   @param4=sum(价格)   from   book<br />          go<br />          执行例子:   <br />          declare   @total_price   money   <br />          exec   insert_book   '003','Delphi   控件开发指南',$100,@total_price<br />          print   '总金额为'+convert(varchar,@total_price)<br />          go<br />        存储过程的3种传回值:<br />          1.以Return传回整数<br />          2.以output格式传回参数<br />          3.Recordset<br />        传回值的区别:<br />          output和return都可在批次程式中用变量接收,而recordset则传回到执行批次的客户端中   <br />        实例3：设有两个表为Product,Order,其表内容如下：<br />          Product<br />          产品编号   产品名称   客户订数   <br />          001   钢笔   30   <br />          002   毛笔   50   <br />          003   铅笔   100   <br />          Order   <br />          产品编号   客户名   客户订金<br />          001   南山区   $30<br />          002   罗湖区   $50<br />          003   宝安区   $4<br />        请实现按编号为连接条件,将两个表连接成一个临时表,该表只含编号.产品名.客户名.订金.总金额,<br />        总金额=订金*订数,临时表放在存储过程中<br />        代码如下:<br />          Create   proc   temp_sale<br />          as<br />          select   a.产品编号,a.产品名称,b.客户名,b.客户订金,a.客户订数*   b.客户订金   as总金额<br />          into   #temptable   from   Product   a   inner   join   Order   b   on   a.产品编号=b.产品编号<br />          if   @@error=0   <br />          print   'Good'<br />          else<br />        &amp;n bsp; print   'Fail'<br />          go</p>
		<p>存储过程介绍   <br />一、先介绍一下什么是存储过程   <br />存储过程是利用SQL   Server所提供的Tranact-SQL语言所编写的程序。Tranact-SQL语言是SQL   Server提供专为设计数据库应用程序的语言，它是应用程序和SQL   Server数据库间的主要程序式设计界面。它好比Oracle数据库系统中的Pro-SQL和Informix的数据库系统能够中的Informix-4GL语言一样。这类语言主要提供以下功能，让用户可以设计出符合引用需求的程序：   <br />1)、变量说明   <br />2)、ANSI兼容的SQL命令(如Select,Update….)   <br />3)、一般流程控制命令(if…else…、while….)   <br />4)、内部函数   </p>
		<p>二、存储过程的书写格   </p>
		<p>CREATE   PROCEDURE   [拥有者.]存储过程名[;程序编号]   <br />[(参数#1,…参数#1024)]   <br />[WITH   <br />{RECOMPILE   |   ENCRYPTION   |   RECOMPILE,   ENCRYPTION}   <br />]   <br />[FOR   REPLICATION]   <br />AS   程序行   </p>
		<p>其中存储过程名不能超过128个字。每个存储过程中最多设定1024个参数   <br />(SQL   Server   7.0以上版本),参数的使用方法如下:   </p>
		<p>@参数名   数据类型   [VARYING]   [=内定值]   [OUTPUT]   </p>
		<p>每个参数名前要有一个“@”符号,每一个存储过程的参数仅为该程序内部使用,参数的类型除了IMAGE外，其他SQL   Server所支持的数据类型都可使用。   <br />[=内定值]相当于我们在建立数据库时设定一个字段的默认值，这里是为这个参数设定默认值。[OUTPUT]是用来指定该参数是既有输入又有输出值的，也就是在调用了这个存储过程时，如果所指定的参数值是我们需要输入的参数，同时也需要在结果中输出的，则该项必须为OUTPUT，而如果只是做输出参数用，可以用CURSOR，同时在使用该参数时，必须指定VARYING和OUTPUT这两个语句。   </p>
		<p>例子:   <br />CREATE   PROCEDURE   order_tot_amt   @o_id   int,@p_tot   int   output   AS   <br />SELECT   @p_tot   =   sum(Unitprice*Quantity)   <br />FROM   orderdetails   <br />WHERE   <a href="mailto:ordered=@o_id">ordered=@o_id</a>   </p>
		<p>例子说明:   <br />该例子是建立一个简单的存储过程order_tot_amt,这个存储过程根据用户输入的定单ID号码(@o_id),由定单明细表(orderdetails)中计算该定单销售总额[单价(Unitprice)*数量(Quantity)],这一金额通过@p_tot这一参数输出给调用这一存储过程的程序   </p>
		<p>三、在SQL   Server中执行存储过程   </p>
		<p>在SQL   Server的查询分析器中，输入以下代码:   <br />declare   @tot_amt   int   <br />execute   order_tot_amt   1,@tot_amt   output   <br />select   @tot_amt   </p>
		<p>以上代码是执行order_tot_amt这一存储过程，以计算出定单编号为1的定单销售金额，我们定义@tot_amt为输出参数，用来承接我们所要的结果  </p>
<img src ="http://www.blogjava.net/machilansing/aggbug/62927.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/machilansing/" target="_blank">Lansing</a> 2006-08-11 10:25 <a href="http://www.blogjava.net/machilansing/archive/2006/08/11/62927.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Oracle 10g TO_DATE() ora-01830 领悟共勉</title><link>http://www.blogjava.net/machilansing/archive/2006/08/10/62857.html</link><dc:creator>Lansing</dc:creator><author>Lansing</author><pubDate>Thu, 10 Aug 2006 13:03:00 GMT</pubDate><guid>http://www.blogjava.net/machilansing/archive/2006/08/10/62857.html</guid><wfw:comment>http://www.blogjava.net/machilansing/comments/62857.html</wfw:comment><comments>http://www.blogjava.net/machilansing/archive/2006/08/10/62857.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/machilansing/comments/commentRss/62857.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/machilansing/services/trackbacks/62857.html</trackback:ping><description><![CDATA[
		<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">今天在 DRM中报错ora-01830 </font>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">把sql语句输出作了以下的实验，发现是时间多了一个.0</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">后来的办法是先把这个时间转成to_char，再转成to_date</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">SQL&gt; select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ss') from dual;</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ss') from dual</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">ORA-01830: 日期格式图片在转换整个输入字符串之前结束</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">SQL&gt; select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:sssss') from dual;</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:sssss') from dual</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">ORA-01836: 小时与日中的秒发生冲突</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">SQL&gt; select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ss.sssss') from dual;</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ss.sssss') from dual</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">ORA-01836: 小时与日中的秒发生冲突</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">SQL&gt; select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ff') from dual;</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">select to_date('2005-10-01 12:01:01.0','yyyy-mm-dd hh24:mi:ff') from dual</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">ORA-01821: 日期格式无法识别</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">------------------------------------------------------------------</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">必须保证传入的字符串和要转换的格式精确匹配</font>
		</p>
		<p>
				<font style="BACKGROUND-COLOR: #d3d3d3" face="Courier" color="#0000ff">SQL&gt; SELECT TO_DATE('11-10-1996-13:51:21','DD/MM/YYYY-HH24') A FROM dual; <br /><br />ERROR: <br />ORA-01830: date format picture ends before converting entire input string.</font>
		</p>
		<p>
				<font face="Courier">
						<font style="BACKGROUND-COLOR: #d3d3d3" color="#0000ff">SQL&gt; SELECT TO_DATE('11-10-1996-13:51:21','DD/MM/YYYY-HH24:MI:SS') B FROM dual; <br />－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－<br /></font>以上是转载的<br />后来我是这么做的哈：<br /><br />SELECT中将其他表的日期TO_CHAR下，然后再将值在INSERT时TO_DATE!<br />具体的做法如下：<br /><br />SELECT TO_CHAR(parameter,'YYYY-MM-DD HH24:MI:SS') AS TIME<br />FROM TABLE_NAME_1;<br /><br />...<br />...<br /><br />INSERT INTO TABLE_NAME_2<br />(COLUME_NAME_1)<br />VALUE (TO_DATE('"+TIME+"','YYYY-MM-DD HH24:MI:SS'));<br /><br />然后就OK了，呵呵，看来要学的还真多！</font>
		</p>
<img src ="http://www.blogjava.net/machilansing/aggbug/62857.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/machilansing/" target="_blank">Lansing</a> 2006-08-10 21:03 <a href="http://www.blogjava.net/machilansing/archive/2006/08/10/62857.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 的JDBC 数据库连接池实现方法</title><link>http://www.blogjava.net/machilansing/archive/2006/08/04/61776.html</link><dc:creator>Lansing</dc:creator><author>Lansing</author><pubDate>Fri, 04 Aug 2006 07:56:00 GMT</pubDate><guid>http://www.blogjava.net/machilansing/archive/2006/08/04/61776.html</guid><wfw:comment>http://www.blogjava.net/machilansing/comments/61776.html</wfw:comment><comments>http://www.blogjava.net/machilansing/archive/2006/08/04/61776.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/machilansing/comments/commentRss/61776.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/machilansing/services/trackbacks/61776.html</trackback:ping><description><![CDATA[Java 的JDBC 数据库连接池实现方法 <br /><br />关键字: Java, JDBC, Connection Pool, Database, 数据库连接池, sourcecode<br /><br />  虽然 J2EE 程序员一般都有现成的应用服务器所带的JDBC 数据库连接池，不过对于开发一般的 Java Application 、 Applet 或者 JSP、velocity 时，我们可用的JDBC 数据库连接池并不多，并且一般性能都不好。 Java 程序员都很羡慕 Windows ADO ，只需要 new Connection 就可以直接从数据库连接池中返回 Connection。并且 ADO Connection 是线程安全的，多个线程可以共用一个 Connection， 所以 ASP 程序一般都把 getConnection 放在 Global.asa 文件中，在 IIS 启动时建立数据库连接。ADO 的 Connection 和 Result 都有很好的缓冲，并且很容易使用。<br /><br />其实我们可以自己写一个JDBC 数据库连接池。写 JDBC connection pool 的注意事项有：<br /><br />1. 有一个简单的函数从连接池中得到一个 Connection。 <br />2. close 函数必须将 connection 放回 数据库连接池。 <br />3. 当数据库连接池中没有空闲的 connection， 数据库连接池必须能够自动增加 connection 个数。 <br />4. 当数据库连接池中的 connection 个数在某一个特别的时间变得很大，但是以后很长时间只用其中一小部分，应该可以自动将多余的 connection 关闭掉。 <br />5. 如果可能，应该提供debug 信息报告没有关闭的 new Connection 。 <br /><br />如果要 new Connection 就可以直接从数据库连接池中返回 Connection， 可以这样写( Mediator pattern ) (以下代码中使用了中文全角空格)：<br /><br /><pre class="overflow">public class EasyConnection implements java.sql.Connection{<br />　　private Connection m_delegate = null;<br /><br />　　public EasyConnection(){<br />　　　　m_delegate = getConnectionFromPool();<br />　　}<br /><br />　　public void close(){<br />　　　　putConnectionBackToPool(m_delegate);<br />　　}<br /><br />　　public PreparedStatement prepareStatement(String sql) throws SQLException{<br />　　　　m_delegate.prepareStatement(sql);<br />　　}<br /><br />　　//...... other method<br /><br />}</pre><br /><br />看来并不难。不过不建议这种写法，因为应该尽量避免使用 Java Interface, 关于 Java Interface 的缺点我另外再写文章讨论。大家关注的是 Connection Pool 的实现方法。下面给出一种实现方法。 <br /><br /><pre class="overflow">import java.sql.*;<br />import java.lang.reflect.*;<br />import java.util.*;<br />import java.io.*;<br /><br />public class SimpleConnetionPool {<br />　　private static LinkedList m_notUsedConnection = new LinkedList();<br />　　private static HashSet m_usedUsedConnection = new HashSet();<br />　　private static String m_url = "";<br />　　private static String m_user = "";<br />　　private static String m_password = "";<br />　　static final boolean DEBUG = true;<br />　　static private long m_lastClearClosedConnection = System.currentTimeMillis();<br />　　public static long CHECK_CLOSED_CONNECTION_TIME = 4 * 60 * 60 * 1000; //4 hours<br /><br />　　static {<br />　　　　initDriver();<br />　　}<br /><br />　　private SimpleConnetionPool() {<br />　　}<br /><br />　　private static void initDriver() {<br />　　　　Driver driver = null;<br />　　　　//load mysql driver<br />　　　　try {<br />　　　　　　driver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();<br />　　　　　　installDriver(driver);<br />　　　　} catch (Exception e) {<br />　　　　}<br /><br />　　　　//load postgresql driver<br />　　　　try {<br />　　　　　　driver = (Driver) Class.forName("org.postgresql.Driver").newInstance();<br />　　　　　　installDriver(driver);<br />　　　　} catch (Exception e) {<br />　　　　}<br />　　}<br /><br />　　public static void installDriver(Driver driver) {<br />　　　　try {<br />　　　　　　DriverManager.registerDriver(driver);<br />　　　　} catch (Exception e) {<br />　　　　　　e.printStackTrace();<br />　　　　}<br />　　}<br /><br /><br />　　public static synchronized Connection getConnection() {<br />　　　　clearClosedConnection();<br />　　　　while (m_notUsedConnection.size() &gt; 0) {<br />　　　　　　try {<br />　　　　　　　　ConnectionWrapper wrapper = (ConnectionWrapper) m_notUsedConnection.removeFirst();<br />　　　　　　　　if (wrapper.connection.isClosed()) {<br />　　　　　　　　　　continue;<br />　　　　　　　　}<br />　　　　　　　　m_usedUsedConnection.add(wrapper);<br />　　　　　　　　if (DEBUG) {<br />　　　　　　　　　　wrapper.debugInfo = new Throwable("Connection initial statement");<br />　　　　　　　　}<br />　　　　　　　　return wrapper.connection;<br />　　　　　　} catch (Exception e) {<br />　　　　　　}<br />　　　　}<br />　　　　int newCount = getIncreasingConnectionCount();<br />　　　　LinkedList list = new LinkedList();<br />　　　　ConnectionWrapper wrapper = null;<br />　　　　for (int i = 0; i &lt; newCount; i++) {<br />　　　　　　wrapper = getNewConnection();<br />　　　　　　if (wrapper != null) {<br />　　　　　　　　list.add(wrapper);<br />　　　　　　}<br />　　　　}<br />　　　　if (list.size() == 0) {<br />　　　　　　return null;<br />　　　　}<br />　　　　wrapper = (ConnectionWrapper) list.removeFirst();<br />　　　　m_usedUsedConnection.add(wrapper);<br /><br />　　　　m_notUsedConnection.addAll(list);<br />　　　　list.clear();<br /><br />　　　　return wrapper.connection;<br />　　}<br /><br />　　private static ConnectionWrapper getNewConnection() {<br />　　　　try {<br />　　　　　　Connection con = DriverManager.getConnection(m_url, m_user, m_password);<br />　　　　　　ConnectionWrapper wrapper = new ConnectionWrapper(con);<br />　　　　　　return wrapper;<br />　　　　} catch (Exception e) {<br />　　　　　　e.printStackTrace();<br />　　　　}<br />　　　　return null;<br />　　}<br /><br />　　static synchronized void pushConnectionBackToPool(ConnectionWrapper con) {<br />　　　　boolean exist = m_usedUsedConnection.remove(con);<br />　　　　if (exist) {<br />　　　　　　m_notUsedConnection.addLast(con);<br />　　　　}<br />　　}<br /><br />　　public static int close() {<br />　　　　int count = 0;<br /><br />　　　　Iterator iterator = m_notUsedConnection.iterator();<br />　　　　while (iterator.hasNext()) {<br />　　　　　　try {<br />　　　　　　　　( (ConnectionWrapper) iterator.next()).close();<br />　　　　　　　　count++;<br />　　　　　　} catch (Exception e) {<br />　　　　　　}<br />　　　　}<br />　　　　m_notUsedConnection.clear();<br /><br />　　　　iterator = m_usedUsedConnection.iterator();<br />　　　　while (iterator.hasNext()) {<br />　　　　　　try {<br />　　　　　　　　ConnectionWrapper wrapper = (ConnectionWrapper) iterator.next();<br />　　　　　　　　wrapper.close();<br />　　　　　　　　if (DEBUG) {<br />　　　　　　　　　　wrapper.debugInfo.printStackTrace();<br />　　　　　　　　}<br />　　　　　　　　count++;<br />　　　　　　} catch (Exception e) {<br />　　　　　　}<br />　　　　}<br />　　　　m_usedUsedConnection.clear();<br /><br />　　　　return count;<br />　　}<br /><br />　　private static void clearClosedConnection() {<br />　　　　long time = System.currentTimeMillis();<br />　　　　//sometimes user change system time,just return<br />　　　　if (time &lt; m_lastClearClosedConnection) {<br />　　　　　　time = m_lastClearClosedConnection;<br />　　　　　　return;<br />　　　　}<br />　　　　//no need check very often<br />　　　　if (time - m_lastClearClosedConnection &lt; CHECK_CLOSED_CONNECTION_TIME) {<br />　　　　　　return;<br />　　　　}<br />　　　　m_lastClearClosedConnection = time;<br /><br />　　　　//begin check<br />　　　　Iterator iterator = m_notUsedConnection.iterator();<br />　　　　while (iterator.hasNext()) {<br />　　　　　　ConnectionWrapper wrapper = (ConnectionWrapper) iterator.next();<br />　　　　　　try {<br />　　　　　　　　if (wrapper.connection.isClosed()) {<br />　　　　　　　　　　iterator.remove();<br />　　　　　　　　}<br />　　　　　　} catch (Exception e) {<br />　　　　　　　　iterator.remove();<br />　　　　　　　　if (DEBUG) {<br />　　　　　　　　　　System.out.println("connection is closed, this connection initial StackTrace");<br />　　　　　　　　　　wrapper.debugInfo.printStackTrace();<br />　　　　　　　　}<br />　　　　　　}<br />　　　　}<br /><br />　　　　//make connection pool size smaller if too big<br />　　　　int decrease = getDecreasingConnectionCount();<br />　　　　if (m_notUsedConnection.size() &lt; decrease) {<br />　　　　　　return;<br />　　　　}<br /><br />　　　　while (decrease-- &gt; 0) {<br />　　　　　　ConnectionWrapper wrapper = (ConnectionWrapper) m_notUsedConnection.removeFirst();<br />　　　　　　try {<br />　　　　　　　　wrapper.connection.close();<br />　　　　　　} catch (Exception e) {<br />　　　　　　}<br />　　　　}<br />　　}<br /><br />　　/**<br />　　 * get increasing connection count, not just add 1 connection<br />　　 * @return count<br />　　 */<br />　　public static int getIncreasingConnectionCount() {<br />　　　　int count = 1;<br />　　　　int current = getConnectionCount();<br />　　　　count = current / 4;<br />　　　　if (count &lt; 1) {<br />　　　　　　count = 1;<br />　　　　}<br />　　　　return count;<br />　　}<br /><br />　　/**<br />　　 * get decreasing connection count, not just remove 1 connection<br />　　 * @return count<br />　　 */<br />　　public static int getDecreasingConnectionCount() {<br />　　　　int count = 0;<br />　　　　int current = getConnectionCount();<br />　　　　if (current &lt; 10) {<br />　　　　　　return 0;<br />　　　　}<br />　　　　return current / 3;<br />　　}<br /><br />　　public synchronized static void printDebugMsg() {<br />　　　　printDebugMsg(System.out);<br />　　}<br /><br />　　public synchronized static void printDebugMsg(PrintStream out) {<br />　　　　if (DEBUG == false) {<br />　　　　　　return;<br />　　　　}<br />　　　　StringBuffer msg = new StringBuffer();<br />　　　　msg.append("debug message in " + SimpleConnetionPool.class.getName());<br />　　　　msg.append("\r\n");<br />　　　　msg.append("total count is connection pool: " + getConnectionCount());<br />　　　　msg.append("\r\n");<br />　　　　msg.append("not used connection count: " + getNotUsedConnectionCount());<br />　　　　msg.append("\r\n");<br />　　　　msg.append("used connection, count: " + getUsedConnectionCount());<br />　　　　out.println(msg);<br />　　　　Iterator iterator = m_usedUsedConnection.iterator();<br />　　　　while (iterator.hasNext()) {<br />　　　　　　ConnectionWrapper wrapper = (ConnectionWrapper) iterator.next();<br />　　　　　　wrapper.debugInfo.printStackTrace(out);<br />　　　　}<br />　　　　out.println();<br />　　}<br /><br />　　public static synchronized int getNotUsedConnectionCount() {<br />　　　　return m_notUsedConnection.size();<br />　　}<br /><br />　　public static synchronized int getUsedConnectionCount() {<br />　　　　return m_usedUsedConnection.size();<br />　　}<br /><br />　　public static synchronized int getConnectionCount() {<br />　　　　return m_notUsedConnection.size() + m_usedUsedConnection.size();<br />　　}<br /><br />　　public static String getUrl() {<br />　　　　return m_url;<br />　　}<br /><br />　　public static void setUrl(String url) {<br />　　　　if (url == null) {<br />　　　　　　return;<br />　　　　}<br />　　　　m_url = url.trim();<br />　　}<br /><br />　　public static String getUser() {<br />　　　　return m_user;<br />　　}<br /><br />　　public static void setUser(String user) {<br />　　　　if (user == null) {<br />　　　　　　return;<br />　　　　}<br />　　　　m_user = user.trim();<br />　　}<br /><br />　　public static String getPassword() {<br />　　　　return m_password;<br />　　}<br /><br />　　public static void setPassword(String password) {<br />　　　　if (password == null) {<br />　　　　　　return;<br />　　　　}<br />　　　　m_password = password.trim();<br />　　}<br /><br />}<br /><br />class ConnectionWrapper implements InvocationHandler {<br />　　private final static String CLOSE_METHOD_NAME = "close";<br />　　public Connection connection = null;<br />　　private Connection m_originConnection = null;<br />　　public long lastAccessTime = System.currentTimeMillis();<br />　　Throwable debugInfo = new Throwable("Connection initial statement");<br /><br />　　ConnectionWrapper(Connection con) {<br />　　　　Class[] interfaces = {java.sql.Connection.class};<br />　　　　this.connection = (Connection) Proxy.newProxyInstance(<br />　　　　　　con.getClass().getClassLoader(),<br />　　　　　　interfaces, this);<br />　　　　m_originConnection = con;<br />　　}<br /><br />　　void close() throws SQLException {<br />　　　　m_originConnection.close();<br />　　}<br /><br />　　public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {<br />　　　　Object obj = null;<br />　　　　if (CLOSE_METHOD_NAME.equals(m.getName())) {<br />　　　　　　SimpleConnetionPool.pushConnectionBackToPool(this);<br />　　　　}<br />　　　　else {<br />　　　　　　obj = m.invoke(m_originConnection, args);<br />　　　　}<br />　　　　lastAccessTime = System.currentTimeMillis();<br />　　　　return obj;<br />　　}<br />}</pre><br /><br />使用方法<br /><br /><pre class="overflow">public class TestConnectionPool{<br />　　public static void main(String[] args) {<br />　　　　SimpleConnetionPool.setUrl(DBTools.getDatabaseUrl());<br />　　　　SimpleConnetionPool.setUser(DBTools.getDatabaseUserName());<br />　　　　SimpleConnetionPool.setPassword(DBTools.getDatabasePassword());<br /><br />　　　　Connection con = SimpleConnetionPool.getConnection();<br />　　　　Connection con1 = SimpleConnetionPool.getConnection();<br />　　　　Connection con2 = SimpleConnetionPool.getConnection();<br /><br />　　　　//do something with con ...<br /><br />　　　　try {<br />　　　　　　con.close();<br />　　　　} catch (Exception e) {}<br /><br />　　　　try {<br />　　　　　　con1.close();<br />　　　　} catch (Exception e) {}<br /><br />　　　　try {<br />　　　　　　con2.close();<br />　　　　} catch (Exception e) {}<br /><br />　　　　con = SimpleConnetionPool.getConnection();<br />　　　　con1 = SimpleConnetionPool.getConnection();<br />　　　　try {<br />　　　　　　con1.close();<br />　　　　} catch (Exception e) {}<br /><br />　　　　con2 = SimpleConnetionPool.getConnection();<br />　　　　SimpleConnetionPool.printDebugMsg();<br /><br />　　}<br />}</pre><img src ="http://www.blogjava.net/machilansing/aggbug/61776.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/machilansing/" target="_blank">Lansing</a> 2006-08-04 15:56 <a href="http://www.blogjava.net/machilansing/archive/2006/08/04/61776.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>