当前位置:主页   - 电脑 - 程序设计 - JAVA
JavaBeans和关系数据库的镜像
来源:网络   作者:   更新时间:2012-06-15
收藏此页】    【字号    】    【打印】    【关闭

   摘要

  JDK1.1包括了新的数据库存 取(JDBC)及组件(JavaBeans)的应用程序接口(APIs)。这两个API结合在一起,可用来开发通用 数据库代码。通过用唯一的一个类去存取任何一种JDBC数据库(封装于不同组件中的各个应用 程序有着其具体的编码),用户就不必因为数据库结构一点点的细小变化去修改数据库编码。

  一个关系数据库基本上包括一系 列相互关连的表,在每一个表中存有一类与应用系统相关的数据。例如一个地址簿数据库中,可 能有关于人员、住址、电话号码等方面的表。在数据库中,每一个这样的实体将被作为一系列的 字符串,整数及其它原始数据类型存贮起来。数据库中,表的定义将描述每一种与实体相关的信 息如何在一个表的字段中存储。例如,你可以在一个名为“人”的表中,有两个字段别表示所存字 符串为“姓”和“名”。每一张表应当有一个或几个字段值作为标识,确保每条记录的唯一性。这些 标识或“键”可以用来连接存在于不同表中的信息。例如你可以在“人员”表中,为每个人指定唯 一的“人员号码”的键值,并在“地址”表中的相应字段中使用同一个键值。这样,你可以通过对两 个表中的“人员号码”字段值的匹配,使每一个人和他的地址关联起来。

  关系数据库系统出现于七十年代, 时至今日,它仍然是存储巨量数据的主要方式。因而,Java软件工具有必要具备处理关系数据库 的能力。

  关系数据库要想被某个Java应用 程序利用,首先需要解决两个问题。第一:需要某些基础的中间件来建立与数据库的连接,向数 据库发出SQL查询等等;第二:操纵数据库的处理结果要与操纵任何一种Java信息一样方便—— 作为一个对象。前一个问题已被SUN及几个数据库产商解决;后一个问题则有待我们进一步去探 究。

  在为普通的程序开发业务定义大 量的APIs这项工作上,SUN一直保持着与许多软件公司的合作关系。在JDK1.1APIs中,JDBC 的API是最早建立起来的。而且,它已得到了为数众多的应用。这些应用中,有的是100%的 纯Java,有的则是Java和其它程序的混合体,如:用现有的ODBC数据源进行连接(参看 图1)。JavaSoft已将一个关于现有的JDBC驱动程序的介绍放在它的Web站点 上(http://splash.javasoft.com/jdbc/jdbc.drivers.html)。

  

  图1一个典型的JDBC或JDBC/ODBC配置

  注意:此图已被简化。另外的组件 已包括其中(如ODBD驱动程序)

  非常明显,这些应用的优缺点取决 于你的环境和设置,在此我不准备对它们的各种情况进行逐一论述。在下面的内容中,我们假 定,在你的机器中已拥有某种Java开发环境,并且你已正确地安装并测试过某个JDBC驱动程序, 或者运用过某种JDBC驱动程序及SUN的JDBC/ODBC桥。

  JDBCAPI

  JDBCAPI作为一个单独的Java 包(或类库,即java.sql)出现,包括有一系列的类。这些类提供了处理某个关系数据库的中间 件。本质上讲,它们使得你可以关联某个数据库,并向其发出查询。你可以对这些查询结果进行 处理,检索你数据库的meta-信息(meta-information),并且处理在此间可能发生的各种异常情况。

  让我们来看一个简 单的JDBC例子,看一看应用了JavaJDBC之后,查询会得到怎样的简化。表1是一个极其简单的数 据库。在清单1中的编码是一段最简单的对关系数据库进行SQL查询所需的Java语句。

人员 #
43674SandorSpruit
90329JohnDoe
65435DonaldDuck
String ur1="jdbc:odbc:sample";
String query="SELECT * FROM PERSON";
boolean more;
try
{
Class.forName("sun.jdbc.odbc.jdbcOdbcDriver");
Connection con = DriverManager.getConnection(ur1,"sandor","guest");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
While (more = rs,next())
{
int number = rs.getInt("PERSON#");
String firstName = rs.getString("FIRST_NAME");
String lastName = rs.getString("LAST_NAME");
System.out.printIn(number + " " + firstName + " " + lastName);
}
rs.close();
stmt.close();
con.close();
}
catch(SQLException ex)
{
ex.printStackTrace();
}

  >单1:一个应用了JDBC的SQL查询

  这段编码的含义是:先装入SUN 的JDBC/ODBC驱动程序,然后与被jdbc:odbc:sample指定的数据库建立起一个关联,最后对该数 据库进行一个简单的SELECT查询。如果没有遇到查询异常(SQLException),程序将循环地从结 果集(Result Set)中每次抽出一条数据库记录,并将其显示在屏幕上。

  好了,现在我们来看一看这段程序 还有哪些不足?在清单1的这类程序中,存在着两个根本性的错误:

  1.这种编码用到了数量众多的 数据库meta-信息,而这些信息都只能手工编码到程序中。当你想要取一个数值时,你必须提前 知道你将取到的数值是一个整数、浮点数、还是一个双精度数。这将会使得编写一个可以处理任 何一个数据库的类变得十分困难;并且每一个数据库的细小调整都会逼你去仔细地检查和修改 程序。

  2.数据库中的信息总是作为一 个单个的RecordSet(记录集)实例来传递,而这种实例并不是一个真正的对象。RecordSet类( 与其它的数据库类封装没什么差别)更象一个指针或游标,借助方法,它能够提供存取数据信息 的途径。RecordSet中的实例实际上并不包括信息,它们仅仅表示获得信息的方式。这正说明了 当你要调用另外的Record Set方法去获取某些真实的数据信息的时候,你必须通过Record Set去 做更多的工作(利用RecordSet.NEXT()去移动指针)。实际上,JDBC类的确仅仅传递这类联系松 散的字段。即使你完全了解数据库的所有内部细节,这也没有任何价值,因为在Java提供了存储 和处理信息的方法。

  所以,理想的状态是,有一种好的 方法,能够逐一地从数据库中抽取记录或字段(通过Record Set),并且将取到的信息“填”入到新 生成的对象之中。

  这一解决方式的关键在于关系数 据库(RDB)和面向对象的数据模型(ODB)之间的相似性。RDB中的表和ODB中的类的作用很相似, 而记录和对象也有着某些相同的属性。你可以将记录看作是用来初始化某个对象的数据元素的 数据组。如果你已将记录从数据库中抽取出来,就必须调用类构造函数来生成上面所说的对象。 若能够将每一条记录自动地传到适当的构造函数中,就可以轻而易举地由记录来构造对象。

  在开发一个小的应用程序时,有可 能将每一个记录传递给某个构造函数,以此来生成新的对象。你可以经常利用对象参照来操纵 从数据库中抽取的任何数据。因为你通过Record Set所得到的每一个对象最终都 是java.lang.Object的扩充。你可以定义一个BibClass,使其具有各类的共同属性,Big Class类 操作将利用Javainstanceof算子来实时决定运行中所遇到的数据库信息,并且通过一个大 的switch选择,跳到相应的程序段中。

  你也可以定义一个相似的带有多 个构造函数的BigClass,每个构造函数有差别不太大的调用参数。你可以 用BigClass(int,int),BigClass(int,float)等等,取决于你从RecordSet中循环取出的数据的 类型(这一方法当然将会包括许多冗余代码)。

  然而,以上两种方法都不能真正解 决问题。因为记录和构造函数在程序中的关系仍将是僵硬的。若想得到通用的数据库编码,必须 自动地建立数据库和构造函数二者的关联。

  Java的性能对于此时的我们就如 雪中送碳。清单2中的程序片段只需一个类名就可以建造一个Java类。这样,我们就可以凭借类 和表的名称来识别那些可以处理从表中抽取出的记录的构造函数。利用标准的JDBC类,可以容 易地获得所有表的表名,在此,我们将要充分利用这个Java小技巧。只要简单地为每个数据库表 开辟一个Java类,使类名和表名相互匹配,无论何时,每当从表中抽取出一条记录的时候,通过 将表名传递给Class.forName(),程序将自动生成一个对象。

Class c = class.forName("Person");
Person p = (Person)c.newInstance();
System。out.println("... just created a " + c.getName();

  清单2:一个简单的Class.forName()例子

  然而,此处还有一些问题。由于对 某些特定的类来说,forName()函数需要调用参数为void的构造函数,所以不能将RecordSet变 量直接传递给构造函数。在这里,我们需要一个初始化函数,把从数据库中抽取出的记录作 为RESULTSET参数,将其值赋予对象的数据元素。一个好的方法是引入超级类,并将其作为所有 数据库表相关类的通用父类。实际上,这个超级类在数据库查询中充当着重要的角色,我们将在 下面展示这一点。

  查询数据库

  利用上面的方法可以由记录生成 对象,但是你仍然得用SQL语句来查询数据库,这需要对数据库结构有深入的了解。这还是没有 解决问题,虽然我们能够自动地匹配数据库表和类的名字,但是还是必须手工编写SQL语句。这 就是说每次修改数据库结构后,将不得不手工编辑这些查询语句。不过,我们仍然可以利用前文 所述的方法来越过这个障碍。通常而言,查询关系数据库时,你将会用到属于主键或索引的字段 名和值。一言弊之,如果某人向你提供了适当的字段名和字段值k你就可以从相应的数据库中抽 取符合要求的记录(或字段)。而DatabaseMetaData对象不但可以被用于检索一系列的表名(见 上所述),而且可以获得一系列的主键及索引字段。上面的问题由此可以迎刃而解。

  通过填入一系列适当的(字段名,字段值)对,可以利用相对而言少得多的代码实现对关系数据库的查询。你可以将对子中的所有字段名和数据库中的主健及索引字段相匹配。每当你找到了名字列表中相应的主健或索引字 段,可以根据相应的数值来生成一个SQL语句,执行它来获取RecordSet,并通过Class.forName()构造机制将结果转化为对象。

  实现这一想法要求可以以(名,值)对的方式对与数据库表相关的每个类的数据元素进行存取。但是这种方法只有通过上节所述的通用父类才能趋于完美。清单3和4利用伪码表示了这一方法。

Open the database connection
Retrieve a list of user defined tables
for each table
{
Check where there is a corresponding class file
if(it is availabe)
{
load the class file
Retrieve lists of key fields and indeces for this table
Store these lists in hashtables for easy access
}
else throw an exception
}
  清单3: 初始化数据库连接的伪码

    Take an object A containing a series of (name,value) pairs
for each table T
{
for each (name,value) pair
{
if(name matches primary_key_field or index_field)
store a refrence to both name and value
}
if all key_fields were found
create a query string using key names and values
else if all index_fields were found
create a query string using index names and values
execute the query to obtain a ResultSet
For each record in the ResultSet
{
Create an object of the class associated with table T
initialize the object using the record's contents
Add the object to the results, e。g。, attach it to A
}
}

  清单4:描述数据库查询的伪码

  Java镜像和Java beans

  Java1.1开发套件(JDK)的引入, 为我们带来了许多强大的新性能,例如全新的用户界面接口类。有两个新的JDKAPI尤其值得注 意:镜像机制(java.lang.reflect包)和JavaBeans组件的应用程序接口(java.beans包)。这两 个API将会帮助我们创建高明的数据库类,使我们可以利用有关类的meta-信息,以此来解决开 发通用数据库类中的问题。

  拥有forName()和newInstance() 方法的Class类,仅仅是镜象(reflection)功能的一个简单例子。真正重要的是,forName()字符 串参数不必须是源程序中出现的字符串。只要给出一个名字(这个名字可从任何地方取来), 你就可以载入并实例化任何一个类。对于我们的数据库类,我们可以直接从数据库自身的表名 中得到类名。这就是说,与数据库表相关的Java类名并不需要出现在源程序中。相应地,当表名 改变或某个表被加入到数据库中时,不需要修改源码,只要确信带有新名字的类已存在你的系 统中。

  镜像类意味着可以在实时运行中 获取、存储和处理Java程序中的类信息。它们的实例能够象任何Java对象一样被运用,你可以象 修改字符串和整数一样,去修改类、数据类型、返回类型、方法参照和参数。在源程序级,这个镜 像的概念看起来并没有什么价值——因为可以应用你自己的编码直接存取你所需要的有关类、 方法及参数的所有信息。但是,镜像(reflection)将会在java的编译文件中发挥作 用。JavaBeansAPI的作用是:通过应用程序的构造机制利用来自于全然不同的开发者或产商所 编写的类。

  JavaBeans规范为类成员的名字 制定一系列的条例。以确保方法函数的名字能系统地描述它们的功能。任何一个符合规则 的Java类都可以被一个Bean的内化实例(通过镜像)检查,以揭示其行为的重要特征——诸如对 于什么样的事件类将有所响应,以及该类将会产生什么样的事件等等。任何符合这些规范的类 都是高效的Bean,因而是一个组件。在理论上,这意味着你可以从各种来源收集一系列beans,当 需要它们时可以将其其实时地绑在一起。

  一个Bean的例子

  在下面一个名为Translation 的Bean中,有一个构造函数和两个方法来操作一个名为“language”的属性。这里我想强调的是, 既然你可以通过检查一个类的代码来了解它的构造函数、方法及属性,那么Bean的内化 器(Introspector)也能做到。

  public class Translation extends Object
{
int language;
  public translation()
{
}
  public int getlanguage()
{
return(language);
}
  public void setLanguage( int language)
{
this。language=language;
}
}

  清单5:个非常简单的Bean

  一个BeanIntro spector能够提供 许多数组的Property Descriptor实例,这些实例包含所有Bean的属性的类型信息,即例子中 由get/set方法所定义的类型。你可以调用这些方法(利用reflection)来读或写这些属性。

  镜像机制(reflection facilities)为我们检查原本松散的类和数据库表的完整性提供了的更好方法。实际上,仅仅 通过类名和一个表匹配并不一定能够保证一些类内部的一致性。一个与表相关的类显然应当具 备存储数据库表中所有字段的数据元素。一个类可能有适当的名字,但其初始化代码可能会省 略。它可能只有一个正确的名字,而其数据成员可能有不同的名字或者是不同的类型。使用JDBC 的DatabaseMetaData及镜像机制可以检查它们是否完全匹配!

  引用一些JDBC调用去获得现实中 必须的数据库信息,通过正确的名字去检查你系统中的类,并且通过镜像去比较表和类的属性, 这实实在在是一块香饼!

  结论和启示

  JDBC,镜像和Java Beans三者的结 合,能够方便地从关系数据库中存取记录并利用记录来初始化组件(不仅仅是对象)。为了实现 上述操作,无需修改你的数据库,只需确认你的类符合Bean规范,并使类属性和表字段相互匹 配。Beans还有其它一些简单的技巧,可使编程更加有趣。Beans能够提供自己的用户界面组件, 并且Beans规范还包括一个名为Customizers的东西。你可以引入另外的类专门地去察看、编辑 和自行定制一个Bean类的实例。(关于定制Beans,请参看下一期《定制你的Java》

  总之,我们可以为数据库类编写自 行定义的类,应用程序能够从关系数据库中抽取数据,通过实例化某个类得到新的实例,并引入 相关的图形用户界面(GUI)组件来查看和编辑数据。所有这些都由通用代码完成,因而能够处理 任何数据库。利用Java编写的、功能齐全的数据库查看/编辑器正迎我们而来。

其它资源
来源声明

版权与免责声明
1、本站所发布的文章仅供技术交流参考,本站不主张将其做为决策的依据,浏览者可自愿选择采信与否,本站不对因采信这些信息所产生的任何问题负责。
2、本站部分文章来源于网络,其版权为原权利人所有。由于来源之故,有的文章未能获得作者姓名,署“未知”或“佚名”。对于这些文章,有知悉作者姓名的请告知本站,以便及时署名。如果作者要求删除,我们将予以删除。除此之外本站不再承担其它责任。
3、本站部分文章来源于本站原创,本站拥有所有权利。
4、如对本站发布的信息有异议,请联系我们,经本站确认后,将在三个工作日内做出修改或删除处理。
请参阅权责声明