DM4 JDBC编程指南
JDBC(Java Database Connectivity)是Java应用程序与数据库的接口规范,旨在让各数据库开发商为Java程序员提供标准的数据库应用程序编程接口(API)。JDBC定义了一个跨数据库、跨平台的通用SQL数据库API。
DM4 JDBC驱动程序是DM4数据库的JDBC驱动程序,它是一个能够支持基本SQL功能的通用低层应用程序编程接口,支持一般的SQL数据库访问。
通过JDBC 驱动程序,用户可以在应用程序中实现对DM4数据库的连接与访问,JDBC驱动程序的主要功能包括:
建立与DM4数据库的连接;
转接发送SQL语句到数据库;
处理并返回语句执行结果。
1.1 数据类型
Java在java.sql.Types中定义了JDBC所支持的标准SQL数据类型,这些SQL数据类型主要有:
BIT——单个二进制位;
TINYINT——1字节整数;
SMALLINT——2字节整数;
INTEGER——4字节整数;
BIGINT——8字节整数;
FLOAT——4字节浮点数;
REAL——4字节浮点数;
DOUBLE——8字节浮点数;
NUMERIC——带有精度和刻度的精确整数;
DECIMAL——带有精度和刻度的精确的浮点数;
CHAR——固定长度的字符串;
VARCHAR——可变长度的字符串;
LONGVARCHAR——可变长度的字符串;
DATE——日期类型;
TIME——时间类型;
TIMESTAMP——时间戳类型;
BINARY——固定长度的二进制数据;
VARBINARY——可变长度的二进制数据;
LONGVARBINARY——可变长度的二进制数据;
BLOB——二进制大对象;
CLOB——字符大对象;
DM4 SQL数据类型与此略有不同,例如DM4不支持某些类型,但又扩展了某些类型如多媒体数据类型,与上述类型不是一一对应。另外,Java也包括一套与其相似但略有不同的数据类型。因此,在驱动程序中必须将三者作一个映射对应。标准SQL数据类型、DM4 SQL数据类型、Java数据类型三者的对应关系如表1.1.1所示。
表1.1.1:数据类型对应关系表
标准SQL数据类型 DM4 SQL数据类型 Java数据类型
CHAR CHAR String
VARCHAR VARCHAR String
LONGVARCHAR TEXT String
CLOB TEXT java.sql.Clob
BOOLEAN BIT Boolean
BIT BIT Boolean
TINYINT TINYINT Byte
SMALLINT SMALLINT Short
INTEGER INTEGER Int
BIGINT BIGINT Long
REAL FLOAT Float
FLOAT FLOAT Float
DOUBLE DOUBLE Double
NUMERIC DECIMAL java.math.BigDecimal
DECIMAL DECIMAL java.math.BigDecimal
BINARY BINARY byte[]
VARBINARY VARBINARY byte[]
LONGVARBINARY BLOB byte[]
BLOB BLOB java.sql.Blob
DATE DATE java.sql.Date
TIME TIME java.sql.Time
TIMESTAMP DATETIME java.sql.Timestamp
1.2 提供的对象和接口
DM4 JDBC驱动程序有两种JDBC驱动程序,DM4 JDBC 2.0驱动程序符合JDBC2.1标准,它实现了JDBC2.1核心API要求必须实现的全部接口:
java.sql.Driver
java.sql.Connection
java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
java.sql.ResultSet
java.sql.ResultSetMetaData
java.sql.DatabaseMetaData
同时,它还实现了JDBC2.1扩展(可选)API的部分接口:
javax.sql.DataSource
javax.sql.ConnectionEvent
javax.sql.ConnectionEventListener
javax.sql.ConnectionPoolDataSource
javax.sql.PooledConnection
JDBC 3.0 规范不再区分核心API和扩展(可选)API,它把这两种API都包括进去,又增加了一些新的特性。DM4 JDBC 3.0驱动程序符合JDBC3.0标准,它兼容DM4 JDBC 2.0,实现了JDBC3.0规范所要求的下列接口:
java.sql.Driver
java.sql.Connection
java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
java.sql.ResultSet
java.sql.ResultSetMetaData
java.sql.DatabaseMetaData
java.sql.Blob
java.sql.Clob
java.sql.PrameterMetaData
java.sql.Savepoint
javax.sql.DataSource
javax.sql.ConnectionEvent
javax.sql.ConnectionEventListener
javax.sql.ConnectionPoolDataSource
javax.sql.PooledConnection
目前DM4提供JDBC 2.0和JDBC 3.0两种驱动程序。
1.2.1 DriverManager
1. 概述
DriverManager 类是 JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,并在数据库和相应驱动程序之间建立连接。另外,DriverManager 类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等。
对于简单的应用程序,一般程序员只需要使用getConnection来建立与数据库的连接。
2. 加载DM4 JDBC驱动程序
dm4.jdbc.driver.Dm4Driver类包含一静态部分,它创建该类的实例。当加载驱动程序时,驱动程序会自动调用DriverManager.registerDriver方法向DriverManager注册自己。通过调用方法Class.forName(String str),将显式加载驱动程序。以下代码加载DM4的JDBC驱动程序:
Class.forName("dm4.jdbc.driver.Dm4Driver");
3. 建立连接
加载DM4 JDBC驱动程序并在DriverManager类中注册后,即可用来与数据库建立连接。DriverManager对象提供三种建立数据库连接的方法。每种方法都返回一个Connection对象实例,区别是参数不同。
Connection DriverManager.getConnection(String url, java.util.Properties info);
Connection DriverManager.getConnection(String url);
Connection DriverManager.getConnection(String url, String user, String password);
通常采用第三种方式进行数据库连接,该方法通过指定数据库url、用户名、口令,来连接数据库。DM4 JDBC URL的语法结构参见1.2.2节。
1.2.2 Driver
1. 概述
Driver 对象代表驱动程序对象。DM4 JDBC驱动程序中 Driver类的实现类为dm4.jdbc.driver.Dm4Driver。在Driver对象中提供了关于JDBC驱动程序的版本号、是否为JDBC COMPLIANTTM等驱动程序信息和URL格式验证、连接数据库等功能。
2. 主要方法介绍
(1) public boolean acceptsURL(String url) throws SQLException
该方法返回一个布尔值,说明参数url所代表的URL字符串中的子协议与该驱动程序关于子协议的约定是否匹配,即能否通过此URL建立到数据库的连接。
JDBC识别一个数据库时使用URL地址,JDBC URL的语法结构如下:
jdbc:<子协议>://<子名>
其中子协议用于识别不同的驱动程序,子名一般包含主机名,端口地址、数据库名、属性设定等。
DM4 JDBC驱动程序使用dm4作为子协议,子名形式为:
<服务器所在主机名或IP地址>[:<端口号>] [/<数据库名>] [?<属性>=<属性值>][{&<属性>=<属性值>}]
例如,假设DM4数据库服务器所在主机名为BPX,其IP地址为223.254.254.100,使用端口为12345,登录的数据库是test,则相应URL为:
jdbc:dm4://BPX:12345/test
或jdbc:dm4:// 223.254.254.100:12345/test
端口12345为默认端口,默认端口可省略不写,即简化为:
jdbc:dm4://bpx/test
或jdbc:dm4://223.254.254.100/test
每个用户有一个默认数据库,如果test是默认数据库,也可以省略不写,即简化为:
jdbc:dm4://bpx
或jdbc:dm4://223.254.254.100
如果是连接本机上的数据库服务器,可使用localhost代替主机名。
DM4服务器所使用的端口号、登录的数据库请向DM4系统管理员咨询。
(2)public Connection connect(String url, java.util.Properties info)
该方法用于与给定的数据库建立物理连接。第一个参数用于指定所连数据库的JDBC URL,第二个参数是一个Properties对象,它包含连接特定数据库所需的属性,通常包括用户名及口令等。
1.2.3 Connection
1. 概述
Connection对象代表与数据库的连接。连接过程包括所执行的SQL语句和在该连接上所返回的结果。一个应用程序可与数据库有一个或多个连接。由于连接的建立和维护的开销较高,因此,在应用程序中应尽可能重用连接对象。
2. 打开连接
与数据库建立连接的标准方法是调用DriverManager.getConnection方法。该方法接受含有URL的字符串。以下代码建立与数据库的连接:
Class.forName("dm4.jdbc.driver.Dm4Driver"); // 加载驱动程序
String url = "jdbc:dm4://223.254.254.19"; // 主机IP = 223.254.254.19
String userID = "SYSDBA";
String passwd = "SYSDBA";
Connection con = DriverManager.getConnection(url, userID, passwd);
3. 发送SQL 语句
连接一旦建立,就可用来向数据库服务器传送SQL语句。JDBC对可被发送的SQL语句类型不加任何限制。这就提供了很大的灵活性,即允许使用特定的数据库语句甚至非SQL语句。然而,它要求用户自己负责确保数据库服务器可以处理所发送的SQL语句。
JDBC提供了三个类,用于向数据库发送SQL语句。Connection接口中的三个方法可用于创建这些类的实例。下面列出这些类及其创建方法:
Statement:由方法createStatement 所创建,用于发送简单的SQL语句。
PreparedStatement:由方法PreparedStatement所创建,用于发送带有一个或多个输入参数(IN参数)的SQL语句。PreparedStatement 拥有一组方法,用于设置IN参数的值。执行语句时,这些IN参数将被送到数据库服务器。PreparedStatement 继承自Statement,因此包括了Statement 的所有方法。PreparedStatement对象可能比Statement 对象的效率更高,因为它已被预编译过并存放在那以供将来使用。
CallableStatement:由方法 prepareCall 所创建,用于执行SQL存储过程─ 一组可通过名称来调用的SQL语句。CallableStatement对象从PreparedStatement中继承了用于处理 IN 参数的方法,而且还增加了用于处理 OUT 参数和 INOUT 参数的方法。
值得注意的是:DM4 JDBC驱动程序中createStatement,PreparedStatement,prepareCall方法均不支持创建TYPE_SCROLL_SENSITIVE类型的语句对象。
4. 事务
缺省情况下,新连接将处于自动提交模式。也就是说,当一条语句执行完语句后,系统将自动提交或回滚该事物。通过setAutoCommit方法可以禁用或启用自动提交模式。如果禁用自动提交模式,事务将在显式调用commit或rollback方法后结束,因此它将提交或回滚上一次commit或rollback方法以来所有执行过的语句。
方法 commit 使 SQL 语句对数据库所做的任何更改成为永久性的,它还将释放事务持有的全部锁。而方法 rollback 将丢弃那些更改。自动提交模式下,不应再显式调用commit或rollback方法,否则将抛出异常。
3.0驱动程序中实现了对逻辑事务点的支持。逻辑事务点是将事务分成多个阶段的逻辑位置。这样就可以对事务进行分阶段控制,当回滚到某个逻辑事务点时,该逻辑事务点以后所设置的其他逻辑事务点以及事务操作都将失效,而不会影响到该逻辑事务点之前的事务操作。
值得注意的是:当执行DDL、DCL语句时, DM4有可能进行隐式提交或回滚操作(无论在自动提交状态还是非自动提交状态下)。其效果与显式调用commit或rollback方法相同。关于事务的显式或隐式结束,具体情况请参见《DM4_SQL语言使用手册》相关章节。
有时用户在另一个更改生效前不想让此更改生效。这可通过禁用自动提交并将两个更新组合在一个事务中来达到。如果两个更新都是成功的,可调用 commit 方法,从而使两个更新结果成为永久性的;如果其中之一或两个更新失败了,则应调用rollback方法,以将值恢复为进行更新之前的值。
表1.2.1:方法说明表
方法名 功能说明
getDefaultRowPrefetch 获得默认每次从服务器获取的元组数。
getSocketTimeout 获得建立数据库连接所需的最长时间。
getConnectTimeout 获得连接处于闲置状态的最长时间。
getEscapesProcessing 获得服务器默认是否进行转义处理。
getContinueBatchOnError 获得在批处理处理过程中如果有语句出现异常,那么是否继续执行该语句后面的语句。
getUseSQLState 获得默认采用的SQLState类型。
setDefaultRowPrefetch 设置每次从服务器获取的元组数。
setSocketTimeout 设置建立数据库连接所需的最长时间。
setConnectTimeout 设置连接处于闲置状态的最长时间。
setEscapesProcessing 设置是否让服务器进行转义处理。
setContinueBatchOnError 设置在批处理处理过程中如果有语句出现异常,是否继续执行该语句后面的语句。
setUseSQLState 设置所采用的SQLState类型。
setClientEncoding(String encoding) 设置客户端所采用的编码
getClientEncoding() 获得客户端所采用的编码
getClientEncodingId() 获得客户端所采用的编码代号
getServerUtf8Encoding() 获得服务器是否采用UTF-8作为信息的存储编码
调用setAutoCommit切换连接的自动提交模式时,如果从自动提交状态改为非自动提交状态,系统将隐式地执行一次提交操作。如果从非自动提交状态改为自动提交状态,用户需要主动提交或回滚。因此,调用setAutoCommit最好在连接建立后马上进行。
事务隔离级可通过setTransactionIsolation方法设置。
1. 结果集特性
JDBC 3.0允许用户调用setHoldability来指定在提交或回滚操作之后是否关闭结果集,便于用户分阶段操作结果集。如果用户要同时操作多个结果集,请用不同的语句对象分别产生结果集,因为DM4目前还不支持一条语句产生多个结果集的功能。
6. 关闭连接
通过调用close将关闭到数据库服务器的连接,释放数据库服务器的资源。由于数据库服务器建立和维护一个连接的开销较高,如果应用程序不再进行任何数据库的操作,建议关闭连接,以提高数据库服务器的性能。值得注意的是:当连接处于非自动提交模式时,关闭连接以前,需要显式提交或回滚事务,否则,未提交的数据可能丢失。
7. 自定义方法列表
为了进一步控制连接对象的属性,在Connection的实现过程中增加了一些新的自定义方法。用户将获得的连接对象反溯成DmdbConnection类型,就可以访问这些自定义方法。
另外,DM4不支持JDBC规范中所要求的标准方法getTypeMap、setTypeMap。
1.2.4 Statement
1. 概述
Statement 对象用于将SQL 语句发送到数据库服务器。DM4 JDBC提供三种类型的语句对象:Statement,PreparedStatement,CallableStatement。其中PreparedStatement是Statement的子类,CallableStatement是PreparedStatement的子类。每一种语句对象用来运行特定类型的SQL语句。
Statement对象用来运行简单类型的SQL语句,语句中无需指定参数。
PreparedStatement对象用来运行包含(或不包含)IN类型参数的预编译SQL语句。
CallableStatement对象用来调用数据库存储过程。
2. 创建Statement对象
建立连接后,Statement对象用Connection对象的createStatement方法创建,以下代码创建Statement对象:
Connection con = DriverManager.getConnection(url, "SYSDBA", "SYSDBA");
Statement stmt = con.createStatement();
3. 使用Statement对象执行语句
Statement接口提供了三种执行SQL语句的方法:executeQuery、executeUpdate和execute。
方法 executeQuery 用于产生单个结果集的语句,例如 SELECT 语句。
方法 executeUpdate 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL语句,如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate的返回值是一个整数,表示受影响的行数。对于CREATE TABLE或DROP TABLE等DDL语句,executeUpdate 的返回值总为零。
方法 execute 用于执行返回多个结果集、多个更新元组数或二者组合的语句。
执行语句的三种方法都将关闭所调用的Statement 对象的当前打开结果集(如果存在)。这意味着在重新执行Statement对象之前,需要完成对当前ResultSet对象的处理。
4. 关闭 Statement 对象
Statement对象可由Java垃圾收集程序自动关闭。但作为一种好的编程风格,应在不需要Statement对象时显式地关闭它们。这将立即释放数据库服务器资源,有助于避免潜在的内存问题。
1. 性能优化调整
(1)批处理更新
DM4 JDBC驱动程序提供批处理更新的功能,通过批处理更新可以一次执行一个语句集合。JDBC提供了三个方法来支持批处理更新:通过addBatch方法,向批处理语句集中增加一个语句;通过executeBatch执行批处理更新;通过clearBatch来清除批处理语句集。
批处理更新过程中,如果出现执行异常,DM4将立即退出批处理的执行,同时返回已经被执行语句的更新元组数(在自动提交模式下)。
推荐批处理更新在事务的非自动提交模式下执行。同时,批处理更新成功后,需要主动提交以保证事务的永久性。
(2)性能优化参数设置
DM4 JDBC驱动程序提供了setFetchDirection、setFetchSize来向驱动程序暗示用户操作语句结果集的缺省获取方向和一次获取的缺省元组数。这些值的设定可以为驱动程序优化提供参考。
6. 语句对象
JDBC在创建语句对象时,可以指定语句对象的缺省属性:结果集类型和结果集并发类型。DM4 JDBC驱动程序支持TYPE_FORWARD_ONLY和TYPE_SCROLL_INSENSITIVE两种结果集类型,不支持TYPE_SCROLL_SENSITIVE结果集类型;支持CONCUR_READ_ONLY、CONCUR_UPDATABLE两种结果集并发类型。
语句对象的缺省属性用来指定执行语句产生的结果集的缺省类型,其具体含义和用法参见1.2.7节中“结果集增强特性部分”。值得注意的是:DM4 JDBC驱动程序中当执行的查询语句涉及多个基表时,结果集不能更新,结果集的类型可能被自动转换为CONCUR_READ_ONLY类型。
1.2.5 PreparedStatement
1. 概述
PreparedStatement 继承 Statement,并与之在两方面有所不同:
PreparedStatement 对象包含已编译的 SQL 语句,语句已经“准备好”。
包含于PreparedStatement对象中的 SQL 语句可具有一个或多个 IN 参数。IN 参数的值在SQL语句创建时未被指定。相反,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号所对应的值必须在该语句执行之前,通过适当的 setXXX 方法来提供。
由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,需要多次重复执行的SQL语句经常创建为PreparedStatement对象,以提高效率。
作为 Statement 的子类,PreparedStatement 继承了Statement的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。同时,三种方法execute、executeQuery和executeUpdate能执行设置好参数的语句对象。
2. 创建 PreparedStatement 对象
以下的代码段(其中con是 Connection 对象)创建一个PreparedStatement对象:
PreparedStatement pstmt = con.prepareStatement(
"UPDATE 厂商登记 SET 厂商名 = ? WHERE 厂商编号 = ?");
对象pstmt包含语句 " UPDATE 厂商登记 SET 厂商名 = ? WHERE 厂商编号 = ?",该语句带两个IN参数占位符,它已发送给数据库,并由服务器为其执行作好了准备。
3. 传递 IN 参数
在执行 PreparedStatement 对象之前,必须设置每个?参数的值。这可通过调用setXXX方法来完成,其中XXX是与该参数相应的类型。例如,如果参数具有Java类型String,则使用的方法就是setString。对于不同类型的参数,一般都会有一个推荐的设置方法和多个可行的设置方法,各个不同类型与其设置方法的对应关系请参1.2.10数据类型访问这一节。setXXX方法的第一个参数是要设置的参数的序号(从1算起),第二个参数是设置给该参数的值。譬如,以下代码将第一个参数设为“中华电视机厂”,第二个参数设为“B0A01”:
pstmt.setString(1, "中华电视机厂");
pstmt.setString(2, " B0A01");
每当设置了给定语句的参数值,就可执行该语句。设置一组新的参数值之前,应先调用 clearParameters 方法清除原先设置的参数值。
4. 使用 setObject
setObject 方法可显式地将输入参数转换为特定的JDBC类型。该方法可以接受三个参数,其中第三个参数用来指定目标JDBC类型。将Java Object发送给数据库之前,驱动程序将把它转换为指定的JDBC类型。
如果没有指定JDBC类型,驱动程序就会将Java Object映射到其缺省的JDBC类型,然后将它发送到数据库。这与常规的 setXXX 方法类似。在这两种情况下,驱动程序在将值发送到数据库之前,会将该值的Java类型映射为适当的JDBC类型。二者的差别在于setXXX方法使用从Java类型到JDBC类型的标准映射,而setObject方法使用从Java Object类型到JDBC类型的映射。
方法setObject允许接受所有Java对象,这使应用程序更为通用,并可在运行时接受参数的输入。这样,如果用户在编辑应用程序时不能确定输入类型,可以通过使用setObject,对应用程序赋予可接受的Java对象,然后由JDBC驱动程序自动将其转换成数据库所需的JDBC类型。但如果用户已经清楚输入类型,使用相应的setXXX方法是值得推荐的,可以提高效率。
1. 将 JDBC NULL 作为 IN 参数发送
setNull方法允许程序员将JDBC NULL值作为IN参数发送给数据库。在这种情况下,可以把参数的目标JDBC类型指定为任意值,同时参数的目标精度也不再起作用。
6. 发送大的 IN 参数
setBytes和setString方法能够发送无限量的数据。但是,内存要足够容纳相关数据。有时程序员更喜欢用较小的块传递大型的数据,这可通过将IN参数设置为Java输入流来完成。当语句执行时,JDBC驱动程序将重复调用该输入流,读取其内容并将它们当作实际参数数据传输。
JDBC提供了四种将IN参数设置为输入流的方法:setBinaryStream用于字节流,setAsciiStream用于ASCII字符流,setUnicodeStream用于Unicode字符流,从JDK1.2起,输入字符流的新方法为setCharacterStream,而setAsciiStream和setUnicodeStream已经很少用。
7. 获得参数元数据
DM4 JDBC 3.0实现了getParameterMetaData()方法,通过这个方法可以获得有关IN参数的各种属性信息,比如类型、精度、刻度等信息,类似于结果集元数据的内容。通过这些信息,用户可以更准确地设置IN参数的值。`
在下面的代码中涉及到了这种方法:
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM BOOKLIST “ + “WHERE ISBN = ? ");
...
//获得参数元数据对象
ParameterMetaData pmd = pstmt.getParameterMetaData();
//获得参数的个数
int paramCount = pstmt.getParameterCount();
//获得第一参数的类型
int colType = pmd.getParameterType(1);
…
8. 自定义方法列表
为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现PreparedStatement接口的过程中,增加了一些自定义的扩展方法。用户将获得的PreparedStatement对象反溯成DmdbPreparedStatment类型就可以访问这些方法。这些方法所涉及到的扩展类请参看1.2.16扩展类这一节。
表1.2.2: 自定义方法列表
方法名 功能说明
setINTERVALYM 设置参数的DmdbIntervalYM类型值。
setINTERVALDT 设置参数的DmdbIntervalDT类型值。
setTIME 设置参数的DmdbTime类型值。
另外,DM4中不支持setArray、setURL、setRef方法。
1.2.6 CallableStatement
1. 概述
CallableStatement用来运行SQL存储过程。存储过程是数据库中已经存在的SQL语句,它通过名字调用。
CallableStatement 是PreparedStatement 的子类。CallableStatement 中定义的方法用于处理 OUT 参数或 INOUT 参数的输出部分:注册OUT参数的 JDBC 类型(一般SQL类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。
2. 创建 CallableStatement 对象
CallableStatement对象是用Connection.prepareCall创建的。以下代码创建 CallableStatement对象,其中含有对存储过程p1的调用,con为连接对象:
CallableStatement cstmt = con.prepareCall("call p1(?, ?)");
其中"?"占位符为 IN、OUT还是INOUT参数,取决于存储过程p1。
3. IN 和 OUT 参数
将 IN 参数传给 CallableStatement 对象是通过 setXXX 方法完成的。该方法继承自 PreparedStatement。所传入参数的类型决定了所用的setXXX方法(例如,用 setString 来传入String值等)。
如果存储过程返回OUT参数,则在执行CallableStatement对象之前必须先注册每个OUT参数的JDBC 类型,有的参数还要同时提供刻度。注册JDBC类型是用registerOutParameter方法来完成的。语句执行完后,CallableStatement的getXXX方法将取回参数值。其中XXX是为各参数所注册的JDBC类型所对应的Java类型。换言之, registerOutParameter使用的是JDBC类型(因此它与数据库返回的JDBC类型匹配),而getXXX将之转换为Java类型。
设存储过程p1的定义如下:
CREATE OR REPLACE PROCEDURE p1( a1 IN CHAR(5), a2 OUT CHAR(20)) AS
DECLARE CUR1 CURSOR FOR
SELECT 厂商名
FROM 厂商登记
WHERE 厂商编号 = a1;
BEGIN
OPEN CUR1;
FETCH CUR1 INTO a2;
END;
以下代码先注册OUT参数,执行由cstmt所调用的存储过程,然后检索通过OUT参数返回的值。方法getString从OUT参数中取出字符串:
CallableStatement cstmt = con.prepareCall("call p1(?, ?)");
cstmt.setString(1, "B0A05");
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString(2);
4. INOUT 参数
如果参数为既接受输入又接受输出的参数类型(INOUT 参数),那么除了调用registerOutParameter方法外,还要调用对应的setXXX方法(继承自PreparedStatement)。setXXX方法将参数值设置为输入参数,而registerOutParameter方法将它的JDBC类型注册为输出参数。setXXX方法提供一个Java值,驱动程序先把这个值转换为JDBC值,然后将它送到数据库服务器。
该IN 值的JDBC类型和提供给registerOutParameter方法的JDBC类型应该相同。如果要检索输出值,就要用对应的getXXX方法。
设有一个存储过程 p2的定义如下:
CREATE OR REPLACE PROCEDURE p2(a1 INOUT CHAR(20)) AS
DECLARE CUR1 CURSOR FOR
SELECT 厂商名
FROM 厂商登记
WHERE 厂商编号 = a1;
BEGIN
OPEN CUR1;
FETCH CUR1 INTO a1;
END;
以下代码中,方法setString把参数设为“B0A05”。然后,registerOutParameter将该参数注册为JDBC VARCHAR。执行完该存储过程后,将返回一个新的JDBC VARCHAR值。方法getString将把这个新值作为Java的String类型返回。
CallableStatement cstmt = con.prepareCall("call p2(?)");
cstmt.setString(1, "B0A05");
cstmt.registerOutParameter(1, java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString(1);
1. 利用参数名进行操作
在通常的情况下一般采用参数索引来进行赋值。JDBC3.0规范要求可以利用参数名来对参数进行赋值,DM4 JDBC 3.0驱动程序实现了这一点。如果某个存储过程的一些参数有默认值,这时候采用参数名进行赋值就非常有用,用户可以只对那些没有默认值的参数进行赋值。参数名可以通过调用DatabaseMetaData.getProcedureColumns()来获得。
下面的代码中,存储过程p2为上面那个存储过程p2。利用参数名进行操作:
CallableStatement cstmt = con.prepareCall("{CALL p2 (?)}");
cstmt.setString("a1", " B0A05");
cstmt.registerOutParameter("a1", java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString("a1");
而且,在读取参数的值和进行参数注册的时候,setXXX、getXXX、registerOutParameter也都可以采用参数名来进行操作。通过调用DatabaseMetaData.supportsNamedParameters()方法就可以确定JDBC驱动程序是否支持利用参数名来进行操作。
注意:在执行同一条语句的过程中,不允许交叉使用参数索引和参数名来进行操作,否则会抛出异常。
6. 自定义方法列表
为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现CallableStatement接口的过程中,增加了一些自定义的扩展方法。用户将获得的CallableStatement对象反溯成DmdbCallableStatment类型就可以访问这些方法。这些方法所涉及到的扩展类请参看1.2.16扩展类这一节。
表1.2.3: 自定义方法列表
方法名 功能说明
getINTERVALYM(int) 获取指定列的DmdbIntervalYM类型值。
getINTERVALYM(String) 获取指定列的DmdbIntervalYM类型值。
getINTERVALDT(int) 获取指定列的DmdbIntervalDT类型值。
getINTERVALDT(String) 获取指定列的DmdbIntervalDT类型值。
getTIME(int) 获取指定列的DmdbTime类型值。
getTIME(String) 获取指定列的DmdbTime类型值。
setTIME(String, DmdbTime) 根据参数名来设置DmdbTime类型值。
setINTERVALDT(String, DmdbIntervalDT) 根据参数名来设置DmdbIntervalDT类型值。
setINTERVALYM(String, DmdbIntervalYM) 根据参数名来设置DmdbIntervalYM类型值。
另外,DM4中不支持方法getArray、setURL、getURL、getRef。由于CallableStatement是PreparedStatement的子类,因此,它将继承PreparedStatement的所有的方法。
1.2.7 ResultSet
1. 概述
ResultSet提供执行SQL语句后从数据库返回结果中获取数据的方法。执行SQL语句后数据库返回结果被JDBC处理成结果集对象,可以用ResultSet对象的next方法以行为单位进行浏览,用getXXX方法取出当前行的某一列的值。
通过Statement,PreparedStatement,CallableStatement三种不同类型的语句进行查询都可以返回ResultSet类型的对象。
2. 行和光标
ResultSet维护指向其当前数据行的逻辑光标。每调用一次next方法,光标向下移动一行。最初它位于第一行之前,因此第一次调用next将把光标置于第一行上,使它成为当前行。随着每次调用next导致光标向下移动一行,按照从上至下的次序获取 ResultSet行。
在ResultSet对象或对应的Statement对象关闭之前,光标一直保持有效。
3. 列
方法getXXX提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。
列名或列号可用于标识要从中获取数据的列。例如,如果ResultSet对象rs的第二列名为“title”,则下列两种方法都可以获取存储在该列中的值:
String s = rs.getString("title");
String s = rs.getString(2);
注意列是从左至右编号的,并且从1开始。
在DM4 JDBC驱动程序中,如果列的全名为“表名.列名”的形式。在不引起混淆的情况下(结果集中有两个表具有相同的列名),可以省略表名,直接使用列名来获取列值。
关于ResultSet中列的信息,可通过调用方法ResultSet.getMetaData得到。返回的ResultSetMetaData对象将给出其ResultSet对象各列的名称、类型和其他属性。
4. 采用流方式获取列值
为了获取大数据量的列,DM4 JDBC驱动程序提供了四个获取流的方法:
getBinaryStream 返回只提供数据库原字节而不进行任何转换的流。
getAsciiStream 返回提供单字节 ASCII 字符的流。
getUnicodeStream 返回提供双字节 Unicode 字符的流。
getCharacterStream 返回提供双字节 Unicode 字符的java.io.Reader流。
在这四个函数中,JDBC规范不推荐使用getCharacterStream方法,其功能可以用getUnicodeStream代替。
1. NULL 结果值
要确定给定结果值是否是JDBC NULL,必须先读取该列,然后使用ResultSet对象的wasNull方法检查该次读取是否返回JDBC NULL。
当使用ResultSet对象的getXXX方法读取JDBC NULL时,将返回下列值之一:
Java null值:对于返回Java对象的getXXX方法(如getString、getBigDecimal、getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、getObject等)。
零值:对于getByte、getShort、getInt、getLong、getFloat 和 getDouble。
false 值:对于getBoolean。
6. 结果集增强特性
在DM4 JDBC驱动程序中提供了符合JDBC 2.0标准的结果集增强特性:可滚动、可更新结果集。及JDBC3.0标准的可持有性。
(1) 结果集的可滚动性
通过执行语句而创建的结果集不仅支持向前(从第一行到最后一行)浏览内容,而且还支持向后(从最后一行到第一行)浏览内容的能力。支持这种能力的结果集被称为可滚动的结果集。可滚动的结果集同时也支持相对定位和绝对定位。绝对定位指的是通过指定在结果集中的绝对位置而直接移动到某行的能力,而相对定位则指的是通过指定相对于当前行的位置来移动到某行的能力。DM4支持可滚动的结果集。
DM4 JDBC驱动程序中支持只向前滚结果集(ResultSet.TYPE_FORWARD_ONLY)和滚动不敏感结果集(ResultSet.TYPE_SCROLL_INSENSITIVE)两种结果集类型,不支持滚动敏感结果集(ResultSet.TYPE_SCROLL_SENSITIVE)。当结果集为滚动不敏感结果集时,它提供所含基本数据的静态视图,即结果集中各行的成员顺序、列值通常都是固定的。
(2) 结果集的可更新性
DM4 JDBC 驱动程序中提供了两种结果集并发类型:只读结果集(ResultSet.CONCUR_READ_ONLY)和可更新结果集(ResultSet.CONCUR_UPDATABLE)。采用只读并发类型的结果集不允许对其内容进行更新。可更新的结果集支持结果集的更新操作。
(3) 结果集的可持有性
JDBC 3.0提供了两种结果集可持有类型:提交关闭结果集(ResultSet.CLOSE_CURSORS_AT_COMMIT)和跨结果集提交(ResultSet.HOLD_CURSORS_OVER_COMMIT)。采用提交关闭结果集类型的结果集在事务提交之后被关闭,而跨结果集提交类型的结果集在事务提交之后仍能保持打开状态。
通过DatabaseMetaData.supportsHoldability()方法可以确定驱动程序是否支持结果集的可持有性。目前DM4支持这两种类型。
(4) 性能优化
DM4 JDBC 驱动程序的结果集对象中提供了方法setFetchDirection和setFetchSize来设置缺省检索结果集的方向和缺省一次从数据库获取的记录条数。它们的含义与用法和语句对象中的同名函数是相同的。
7. 更新大对象数据
从DM JDBC 2.0驱动程序就支持可更新的结果集,但是对LOB对象只能读取,而不能更新,这也是JDBC 2.0标准所规定的。而JDBC 3.0规范规定用户可以对LOB对象进行更新,DM4 JDBC 3.0驱动程序中实现了这一点:
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery("select comment from booklist " +
"where isbn = 140185852");
rs.next();
Clob commentClob = new Clob(...);
rs.updateClob("author", commentClob); // commentClob is a Clob Object
rs.updateRow();
8. 自定义方法列表
为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现ResultSet接口的过程中,增加了一些自定义的扩展方法。用户将获得的ResultSet对象反溯成DmdbResultSet类型就可以访问这些方法。这些方法所涉及到的扩展类请参看1.2.16扩展类这一节。
表1.2.4: 自定义方法列表
方法名 功能说明
getINTERVALYM(int) 获取指定列的DmdbIntervalYM类型值。
getINTERVALYM(String) 获取指定列的DmdbIntervalYM类型值。
getINTERVALDT(int) 获取指定列的DmdbIntervalDT类型值。
getINTERVALDT(String) 获取指定列的DmdbIntervalDT类型值。
getTIME(int) 获取指定列的DmdbTime类型值。
getTIME(String) 获取指定列的DmdbTime类型值。
updateINTERVALYM(int, DmdbIntervalYM ) 将指定列设置为DmdbIntervalYM类型值。
updateINTERVALYM(String, DmdbIntervalYM) 将指定列设置为DmdbIntervalYM类型值。
updateINTERVALDT(int , DmdbIntervalDT) 将指定列设置为DmdbIntervalDT类型值。
updateINTERVALDT(String , DmdbIntervalDT) 将指定列设置为DmdbIntervalDT类型值。
updateTIME(int , DmdbTime ) 将指定列设置为DmdbTime类型值。
updateTIME(String, DmdbTime ) 将指定列设置为DmdbTime类型值。
另外,DM4中不支持getArray、getURL、getRef方法。
1.2.8 ResultSetMetaData
1. 概述
ResultSetMetaData提供许多方法,用于读取ResultSet对象返回数据的元信息。包括:列名、列数据类型、列所属的表、以及列是否允许为NULL值等,通过这些方法可以确定结果集中列的一些信息。
2. 创建结果集元数据对象
结果集元数据是用来描述结果集的特征,所以,需要首先执行查询获得结果集,才能创建结果集元数据对象。
3.创建ResultSetMetaData对象如下例所示:
假如有一个表TESTTABLE(no int,name varchar(10)),利用下面的代码就可以知道这个表的各个列的类型:
ResultSet rs = stmt.executeQuery("SELECT * FROM TESTTABLE");
ResultSetMetaData rsmd = rs.getMetaData();
for(int i = 1; i <= rsmd.getColumnCount(); i ++)
{
String typeName = rsmd.getColumnTypeName(i);
System.out.println("第" + i + "列的类型为:" + typeName);
}
1.2.9 DatabaseMetaData
1. 概述
DatabaseMetaData提供了许多方法,用于获取数据库的元数据信息。包括:描述数据库特征的信息(如是否支持多个结果集)、目录信息、模式信息、表信息、表权限信息、表列信息、存储过程信息等。DatabaseMetaData有部分方法以ResultSet对象的形式返回结果,可以用ResultSet对象的getXXX()方法获取所需的数据。
2. 创建数据库元数据对象
数据库元数据对象由连接对象创建。以下代码创建DatabaseMetaData对象(其中con为连接对象):
DatabaseMetaData dbmd = con.getMetaData();
利用该数据库元数据对象就可以获得一些有关数据库和JDBC驱动程序的信息:
String databaseName = dbmd.getDatabaseProductName(); // 数据库产品的名称
int majorVersion = dbmd.getJDBCMajorVersion(); // JDBC驱动程序的主版本号
String []types ={"TABLE"};
ResultSet tablesInfor = dbmd.getTables(null, null, “*TE%”, types);
1.2.10 数据类型访问
1.概述
对数据的访问主要是(1)通过PreparedStatement对象、CallableStatement对象的setXXX方法以及ResultSet对象的updateXXX()方法进行设置; (2)通过CallableStatement对象、ResultSet对象的getXXX()方法来进行检索。同时,为了支持DM4特有的数据类型,DM4 JDBC驱动程序又新增了一些特殊的扩展方法。
2.各个类型能采用的setXXX方法或updateXXX方法
请参看SUN公司的JDBC 3.0 specification文档第186页的TABLE-6。一般地,只要把里面的getXXX改为setXXX或updateXXX即可。
3.各个类型能采用的getXXX方法
请参看SUN公司的JDBC 3.0 specification文档第186页的TABLE-6。
4.对DM4特殊类型的支持
为了能够在DM4 JDBC驱动程序中访问DM4特有的数据类型,我们在DmdbPreparedStatement、DmdbCallableStatement和DmdbResultSet类中增加了许多自定义的扩展方法。用户如果想使用这些方法,必须把对象强制转化为DmdbXXX类型:
PreparedStatement pstmt = connection.prepareStatement("insert into testInterval values (?)");
(DmdbPreparedStatement)pstmt.setINTERVALYM(1,"Interval '0015-08' year to month");
int updateCount = pstmt.executeUpdate();
其它方法的使用方式与此类似。
DmdbPreparedStatement类中增加了:
表1.2.5: 方法功能表
方法名 功能说明
setINTERVALYM(int,String) 设置参数为年-月时间间隔类型值
setINTERVALDT(int,String) 设置参数为日-时时间间隔类型值
setTIME(int,String) 设置参数为时间类型(带-纳秒)
另外setObject(int,Object)和setString(int,String)也可以操作这些类型。
DmdbCallableStatement类中增加了:
表1.2.6: 方法功能表
方法名 功能说明
getINTERVALYM(int):String 获得参数的值
getINTERVALYM(String):String 获得参数的值
getINTERVALDT(int):String 获得参数的值
getINTERVALDT(String):String 获得参数的值
getTIME(int):String 获得参数的值
getTIME(String):String 获得参数的值
setINTERVALDT(String,String) 设置参数为年-月时间间隔类型值
setINTERVALYM(String,String) 设置参数为日-时时间间隔类型值
setTIME(String,String) 设置参数为时间类型(带纳秒)
另外setObject(String,Object)、setString(String,String)、getObject(int)、getObject(String)、getString(int)、getString(String)也可以操作这些类型。
DmdbResultSet类中增加了:
表1.2.7: 方法功能表
方法名 功能说明
getINTERVALYM(int):String 获得列的值
getINTERVALYM(String):String 获得列的值
getINTERVALDT(int):String 获得列的值
getINTERVALDT(String):String 获得列的值
getTIME(int):String 获得列的值
getTIME(String):String 获得列的值
updateINTERVALDT(int,String) 设置列为年-月时间间隔类型值
updateINTERVALYM(int,String) 设置列为日-时时间间隔类型值
updateTIME(int,String) 设置列为时间类型(带纳秒)
updateINTERVALDT(String,String) 设置列为年-月时间间隔类型值
updateINTERVALYM(String,String) 设置列为日-时时间间隔类型值
updateTIME(String,String) 设置列为时间类型(带纳秒)
另外getObject(int)、getObject(String)、getString(int)、getString(String)、updateObject(int,Object) 、updateObject(String,Object)、 updateString(int,String) 、updateString(String,String)也可以操作这些类型。
1.2.11 ParameterMetaData
1.概述
参数元数据是JDBC 3.0标准新引入的接口,它主要是对PreparedStatement、CallableStatement对象中的?参数进行描述,例如参数的个数、参数的类型、参数的精度等信息,类似于ResultSetMetaData接口。通过引入这个接口,就可以对参数进行较为详细、准确的操作。
2.创建参数元数据对象
通过调用PreparedStatement或CallableStatement对象的getParameterMetaData()方法就可以获得该预编译对象的ParameterMetaData对象:
ParameterMetaData pmd = pstmt.getParameterMetaData();
然后就可以利用这个对象来获得一些有关参数描述的信息:
// 获取参数个数
int paraCount = pmd.getParameterCount();
for(int i = 1; i <= paraCount; i ++) {
// 获取参数类型
System.out.println("The Type of Parameter("+i+") is " + ptmt.getParameterType(i));
// 获取参数类型名
System.out.println("The Type Name of Parameter("+i+") is "
+ ptmt.getParameterTypeName(i));
// 获取参数精度
System.out.println("The Precision of Parameter("+i+") is " + ptmt.getPrecision(i));
// 获取参数是否为空
System.out.println("Parameter("+i+") is nullable? " + ptmt.isNullable (i));
}
1.2.12 大对象
1.概述
JDBC标准为了增强对大数据对象的操作,在JDBC 3.0标准中增加了java.sql.Blob和java.sql.Clob这两个接口。这两个接口定义了许多操作大对象的方法,通过这些方法就可以对大对象的内容进行操作。
2.产生Lob对象
在ResultSet和CallableStatement对象中调用getBlob()和getClob()方法就可以获得Blob对象和Clob对象:
Blob blob = rs.getBlob(1);
Clob clob = rs.getClob(2);
3.设置Lob对象
Lob对象可以像普通数据类型一样作为参数来进行参数赋值,在操作PreparedStatement、CallableStatement、ResultSet对象时使用:
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO bio (image, text) “ +
“VALUES (?, ?)");
//authorImage is a Blob Object
pstmt.setBlob(1, authorImage);
//authorBio is a Clob Object
pstmt.setClob(2, authorBio);
在一个可更新的结果集中,也可以利用updateBlob(int,Blob)和updateClob(int,Clob)来更新当前行。
4.改变Lob对象的内容
Lob接口提供了方法让用户可以对Lob对象的内容进行任意修改:
byte[] val = {0,1,2,3,4};
Blob data = rs.getBlob("DATA");
int numWritten = data.setBytes(1, val); // 在指定的位置插入数据
PreparedStatement ps = conn.prepareStatement("UPDATE datatab SET data = ?");
ps.setBlob("DATA", data);
ps.executeUpdate();
1.2.13 Savepoint
1.概述
为了增加对事务的控制,JDBC规范3.0增加了Savepoint这个接口。它代表一个逻辑事务点。在非自动提交模式下,一个事务中可以设置多个Savepoint。这样在回滚时,可以回滚到指定的Savepoint,从事务开始到该Savepoint之间的操作依然保留着。这就提高了事务处理的粒度,更好地操纵数据。
Savepoint分为命名的和未命名的两种。未命名的用系统分配的ID作为标识。同时可以通过DatabaseMetaData.supportsSavepoint()来判断JDBC驱动程序是否支持Savepoint。
2.操作Savepoint
Savepoint的操作位于Conncetion接口:
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("insert into tab1(col) values(' first') "); // 插入第一条数据
Savepoint svpt1 = conn.setSavepoint("SAVEPOINT_1"); // 设置一个Savepoint
rows = stmt.executeUpdate("intert into tab1(col) values('second') "); // 插入第二条数据
……
conn.rollback(svpt1); // 回滚到指定的Savepoint
……
conn.commit(); // 提交事务, 真正在数据库中仅插入了第一条数据
这样,数据库中仅增加了第一次插入的那条记录。注意:在使用Savepoint之前,一定要把当前连接设置为非自动提交模式。
在事务提交之后,先前事务中的所有Savepoint都自动取消。如果有两个Savepoint,当事务回滚到前面的那个Savepoint时,后面的这个Savepoint也自动取消。
1.2.14 autoGenerated Key
在很多数据库中,插入一条新的记录时数据库会为新增的记录自动产生一个唯一的标识。JDBC3.0规范中的Statement新增了一个方法:getGeneratedKeys,利用这个方法就可以检索出系统自动为插入的新行所产生的关键字。可以传递一个Statement.RETURN_GENERATED_KEYS标识给Statement对象的execute、executeUpdate()的方法或者PreparedStatement对象的一些方法,这样在执行完这些方法之后就可以检索新产生的关键字。
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("insert into orders(isbn,customerid) values(1953, 'billg') ",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
boolean b = rs.next();
if (b)
{
// 检索新产生的关键字的值
……
}
为了检索方便,用户也可以为自动产生的关键字指定列名或者索引:
String[] keyColumn = {"order_id"};
… …
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("insert into orders (isbn,customerid) values(9664, 'Billg') ",keyColumn);
ResultSet rs = stmt.getGeneratedKeys();
while(rs.next())
{
// 检索新产生的关键字的值
byte[] bs = rs.getBytes(order_id);
}
……
JDBC驱动程序是否支持自动产生的关键字,可以通过DataBaseMetaData.supportsGetGeneratedKeys来判断。目前DM4数据库系统提供了自动产生关键字的功能。
1.2.15 数据源
数据源是JDBC 2.0规范作为扩展包引入的,在JDBC 3.0规范中成为核心API。数据源不仅能够从功能上完全取代利用DriverManager建立连接的方式,而且具有以下几点优势:
(1)增强了代码的可移植性;
(2)方便了代码的维护;
(3)利用连接池来提高系统的性能。
DM4 JDBC驱动程序提供了对数据源的支持,实现了javax.sql.DataSource接口和java.sql.ConnectionPoolDataSource接口。用户通过javax.sql.DataSource接口来建立连接。
使用数据源来建立连接时,首先要向JNDI注册一个数据源。在建立连接的时候,首先通过JNDI来获得要使用的数据源:DataSource ds = (DataSource) ctx.lookup(datasourceName);然后使用这个数据源来建立连接对象:Connection con = ds.getConnection();该连接同通过DriverManager所建立的连接是一样的。通过数据源来建立连接的具体过程请参看1.3.2 创建JDBC数据源这一节。
实现javax.sql.ConnectionPoolDataSource接口是为了提高系统的性能。通过设置一个连接缓冲区,在其中保持数目较小的物理连接的方式,这个连接缓冲区由大量的并行用户共享和重新使用,从而避免在每次需要时建立一个新的物理连接,以及当其被释放时关闭该连接的昂贵操作。该连接池是JDBC提供系统性能的一种措施,是在JDBC内部实现的,能够为用户提供透明的高速缓冲连接访问。用户利用数据源来建立连接的应用不需为此做出任何代码上的变动。
1.2.16 扩展类
DM4 JDBC驱动程序为了支持DM4特有的数据类型,采用了自定义扩展包的方式来进行解决。dm4.sql这个包中包含了DM4特有数据类型所对应的类。时间间隔类型是DM4具有的数据类型,JDBC标准中没有相对应的类型。故令dm4.sql.DmdbIntervalYM和dm4.sql.DmdbIntervalDT分别对应于年-月间隔类型和日-时间隔类型。JDBC标准中的java.sql.Types.Time类型不允许带有纳秒,为了表示DM4中带纳秒的时间类型,设立了dm4.sql.DmdbTime类型。这些类型的主要方法为:
1.dm4.sql.DmdbIntervalDT
方法名 功能说明
DmdbIntervalDT(byte[]) 利用字节数组作为参数的构造函数。
DmdbIntervalDT(String) 利用字符串作为参数的构造函数。
setBytes(bytes[]) 设置字节数组。
setBytes(int,int,int,int,int) 把给定的Int参数转化为字节数组。
getString() 把DmdbIntervalDT的值以字符串的形式返回。
toString() 把DmdbIntervalDT的值转化为字符串的形式。
getBytes() 获得字节数组。
2.dm4.sql.DmdbIntervalYM
方法名 功能说明
DmdbInterval (byte[]) 利用字节数组作为参数的构造函数。
DmdbInterval (String) 利用字符串作为参数的构造函数。
setBytes(bytes[]) 设置字节数组。
getString() 把DmdbIntervalYM的值以字符串的形式返回。
toString() 把DmdbIntervalDT的值转化为字符串的形式。
getBytes() 获得字节数组。
setBytes(int,int) 把给定的Int参数转化为字节数组。
3.dm4.sql.DmdbTime
方法名 功能说明
DmdbTime(bytes[]) 利用字节数组作为参数的构造函数。
DmdbTime(String) 利用字符串作为参数的构造函数。
setBytes(bytes[]) 设置字节数组。
getString() 把DmdbTime的值以字符串的形式返回。
toString() 把DmdbTime的值转化为字符串的形式。
getBytes() 获得字节数组。
getTime() 把DmdbTime的值以java.sql.Types.Time的形式返回。只是去掉纳秒而已。
4.访问方式
为了能够在DM4 JDBC驱动程序中访问这些特有的数据类型,我们在DmdbPreparedStatement、DmdbCallableStatement和DmdbResultSet类中增加了许多方法。客户如果想使用这些方法,必须把对象强制转化为DmdbXXX类型:
PreparedStatement pstmt = connection.prepareStatement("insert into testInterval “ + “values(?)");
(DmdbPreparedStatement)pstmt.setIntervalYM(1, "Interval '0015-08' year to month");
int updateCount = pstmt.executeUpdate();
其它方法的使用方式与此类同。而且,时间间隔类型可以利用setString和getString方法进行存取,带纳秒的时间类型也可以当作普通的时间类型(不带纳秒)来进行操作。
1.3 建立JDBC连接
通常有两种途径来获得JDBC的连接对象,一种是通过驱动管理器DriverManager的getConnection(String url, String user, String password)方法来建立,另外一种就是通过数据源的方式来建立。DM4 JDBC 2.0和JDBC 3.0都支持这两种建立连接的方式。
1.3.1 通过DriverManager建立连接
这种建立连接的途径是最常用的,也称作编程式连接。利用这种方式来建立连接通常需要以下几个步骤:
注册数据库驱动程序(driver)。可以通过调用java.sql.DriverManager类的registerDriver方法显式注册驱动程序,也可以通过加载数据库驱动程序类隐式注册驱动程序。
//显示注册
DriverManager.registerDriver(new dm4.jdbc.driver.Dm4Driver());
//隐式注册
Class.forName(“dm4.jdbc.driver.Dm4Driver”);
隐式注册过程中加载实现了java.sql.Driver的类,该类中有一静态执行的代码段,在类加载的过程中向驱动管理器DriverManager注册该类。而这段静态执行的代码段其实就是上述显式注册的代码。
建立连接。注册驱动程序之后,就可以调用驱动管理器的getConnection方法来建立连接。建立数据库连接需要指定标示不同数据库的url、登录数据库所用的用户名user和密码password。通过DriverManager建立连接的具体过程请参看1.2.1 DriverManager这一节。
利用这种方式来建立数据库连接,连接数据库所需要的参数信息都被硬编码到程序中,这样每次更换不同的数据库或登录用户信息都要对应用进行重新改写、编译,不够灵活。而且,当用户同时需要多个连接时,就不得不同时建立多个连接,造成资源浪费和性能低下。为了解决这些问题,SUN公司在JDBC 2.0的扩展包中定义了数据源接口,提供了一种建立连接的新途径。
1.3.2 创建JDBC数据源
数据源是在JDBC 2.0中引入的一个概念。在JDBC 2.0扩展包中定义了javax.sql.DataSource接口来描述这个概念。如果用户希望建立一个数据库连接,通过查询在JNDITM服务中的数据源,可以从数据源中获取相应的数据库连接。这样用户就只需要提供一个逻辑名称(Logic Name),而不是数据库登录的具体细节。
JNDITM的全称是Java Naming and Directory Interface, 可以理解为Java名称和目录服务接口。JNDI向应用程序提供了一个查询和使用远程服务的机制。这些远程服务可以是任何企业服务。对于JDBC应用程序来说,JNDI提供的是数据库连接服务。JNDI使应用程序通过使用逻辑名称获取对象和对象提供的服务,从而使程序员可以避免使用与提供对象的机构有关联的代码。
一个DataSource对象代表一个实际的数据源。在数据源中存储了所有建立数据库连接的信息。系统管理员用一个逻辑名字对应DataSource对象,这个名字可以是任意的。在下面的例子中DataSource对象的名字是NativeDB。依照传统习惯, DataSource对象的名字包含在jdbc/下,所以这个数据源对象的完整名字是:jdbc/NativeDB。
数据源的逻辑名字确定之后,就需要向JNDI服务注册该数据源。下面的代码展示了向JNDI服务注册数据源的过程。
// 初始化名称-目录服务环境
Context ctx = null;
try
{
Hashtable env = new Hashtable (5);
env.put (Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
ctx = new InitialContext(env);
}
catch (NamingException ne)
{
ne.printStackTrace();
}
bind(ctx, "jdbc/NativeDB");
程序首先生成了一个Context实例。javax.naming.Context接口定义了名称服务环境(Naming Context)及该环境支持的操作。名称服务环境实际上是由名称和对象间的相互映射组成的。程序中初始化名称服务环境的环境工厂(Context Factory)是com.sun.jndi.fscontext.RefFSContextFactory(该类在fscontext.jar中可以找到,由于fscontext.jar中包含的不是标准的API,用户需要从www.javasoft.com中的JNDI专区下载fscontext.jar)类,环境工厂的作用是生成名称服务环境的实例。javax.naming.spi.InitialContextFactory接口定义了环境工厂应该如何初始化名称服务环境(该接口在providerutil.jar中实现,由于providerutil.jar中包含的不是标准的API,用户需要从www.javasoft.com中的JNDI专区下载providerutil.jar)。在初始化名称服务环境时还需要定义环境的URL。程序中使用的是"file:JNDI",也就是把环境保存在本地硬盘的JNDI目录下。目前很多J2EETM应用服务器都实现了自己的JNDI服务,用户可以选用这些服务包。
初始化了名称服务环境后,就可以把数据源实例注册到名称服务环境中。注册时调用javax.naming.Context.bind()方法,参数为注册名称和注册对象。注册成功后,在JNDI目录下会生成一个.binding文件,该文件记录了当前名称-服务环境拥有的名称及对象。具体实现如下例所示。
void bind (Context ctx, String ln)throws NamingException, SQLException
{
// 创建一个DmdbDataSource实例
DmdbDataSource dmds = new DmdbDataSource ();
// 把DmdbDataSource实例注册到JNDI中
ctx.bind (ln, dmds);
}
当需要在名称服务环境中查询一个对象时,需要调用javax.naming.Context.lookup()方法,并把查询到的对象显式转化为数据源对象。然后通过该数据源对象进行数据库操作。
DataSource ds = (DataSource) lookup (ctx, "jdbc/NativeDB");
Connection conn = ds.getConnection();
DataSource对象中获得的Connection对象和用DriverManager.getConnection方法获得的对象是等同的。由于DataSource方法具有很多优点,该方法成为获得连接的推荐方法。
1.3.3 数据源与连接池
利用数据源可以增强代码的可移植性,方便代码的维护。而且还可以利用连接池的功能来提高系统的性能。连接缓冲池的工作原理是:当一个应用程序关闭一个连接时,这个连接并不真正释放而是被循环利用。因为建立连接是消耗较大的操作,循环利用连接可以减少新连接的建立,能够显著地提高性能。
JDBC规范为连接池定义了两个接口,一个客户端接口和一个服务器端接口。客户端接口就是javax.sql.DataSource,这样客户先前采用数据源来获得连接的代码就不需要任何的修改。通过数据源所获得的连接是否是缓冲的,这取决于具体实现的JDBC驱动程序是否实现了连接池的服务器端接口javax.sql.ConnectionPoolDataSource。DM4 JDBC2.0和JDBC 3.0这两种驱动程序都实现了连接缓冲池。在实现连接缓冲池的过程中采用了新水平的高速缓存。一般说来,连接高速缓存是一种在一个池中保持数目较小的物理数据库连接的方式,这个连接池由大量的并行用户共享和重新使用,从而避免在每次需要时建立一个新的物理数据库连接,以及当其被释放时关闭该连接的昂贵的操作。连接池的实现对用户来说是透明的,用户不需为其修改任何代码。
1.4 编程基本步骤
DM4 JDBC驱动程序的应用主要包括四种方式:
(1)通过Java Applet小应用程序进行数据库访问;
(2)通过Java Application应用程序进行数据库访问;
(3)通过JSP/Servlet进行数据库访问;
(4)通过JavaBeans进行数据库访问。
第一种工作方式中,客户端先用Web浏览器通过HTTP协议从Web服务器自动下载包含应用逻辑的Java Applet程序(连同JDBC驱动程序一起),小应用程序Applet通过JDBC直接访问源宿主机(Web服务器)上的数据库服务器。
这种方式交互性强,但工作时需要同时从网络下载小应用程序与驱动程序,下载量大,同时存在安全限制,通常,选择第二种方式即通过应用程序实现对数据库的连接与访问控制。
在应用程序中进行数据库访问控制时,首先通过JDBC驱动程序管理器加载JDBC驱动程序,之后在程序中调用JDBC标准接口,进行数据库连接、运行SQL语句,及取得数据库返回结果等操作。
Jsp / Servlet是一种瘦客户机的技术,也是目前建设动态网页的主流技术之一。在JSP / Servlet中进行数据库访问控制时,主要是通过连接池数据源来和数据库建立连接,之后在程序中调用JDBC标准接口,运行SQL语句,取得数据库返回结果。
JavaBeans是一种组件技术,也是目前J2EE平台的主流技术之一。在JavaBeans中进行数据库访问时,主要是通过行集来同数据库建立连接,然后调用JDBC标准接口,运行SQL语句,取得数据库返回结果。
虽然JDBC驱动程序的应用环境不同,但其操作过程基本上都是一样的。利用驱动管理器或数据源来建立连接这可能是其中最大的区别之处。利用JDBC驱动程序进行编程的一般步骤为:
获得java.sql.Connection对象。利用DriverManager或者数据源来建立同数据库的连接。
创建java.sql.Statement对象。这里也包含了java.sql.PreparedStatement和java.sql.CallableStatement对象。利用连接对象的创建语句对象的方法来创建。在创建的过程中,根据需要来设置结果集的属性。
数据操作。数据操作主要分为两个方面,一个是更新操作,例如更新数据库、删除一行、创建一个新表等;另一个就是查询操作。执行完查询之后,会得到一个java.sql.ResultSet对象。可以操作该对象来获得指定列的信息、读取指定行的某一列的值。
释放资源。在操作完成之后,用户需要释放系统资源,主要是关闭结果集、关闭语句对象,释放连接。当然,这些动作也可以由JDBC驱动程序自动执行,但由于Java语言的特点,这个过程会比较慢(需要等到Java进行垃圾回收时进行),容易出现意想不到的问题。
下面用一个具体的编程实例来展示利用JDBC驱动程序进行数据库操作的基本步骤。
/*该例程实现插入数据,修改数据,数据查询等基本操作。*/
import java.sql.*;
public class BasicApp {
// 定义DM4 JDBC驱动串
String jdbcString = "dm4.jdbc.driver.Dm4Driver";
// 定义DM4 URL连接串
String urlString = "jdbc:dm4://localhost:12345";
// 定义连接用户名
String userName = "SYSDBA";
// 定义连接用户口令
String password = "SYSDBA";
// 定义连接对象
Connection conn = null;
/* 加载JDBC驱动程序
* @throws SQLException 异常 */
public void loadJdbcDriver() throws SQLException {
try {
System.out.println("Loading JDBC Driver...");
// 加载JDBC驱动程序
Class.forName(jdbcString);
}
catch (ClassNotFoundException e) {
throw new SQLException("Load JDBC Driver Error : " + e.getMessage());
}
catch(Exception ex) {
throw new SQLException("Load JDBC Driver Error : " + ex.getMessage());
}
}
/* 连接DM4数据库
* @throws SQLException 异常 */
public void connect() throws SQLException {
try {
System.out.println("Connecting to DM4 Server...");
// 连接DM4数据库
conn = DriverManager.getConnection(urlString,userName,password);
}
catch (SQLException e) {
throw new SQLException("Connect to DM4 Server Error : "
+ e.getMessage());
}
}
/* 关闭连接
* @throws SQLException 异常 */
public void disConnect() throws SQLException{
try {
// 关闭连接
conn.close();
}
catch(SQLException e) {
throw new SQLException("close connection error : " + e.getMessage());
}
}
/* 往厂商登记表插入数据
* @throws SQLException 异常 */
public void insertTable() throws SQLException{
// 插入数据语句
String sql = "INSERT INTO 厂商登记 VALUES ('B0A01',"
+ "'小鸭洗衣机厂',2000, '上海');";
// 创建语句对象
Statement stmt = conn.createStatement();
System.out.println("inserting table...");
// 执行语句
stmt.executeUpdate(sql);
// 关闭语句
stmt.close();
}
/* 修改厂商登记表数据
* @throws SQLException 异常 */
public void updateTable() throws SQLException{
// 更新数据语句
String sql = "UPDATE 厂商登记 SET 厂商地址 = ?"
+ "WHERE 厂商编号 = 'B0A01';";
// 创建语句对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 为参数赋值
pstmt.setString(1, “北京”);
// 执行语句
pstmt.executeUpdate();
// 关闭语句
pstmt.close();
}
/* 查询厂商登记表
* @throws SQLException 异常 */
public void queryTable() throws SQLException{
// 查询语句
String sql = "SELECT * FROM 厂商登记";
// 创建语句对象
Statement stmt = conn.createStatement();
System.out.println("querying table...");
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 显示结果集
displayResultSet(rs);
// 关闭结果集
rs.close();
// 关闭语句
stmt.close();
}
/* 显示结果集
* @param rs 结果集对象
* @throws SQLException 异常 */
private void displayResultSet(ResultSet rs) throws SQLException {
// 取得结果集元数据
ResultSetMetaData rsmd = rs.getMetaData ();
// 取得结果集所包含的列数
int numCols = rsmd.getColumnCount ();
// 显示列标头
for (int i = 1; i <= numCols; i++) {
if (i > 1)
System.out.print(",");
System.out.print(rsmd.getColumnLabel(i));
}
System.out.println("");
// 显示结果集中所有数据
while (rs.next()) {
for (int i = 1; i <= numCols; i++) {
if (i > 1)
System.out.print(",");
System.out.print(rs.getString(i));
}
System.out.println("");
}
}
/* 类主方法
* @param args 参数 */
public static void main(String args[]) {
try {
// 定义类对象
BasicApp basicApp = new BasicApp();
// 加载驱动程序
basicApp.loadJdbcDriver();
// 连接DM4数据库
basicApp.connect();
// 插入数据
basicApp.insertTable();
// 在更新前查询厂商登记表
basicApp.queryTable();
// 修改厂商登记表
basicApp.updateTable();
// 在更新后查询厂商登记表
basicApp.queryTable();
// 关闭连接
basicApp.disConnect();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
1.5 多字符集/多语言的支持
为了实现对多种语言的存储和访问,DM4采用两种编码方式来存储数据。一种是根据操作系统所提供的系统默认编码来进行存储,一种是利用Unicode编码来进行存储,具体地说就是用UTF-8来进行编码。UTF-8编码格式正逐步成为一种占主导地位的交换国际文本信息的方法,因为它可以支持世界上所有的语言,而且它还与ASCII兼容。UTF-8 使用变长编码。把从0到0x7f(127)的字符编码成单字节,而将值更大的字符编码成2 到6个字节。
Java语言本身采用Unicode编码,这就为解决国际化问题提供了很多方便。当Java虚拟机启动时,它会根据操作系统的编码方式来设置虚拟机的file.encoding属性。之后,应用中任何字节流到字符流的转换,JVM都会按照这一系统缺省编码方式进行转换。
服务器的编码方式在初始化数据库的过程中就已经确定下来了。但可能会有多个处于不同语言环境的客户端同时操作同一个数据库。在这种情况下,服务器的编码方式一般设置为UTF-8,而每个客户端都通过DM4 JDBC驱动程序提供的自定义方法来设置自身所采用的编码方式。由DM4 JDBC自动实现从客户端所采用的编码到服务器所采用的编码之间的信息转换。如果用户不指定客户端的编码,那么Java虚拟机就会按照客户端的系统缺省编码方式进行处理。
DM4 JDBC驱动程序在实现java.sql.Connection接口的类DmdbConnection中提供了一些自定义方法供用户来设置和读取客户端和服务器所采用的编码信息。
用户同数据库建立连接之后,就可以把获得的连接对象反溯成DmdbConnection类型,然后调用其中的自定义方法进行操作。一般操作过程如下例所示。
Connection con = DriverManager.getConnection(url, user, password);
DmdbConnection dmdbcon = (DmdbConnection) con;
dmdbcon.setClientencoding(“GBK”);
String encoding = dmdbcon.getClientencoding();
boolean server_utf8 = dmdbcon.getServerUtf8Encoding();
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(“SELECT * FROM 厂商登记;”);
…
在执行的过程中,DM4 JDBC驱动程序首先判断服务器是否采用了UTF-8的编码方式来存储数据。如果是的话,那么就根据客户端所采用的编码来解析客户的输入,然后将之转换成UTF-8编码方式的字节流发送给服务器。从服务器获得数据之后,首先判断服务器是否采用了UTF-8的编码方式来存储数据。如果是的话,那么就将这些数据利用UTF-8编码进行解析,然后转换成客户端所采用的编码方式的数据。如果服务器没有采用UTF-8编码格式来存储数据,那么客户端就直接利用自身所处系统的默认编码方式来解析数据,不进行编码转换。