实施研究知识库
研究一直是教育,特别是高等教育不可或缺的一部分。研究产生新知识,并提供创造性和独立性思维的训练。研究在高等教育中的作用体现在,世界上几乎所有著名的大学也被称为研究型大学。
现代研究高度专业化且高度协作。如今,找到引用超过100篇论文的文章很常见。此外,互联网的兴起使得快速发布研究成果成为可能,获取科学信息的门槛也大大降低。例如,仅在天体物理学领域,其互联网预印本服务每天就发布超过15篇新的研究论文,所有这些论文都可供公众免费访问。虽然这为研究人员进行高级研究提供了巨大的力量,但也带来了跟上最新成果的巨大负担。
这些情况引出了现代研究中的两个重要问题:我们如何组织知识以跟上最新发展,并在需要时能够检索信息?其次,我们如何组织和记录涉及多位研究人员的研究项目?在研究领域,新知识以出版物的形式呈现。我使用“参考文献”一词来指代知识片段,包括未发表或私下交流的提示和结果。在本文中,我将讨论以参考文献管理系统形式的研究知识库系统。
已经有一些商业尝试来解决上述问题。像 EndNote、Pro-Cite 和 PAPYRUS 这样的应用程序提供了参考文献管理功能,甚至一些网络功能。然而,我发现它们在我的研究中很难使用和定制。
研究涵盖人类知识的各个领域,更重要的是,研究旨在探索意想不到的事物。每个研究小组对于组织和展示其参考文献可能有不同的要求。大多数专有的参考文献管理系统都针对特定的研究领域(通常是医学研究)。作为闭源软件,用户不可能更改和改进软件以适应特定需求。
解决这个问题的最佳方案是设计一个开源的、基于网络的、多用户的知识库系统。它将运行在连接互联网的服务器上,并可从任何标准 Web 浏览器和所有平台访问。用户可以发布/组织参考文献,并在评论区进行讨论。所有知识和讨论互动都可以从一个安全的服务器集中存档。这种基于 Web 的方法将使用 Web 浏览器作为用户界面 (UI),并使任何人都可以通过简单地更改类似 HTML 的源代码来更改 UI。我对这种软件需求的答案是 OpenReference 参考文献管理系统。
编写此软件的一个目标是对知识进行分类,以便多个用户将来轻松检索。简单的关键词搜索是不够的,因为搜索不能保证找到所有相关的参考文献。如果您心中有一个特定的主题,浏览类别树会容易得多。因此,高级类别和用户管理是我决定实现的两个核心功能。
大的类别,每个类别包含数百个参考文献,与没有类别没什么两样。为了使分类有用,树上的叶子类别应包含少于一页或每个类别 20 个参考文献。这需要非常精细的类别结构。在更活跃的研究领域需要更精细的类别。然而,不可能预先判断任何领域需要多少级别的子类别才能进行有效的分类。唯一的方法是设计一个可以在运行时调整的动态类别结构。如果某个类别中出现大量参考文献,管理员可以根据这些参考文献的性质将其划分为几个子类别。
正如我所说,叶子类别需要专注于狭窄的主题,以保持参考文献的数量较少。今天的研究工作变得越来越跨学科,使得将参考文献分类到狭窄的类别中变得困难。解决这个问题的方法是允许一个参考文献与多个类别关联。
该系统不仅被设计为个人参考文献组织器,还被设计为在评论区交流想法的群组讨论服务器。基于网络的协作系统可以跟踪信息,并使想法交流的存档成为可能。
作为一个多用户系统,该软件必须建立一些用户访问控制。管理员可以设置策略来接受新用户。每个用户都需要使用合法的用户名/密码组合登录才能发布参考文献和评论。每个用户都可以编辑/删除/重新分类他或她自己的帖子。只有管理员可以触及类别结构。
最后,为了在论坛中启用私人对话,我还允许用户指定一个其他用户的列表,这些用户可以看到她或他的帖子。这些私人对话传统上发生在电子邮件通信中,但该软件鼓励用户使用 Web 系统,以便更好地存档研究成果。
系统的后端自然是关系数据库管理系统 (RDBMS)。我们想要存储/检索/搜索的大部分数据是文本的,而 RDBMS 非常适合此目的。任何启用 SQL 的数据库服务器都可以工作,并且有很多这样的服务器可供选择。我选择了 GPL 许可的 MySQL,因为它速度快且可靠。如果数据完整性和事务支持对您来说是必须的,您可以选择开源的 PostgreSQL 数据库服务器。
中间件是类/方法的集合,它们封装了数据查询操作。它们旨在使前端开发人员免受数据库连接和 SQL 语言的技术细节的影响。
我选择使用 Java 来开发中间件。使用 Java 的一个主要优势是它是一种成熟的、面向对象的语言,使得实现大型项目所需的复杂逻辑/结构设计变得容易得多。使用 Java 还允许我们利用大量已经作为 Java 库或 Bean 存在的实用程序类。我为这个项目使用的包括 JDBC 驱动程序、数据库连接池、会话管理和文本处理。
在 J2EE 中构建数据库中间件的标准方法是使用实体 EJB (Enterprise JavaBeans)。然而,这种方法需要运行 EJB 容器,这可能会很昂贵。事实上,很少有低成本的 JSP 托管服务提供 EJB 容器。对于 OpenReference 相对简单的数据库结构,我决定使用一种更简单的方法:辅助类中的静态方法来提供数据库访问。表中的每一行都由一个 HashTable 表示。
中间件使用 JDBC 在 Java 应用程序和 SQL 数据库之间传递信息。所有主要的 RDBMS 都有 JDBC 驱动程序。对于 MySQL,我使用了 mm.mysql 驱动程序。我为每个数据库表构建了一个类。该类知道该表中的字段,并知道哪些字段是可搜索的。每个类都实现了一组基本的数据查询函数(例如,getAllRows、AddRow、updateRows 等)和一个搜索函数,该函数搜索所有可搜索的字段并返回所有匹配的行。每个类也有自己的查询函数来执行特定的或跨表的查询。例如,在 ReferenceTable.java 类中,有一个函数 getReferencesByUserName。这将以用户名作为输入,并在 User 表中找到相应的用户 ID,然后从 Reference 表中返回用户 ID 匹配的行。例如,请参阅清单 1 [可在 ftp://ftp.linuxjournal.com/pub/lj/listings/issue91/4769.tgz 获取] 以获取 Category 类的完整 API。
我为前端选择了 JavaServer Pages (JSP)。JSP 具有完整 Java 语言的强大功能,以及将 Web 表示与应用程序功能分离的好处。JSP 支持所有用于格式化显示的 HTML 标签语法,并且可以在 HTML 注释中添加处理 Bean 和其他功能的完整 Java 程序。甚至可以设计自定义标签来封装后端操作(例如,数据库查询)。很容易培训 HTML 程序员/表示专家使用他们喜欢的 HTML 编辑器来处理网页,而无需关心数据库查询是如何执行的。同时,后端程序员可以处理数据查询部分,而无需关心数据将如何显示。《Linux Journal》2001 年 5 月至 7 月刊的 Reuven Lerner 的 At the Forge 专栏中有更多关于 JSP 的信息。
任何与 J2EE 兼容的 Java 服务器都能够运行 JSP。我最喜欢的是 Apache 基金会的 Tomcat 引擎和 Caucho Technology 的 Resin 引擎。它们中的任何一个都可以作为 Apache Web 服务器的扩展模块运行,以利用 Apache 的许多其他有用功能。Tomcat 在 GPL 下发布。Resin 是闭源软件,但可免费用于非商业用途。Resin 的运行速度明显快于 Tomcat,并提供一些有用的功能,例如内置的 JDBC 驱动程序和驱动程序池以及用于回退的多个 JVM。
最初我计划使用 XML 文件来表示类别结构,因为 XML 文档自然以树结构(DOM 模型)组织,并且易于人类阅读和编辑。Java 中有很多优秀的 XML-DOM 工具可以操作树。
然而,我们需要一个大型且动态的类别结构来进行准确的分类和浏览,因为能够搜索类别非常重要。使用 XML 的一个缺点是很难将其与存储在 RDBMS 中的其他内容一起搜索。此外,将一个大的解析后的 DOM 对象 постоянно 存储在内存中效率低下且难以在多个 JVM 之间同步。将其存储在磁盘上并在需要时解析它会带来过多的处理开销。因此,我决定使用数据库表来存储类别。
整个类别都存储在一个数据库表中。每个记录代表一个类别,并具有其唯一的类别 ID。它还具有父类别 ID,以便将记录链接在一起形成树。参考文献通过单独的类别 ID 与参考文献 ID 表链接到类别。
中间件中的 Category 类包含操作树的所有方法。为了维护类别表的链接和结构完整性,重要的是我们仅通过 Category 对象的公共方法来操作类别表。一些重要的方法包括
在树的任何级别或级别之间插入新的子类别。关于在叶子类别下添加新子类别的特别说明:由于所有参考文献都必须仅与叶子关联,因此与旧叶子关联的参考文献现在与新的子类别关联。
更改类别描述/关键词/属性。
从树的任何级别删除类别,使目标类别的子类别成为其父类别的子类别,然后从表中删除目标类别本身。
返回给定类别的子类别(或父类别)列表。
从类别描述中搜索关键词。
清单 1 中提供了 Category 类源代码的说明性列表。我发现这些方法足以满足我的使用。您可能想要执行更复杂的操作,例如将子树移动到另一个分支等等。开源软件的优势在于任何人都可以向代码添加功能,而无需重写基本部分。
用户登录和会话通过 J2EE 的会话 API 进行管理。用户名和密码从数据库表中授权,然后 servlet 引擎为此用户建立会话。servlet 引擎通过会话对象跟踪会话。
会话对象需要知道拥有它的用户。这包括用户名、个人资料、首选项和当前浏览状态。将会话对象中存储此类信息可以大大提高性能。否则,例如,程序每次在为用户显示页面之前都必须查找数据库以获取显示首选项。
相反,我可以将会话对象本身内部存储所有信息。但为了更好地组织,我使用了几个与会话对象关联的 JavaBeans 来存储额外的用户信息。在 JSP 规范中,JavaBean 可以具有会话或页面的作用域。带有 JavaBeans 的会话大大简化了开发工作。
数据库连接的建立成本很高。每次我们需要查询时都打开新连接会大大降低计算机的速度并迅速耗尽系统资源。
有几种方法可以减少对新连接的需求。一种方法是为每个会话分配一个连接。该连接可以存储在具有会话作用域的 Bean 中,并且来自该会话的所有查询都通过它进行。但是,如果同时有很多会话处于活动状态,则可用连接会很快用完。此解决方案的可扩展性不佳。
另一种选择是为每个页面分配一个连接。但是,我不喜欢这个想法;连接对象必须由 JSP 编码器传递给中间件。这对于非技术 JSP 编码器来说是不直观的,并且违背了将表示与应用程序逻辑分离的目标。可以设计一组自定义 JSP 标签来封装数据库连接,以便 JSP 编码器看不到它们,但这需要额外设计工作,并且 JSP 编码器需要学习非标准语言。从长远来看,使用自定义标签肯定会提高生产力,但也会使短期和简单的更改更加困难,并且系统的学习曲线更陡峭。我仍在寻找最佳解决方案。目前,我决定将连接处理完全隐藏在中间件中。
我在中间件的每个数据库查询函数中都建立了一个新的数据库连接。每个查询函数完成多个相关的查询,以有效利用连接。每个 JSP 页面仅调用一到两个这样的函数。
为了减少打开新连接造成的开销,我使用了“连接池”实用程序。这些实用程序是维护内存中打开连接池的类。当用户请求新连接时,它只是从其池中获取一个连接,而不是创建一个新连接。当用户关闭连接时,它会返回到池中。有几个这样的实用程序,例如 PoolMan。它们的使用非常简单,您只需要进行最少的更改即可转换您的代码以利用这些实用程序。
我们必须允许用户在文本中混合一些 HTML 标签,以便他们可以正确地格式化备忘录和评论。然而,显示用户 HTML 存在很大的安全风险:未经授权的用户 HTML 标签可能会损坏/更改页面导航和其他人的评论的显示。然后,它们可以用于加载未经授权的图像/小程序,甚至使用 JavaScript 将用户重定向到另一个站点。
因此,我决定仅允许四个 HTML 标签:<p>、<b>、<i> 和 <a>。这确保了用户无法更改除他或她自己以外的任何内容的格式。未经授权的 HTML 标签在将文本提交到数据库之前会被过滤掉。允许的 HTML 标签可以在编译时由站点管理员配置。
还需要注意的是,一些用户可能想要在备忘录或评论中输入包含“<”的 XML 示例代码或数学公式。将所有“<”都视为 HTML 标签标记是不合适的。因此,我提供了纯文本模式,在该模式下,所有“<”符号在提交到数据库之前都会转换为“<”。
处理文本的一种强大方法是使用正则表达式。已经有几个好的 Java 正则表达式引擎可用。我选择了 gnu.regexp,因为它是一个免费软件实现,几乎包含了所有 Perl5 正则表达式功能,通过一个简单直观的 API。使用正则表达式引擎过滤 HTML 标签和组合 SQL 查询字符串的代码在清单 2 [可在 ftp://ftp.linuxjournal.com/pub/lj/listings/issue91/4769.tgz 获取] 中列出。

Michael Yuan 是德克萨斯大学奥斯汀分校天体物理学博士候选人。他研究遥远的类星体(200多亿光年之外),以了解我们宇宙的历史和演化。当他不观测类星体时,他喜欢使用 Java 和 Perl 等人间语言开发有用的软件。