大凡使用过JDBC连ORACLE的人都会知道这样一个事实:我们需要的库文件classes12.zip存在于$ORACLE_HOME/jdbc/lib目录下(但仍有部分菜鸟每每在论坛上求此库文件,真是辛苦);但很少有人知道ORACLE还为我们准备了使用jdbc的示例,它存在于 $ORACLE_HOME/jdbc/demo/demo.zip 中。
前一段时间,我学习ORACLE的OOP技术,很受触动。当时我想:JDBC中一定存在某些OO技术以支持它。很久都没有找到合适的例子,最后在ORACLE的安装目录下找到它。真可谓是:踏破铁鞋无觅处,得来全不费工夫。
约定:
1、如果出现 java.lang.UnsatisfiedLinkError: do_open,则你需要把 DriverManager.getConnection() 方法的 url 修改成 jdbc:oracle:thin:@127.0.0.1:1521:oradb,具体原因未知;
2、如果出现 java.sql.SQLException: 不支持的字符集: oracle-character-set-852,则你需要把 nls_charset12.zip加入你的工程中(此文件与 classes12.zip 同目录);
下面我就把文件夹\samples\oci8\object-samples下的文件做一个详细的功能描述:
1、PersonObject.java
这个例子演示了表 people 中存在ADT字段 empid,其类型为 PERSON,而且类型 PERSON中存在ADT字段 home,其类型为 ADDRESS,而且类型 ADDRESS是一个ADT。
如果使用常规SQL语句,其插入语句与在sql/plus中无异,即:使用构造函数嵌套构造。
另有一种方法,使用 STRUCT 的构造函数 STRUCT(StructDescriptor, Connection, Object[]) 构造出一个STRUCT对象,即一个ADT对象。同时,如果有嵌套则需要嵌套构造ADT对象。最后通过 PreparedStatement的 setObject方法指定ADT对象即可。
读取数据时则采用与上述方法相逆的办法:如果是简单类型,则直接读取;如果是ADT,则使用ResultSet的getObject(),再强制转换成STRUCT,然后调用STRUCT的getAttributes()方法取得 Object[] 类型数据,如是递归。
2、SQLDataExample.java与EmployeeObj.java
此例与1中相似,也是对ADT的处理,不同的是类型没有嵌套。
比较而言,2比1的代码简洁了很多,不过也是付出了代价:为类型 EMPLOYEE 抽象出一个类EmployeeObj,它实现了SQLData接口,并重写了三个方法(必须的)。
前台的调用比1中的第二种方法简洁了很多,只需要直接使用PreparedStatement的 setObject方法指定ADT对象即可(不过需要指定其类型为OracleTypes.STRUCT)。读取时也可直接使用OracleResultSet的getObject()并强制转换成EmployeeObj对象即可。
真可谓是:有得必有失!!
另,此例中有几处需要注意的地方:
2.1 EmployeeObj.java中的 import oracle.jdbc2.*; 改成 import oracle.jdbc.*; 原因未知;
2.2 SQLDataExample.java 中的 Dictionary map = conn.getTypeMap(); 改成 java.util.Map map = conn.getTypeMap(); 原因:NOTE: This class(指的是Dictionary) is obsolete. New implementations should implement the Map interface, rather than extendidng this class.(来源:javadoc);
2.3 SQLDataExample.java 中的 pstmt.executeQuery(); 改成 pstmt.executeUpdate(); 更合理些,因为这是更新而非查询(不改也不会影响功能,只是建议);
3、CustomDatumExample.java与Employee.java
此例与2完全相同。不同的是采用了另外一种抽象技术,并实现了接口CustomDatum 与 CustomDatumFactory,并重写了二个方法toDatum()与create()。在前台访问数据时亦有少许不同:采用了OracleResultSet的getCustomDatum()方法并把它强制转换成Employee。从外观上看,2中SQLData接口存在于 java.sql.* 包中;而接口CustomDatum 与 CustomDatumFactory则存在于oracle.sql.*包中,可以认为是Oracle公司对自己产品的专门实现。或许有更高的性能、更小的开销?不过3不如2来得直接,个人认为。
4、ArrayExample.java
从文件名可看出,此示例演示的是VARRAY类型。
同1,插入亦有两种方法。一种可直接使用SQL;另外,可使用OraclePreparedStatement的setARRAY方法,构造ARRAY的过程与构造 STRUCT 无异。数据的读取过程与此相反:先使用OracleResultSet的getARRAY()方法取得ARRAY对象,再调用此对象的getArray()方法并强制转换成对象数组,然后对此数组操作即可。
5、PersonRef.java与StudentRef.java
不知是不是ORACLE与我们开玩笑,这两个文件除了类名不同外,其余一切相同。
这个例子给我们演示的是对象表的概念。对象表的插入与普通表没有任何不同,数据的读取首先体现在其SQL上,需要使用REF函数,然后调用ResultSet的getObject()方法并强制转换成REF对象,再利用此对象的getValue()并转换成STRUCT对象,再利用此对象 的getAttributes()方法得到Object[],然后对此数组操作即可。
6、RefClient.java与GenREF.java
This sample demonstrates using REF over two different Sessions.
类GenREF用来封装REF对象所指向的类型及其二进制内容。程序先Materialize into GenREF,然后在另一会话中De-materialize REF from GenREF,下面就与处理REF相同:利用此对象的getValue()并转换成STRUCT对象,再利用此对象 的getAttributes()方法得到Object[],然后对此数组操作即可。
7、FileExample.java与PLSQL_FileExample.java
此示例使用 BFILE 数据类型,Contains a locator(定位器) to a large binary file stored outside the database。数据插入时需要使用函数 bfilename,这个函数需要目录对象,此对象需要使用create directory mydir来创建(此用户需具有CREATE ANY DIRECTORY系统权限)。我曾在这个地方有个大教训:按着步骤做,可在读取时总是提示我没有此目录!!最后,我把BFILENAME函数的第一个参数(即目录名)大写就好了。原因:目录名在函数中区分大小写,虽然我们创建它时用的是小写,可到了数据库中后就自动变成了大写。
读取BFILE类型的数据时,首先通过其getBinaryStream()方法得到二进制的输入流,然后操作这个输入流就可以了。The limitation is your imagination。
PLSQL_FileExample.java实现的功能与上述的相同,只不过所有针对BFILE的操作都是通过调用pl/sql匿名块来完成的,并使用了OracleCallableStatement。如果没有必要,使用前者即可。
8、LobExample.java与PLSQL_LobExample.java
此示例使用了CLOB和BLOB数据类型。
插入数据时,需要分别通过getBinaryOutputStream()和getCharacterOutputStream()得到二进制输出流和字符输出流,然后就是针对此输出流的操作了,与传统的 java I/O 相同。
读取数据时,需要分别通过getBinaryStream()和getCharacterStream()得到二进制输入流和字符输入流,然后就是针对此输入流的操作了,与传统的 java I/O 相同。
PLSQL_LobExample.java实现的功能与上述的相同,只不过所有针对CLOB和BLOB的操作都是通过调用pl/sql匿名块来完成的,并使用了OracleCallableStatement。如果没有必要,使用前者即可。
注:7、8的第二个示例中均使用到了dbms_lob程序包。
上面我仅对ORACLE给我们提供的demo的极小的一部分给出了解释,并加入我自己的看法。我也是想通过这篇文章,起到抛砖引玉的作用,希望广大网友多对产品的示例进行分析,从而得到最原汁原味的代码。
你的意见,我愿意听!! email:zbdlmxc@163.com