DM JDBC 编程指南

4.1 JDBC介绍

JDBC(Java Database Connectivity)是Java应用程序与数据库的接口规范,旨在让各数据库开发商为Java程序员提供标准的数据库应用程序编程接口(API)。JDBC定义了一个跨数据库、跨平台的通用SQL数据库API。

DM JDBC驱动程序是DM数据库的JDBC驱动程序,它是一个能够支持基本SQL功能的通用应用程序编程接口,支持一般的SQL数据库访问。

通过JDBC驱动程序,用户可以在应用程序中实现对DM数据库的连接与访问,JDBC驱动程序的主要功能包括:

  1. 建立与DM数据库的连接;
  2. 转接发送SQL语句到数据库;
  3. 处理并返回语句执行结果。

4.2 JDBC基本示例

利用JDBC驱动程序进行编程的一般步骤为:

  1. 获得java.sql.Connection对象。

    利用DriverManager或者数据源来建立同数据库的连接。

  2. 创建java.sql.Statement对象。这里也包含了java.sql.PreparedStatement和java.sql.CallableStatement对象。

    利用连接对象的创建语句对象的方法来创建。在创建的过程中,根据需要来设置结果集的属性。

  3. 数据操作。

    数据操作主要分为两个方面,一个是更新操作,例如更新数据库、删除一行、创建一个新表等;另一个就是查询操作,执行完查询之后,会得到一个java.sql.ResultSet对象,可以操作该对象来获得指定列的信息、读取指定行的某一列的值。

  4. 释放资源。

    在操作完成之后,用户需要释放系统资源,主要是关闭结果集、关闭语句对象,释放连接。当然,这些动作也可以由JDBC驱动程序自动执行,但由于Java语言的特点,这个过程会比较慢(需要等到Java进行垃圾回收时进行),容易出现意想不到的问题。

下面用一个具体的编程实例来展示利用JDBC驱动程序进行数据库操作的基本步骤:

/*该例程实现插入数据,修改数据,删除数据,数据查询等基本操作。*/
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import javax.imageio.ImageIO;
public class BasicApp {
	// 定义DM JDBC驱动串
	String jdbcString = "dm.jdbc.driver.DmDriver";
	// 定义DM URL连接串
	String urlString = "jdbc:dm://localhost:5236";
	// 定义连接用户名
	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());
		}
	}
	/* 连接DM数据库
	 * @throws SQLException 异常 */
	public void connect() throws SQLException {
		try {
			System.out.println("Connecting to DM Server...");
			// 连接DM数据库
			conn = DriverManager.getConnection(urlString, userName, password);
		} catch (SQLException e) {
			throw new SQLException("Connect to DM 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 production.product(name,author,publisher,publishtime,"
				+ "product_subcategoryid,productno,satetystocklevel,originalprice,nowprice,discount,"
				+ "description,photo,type,papertotal,wordtotal,sellstarttime,sellendtime) "
				+ "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";
		// 创建语句对象
		PreparedStatement pstmt = conn.prepareStatement(sql);
		// 为参数赋值
		pstmt.setString(1, "三国演义");
		pstmt.setString(2, "罗贯中");
		pstmt.setString(3, "中华书局");
		pstmt.setDate(4, Date.valueOf("2005-04-01"));
		pstmt.setInt(5, 4);
		pstmt.setString(6, "9787101046121");
		pstmt.setInt(7, 10);
		pstmt.setBigDecimal(8, new BigDecimal(19.0000));
		pstmt.setBigDecimal(9, new BigDecimal(15.2000));
		pstmt.setBigDecimal(10, new BigDecimal(8.0));
		pstmt.setString(11, "《三国演义》是中国第一部长篇章回体小说,中国小说由短篇发展至长篇的原因与说书有关。");
		// 设置大字段参数 
		try {
			// 创建一个图片用于插入大字段
			String filePath = "c:\\三国演义.jpg";
			CreateImage(filePath);
			File file = new File(filePath); 
			InputStream in = new BufferedInputStream(new FileInputStream(file)); 
			pstmt.setBinaryStream(12, in, (int) file.length());
		} catch (FileNotFoundException e) {
			System.out.println(e.getMessage());
			// 如果没有图片设置为NULL
			pstmt.setNull(12, java.sql.Types.BINARY);
		} catch (IOException e) {
		System.out.println(e.getMessage());
		} 
		pstmt.setString(13, "25");
		pstmt.setInt(14, 943);
		pstmt.setInt(15, 93000);
		pstmt.setDate(16, Date.valueOf("2006-03-20"));
		pstmt.setDate(17, Date.valueOf("1900-01-01"));
		// 执行语句
		pstmt.executeUpdate();
		// 关闭语句
		pstmt.close();
	}
	/* 查询产品信息表
	 * @throws SQLException 异常 */
	public void queryProduct() throws SQLException {
		// 查询语句
		String sql = "SELECT productid,name,author,description,photo FROM production.product WHERE productid=11";
		// 创建语句对象
		Statement stmt = conn.createStatement();
		// 执行查询
		ResultSet rs = stmt.executeQuery(sql);
		// 显示结果集
		displayResultSet(rs);
		// 关闭结果集
		rs.close();
		// 关闭语句
		stmt.close();
	} 
	/* 修改产品信息表数据
	 * @throws SQLException 异常 */
	public void updateTable() throws SQLException {
		// 更新数据语句
		String sql = "UPDATE production.product SET name = ?"
				+ "WHERE productid = 11;";
		// 创建语句对象
		PreparedStatement pstmt = conn.prepareStatement(sql);
		// 为参数赋值
		pstmt.setString(1, "三国演义(上)");
		// 执行语句
		pstmt.executeUpdate();
		// 关闭语句
		pstmt.close();
	}
	/* 删除产品信息表数据
	 * @throws SQLException 异常 */
	public void deleteTable() throws SQLException {
		// 删除数据语句
		String sql = "DELETE FROM production.product WHERE productid = 11;";
		// 创建语句对象
		Statement stmt = conn.createStatement();
		// 执行语句
		stmt.executeUpdate(sql);
		// 关闭语句
		stmt.close();
	}
	/* 查询产品信息表
	 * @throws SQLException 异常 */
	public void queryTable() throws SQLException {
		// 查询语句
		String sql = "SELECT productid,name,author,publisher FROM production.product";
		// 创建语句对象
		Statement stmt = conn.createStatement();
		// 执行查询
		ResultSet rs = stmt.executeQuery(sql);
		// 显示结果集
		displayResultSet(rs);
		// 关闭结果集
		rs.close();
		// 关闭语句
		stmt.close();
	}
	/* 调用存储过程修改产品信息表数据
	 * @throws SQLException 异常 */
	public void updateProduct() throws SQLException {
		// 更新数据语句
		String sql = "{ CALL production.updateProduct(?,?) }";
		// 创建语句对象
		CallableStatement cstmt = conn.prepareCall(sql);
		// 为参数赋值
		cstmt.setInt(1, 1);
		cstmt.setString(2, "红楼梦(上)");
		// 执行语句
		cstmt.execute();
		// 关闭语句
		cstmt.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(",");
				}
				// 处理大字段
				if ("IMAGE".equals(rsmd.getColumnTypeName(i))) {
					byte[] data = rs.getBytes(i);
					if (data != null && data.length > 0) {
						FileOutputStream fos;
						try {
							fos = new FileOutputStream("c:\\三国演义1.jpg");
							fos.write(data);
							fos.close();
						} catch (FileNotFoundException e) {
							System.out.println(e.getMessage());
						} catch (IOException e) {
							System.out.println(e.getMessage());
						}
					}
		System.out.print("字段内容已写入文件c:\\三国演义1.jpg,长度" + data.length);
				} else {
					// 普通字段
					System.out.print(rs.getString(i));
				}
			}
			System.out.println("");
		}
	}
	 /* 创建一个图片用于插入大字段
	 * @throws IOException 异常 */
	private void CreateImage(String path) throws IOException {
		int width = 100;
		int height = 100;
		String s = "三国演义";
		File file = new File(path);
		Font font = new Font("Serif", Font.BOLD, 10);
		BufferedImage bi = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = (Graphics2D) bi.getGraphics();
		g2.setBackground(Color.WHITE);
		g2.clearRect(0, 0, width, height);
		g2.setPaint(Color.RED);
		FontRenderContext context = g2.getFontRenderContext();
		Rectangle2D bounds = font.getStringBounds(s, context);
		double x = (width - bounds.getWidth()) / 2;
		double y = (height - bounds.getHeight()) / 2;
		double ascent = -bounds.getY();
		double baseY = y + ascent;
		g2.drawString(s, (int) x, (int) baseY);
		ImageIO.write(bi, "jpg", file);
	}
	/*
	 * 类主方法 @param args 参数
	 */
	public static void main(String args[]) {
		try {
			// 定义类对象
			BasicApp basicApp = new BasicApp();
			// 加载驱动程序
			basicApp.loadJdbcDriver();
			// 连接DM数据库
			basicApp.connect();
			// 插入数据
			System.out.println("--- 插入产品信息 ---");
			basicApp.insertTable();
			// 查询含有大字段的产品信息
			System.out.println("--- 显示插入结果 ---");
			basicApp.queryProduct();
			// 在修改前查询产品信息表
			System.out.println("--- 在修改前查询产品信息 ---");
			basicApp.queryTable();
			// 修改产品信息表
			System.out.println("--- 修改产品信息 ---");
			basicApp.updateTable();
			// 在修改后查询产品信息表
			System.out.println("--- 在修改后查询产品信息 ---");
			basicApp.queryTable();
			// 删除产品信息表
			System.out.println("--- 删除产品信息 ---");
			basicApp.deleteTable();
			// 在删除后查询产品信息表
			System.out.println("--- 在删除后查询产品信息 ---");
			basicApp.queryTable();
			// 调用存储过程修改产品信息表
			System.out.println("--- 调用存储过程修改产品信息 ---");
			basicApp.updateProduct();
			// 在存储过程更新后查询产品信息表
			System.out.println("--- 调用存储过程后查询产品信息 ---");
			basicApp.queryTable();
			// 关闭连接
			basicApp.disConnect();
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
	}
}

运行程序,启动达梦数据库服务器并创建用于修改产品信息的存储过程。存储过程代码如下:

-- 创建修改产品信息的存储过程
create or replace procedure "PRODUCTION"."updateProduct"
(
  v_id int,
  v_name varchar(50)
)
as
begin
  UPDATE production.product SET name = v_name WHERE productid = v_id;
end;

将以上Java程序保存为C:\BasicApp.java文件,打开命令行窗口进入C盘根目录,输入javac BasicApp.java编译该类文件,编译完成后在命令行窗口输入java -classpath D:\dmdbms\jdbc\DmJdbcDriver.jar; BasicApp运行该文件。其DmJdbcDriver.jar有三个版本:DmJdbcDriver16.jar、DmJdbcDriver17.jar和DmJdbcDriver18.jar,分别对应JDK版本1.6、1.7和1.8,用户根据需要自行选择。

注意这里还需要将达梦数据库驱动程序DmJdbcDriver.jar(根据JDK版本选择上述三个驱动之一)文件加入到classpath中,达梦数据库驱动程序DmJdbcDriver.jar文件存在于达梦数据库安装路径下的jdbc目录下,这里假设达梦数据库安装在D:\dmdbms目录,那么达梦数据库驱动程序DmJdbcDriver.jar文件就在D:\dmdbms\drivers\jdbc目录下。

4.3 DM JDBC特性

DM JDBC驱动程序有JDBC3.0和JDBC4.0两种类型。

DM JDBC 3.0驱动程序符合SUN JDBC3.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

DM JDBC 4.0驱动程序符合SUN JDBC4.0标准,实现了下列新特性:

  1. JDBC驱动类的自动加载;
  2. 连接管理的增强;
  3. 对RowId SQL类型的支持;
  4. SQL的DataSet实现使用了Annotations;
  5. SQL异常处理的增强;
  6. 对SQL XML的支持;
  7. 对BLOB/CLOB 的改进支持以及对国际字符集的支持。

4.4 DM JDBC扩展

4.4.1 数据类型扩展

DM JDBC驱动程序为了支持DM特有的数据类型,采用了自定义扩展包的方式来进行解决。dm.jdbc.driver这个包中包含了DM特有数据类型所对应的类。时间间隔类型是DM具有的数据类型,JDBC标准中没有相对应的类型。故令DmdbIntervalYM和DmdbIntervalDT分别对应于年-月间隔类型和日-时间隔类型。JDBC标准中的java.sql.Types.Time类型不允许带有纳秒,为了表示DM中带纳秒的时间类型,设立了DmdbTimestamp类型。这些类型的主要方法为:

  1. DmdbIntervalDT
方法名 功能说明
DmdbIntervalDT(byte[]) 利用字节数组作为参数的构造函数。
DmdbIntervalDT(byte[],int,int) 利用字节数组,指定引导精度、纳秒精度构造函数。
DmdbIntervalDT(String) 利用字符串作为参数的构造函数。
DmdbIntervalDT(String,int,int) 设置字符串,指定引导精度、纳秒精度构造函数。
getByteArrayValue() 获取字节数组。
getDTString() 把DmdbIntervalDT的值以字符串的形式返回。
getDTType() 获取DmdbIntervalDT值类型。
getDay() 获取DmdbIntervalDT值中日值。
getHour() 获取DmdbIntervalDT值中时值。
getMinute() 获取DmdbIntervalDT值中分值。
getSecond() 获取DmdbIntervalDT值中秒值。
getNano() 获取DmdbIntervalDT值中纳秒值。
getLoadPrec() 获取DmdbIntervalDT值中引导精度数。
getSecPrec() 获取DmdbIntervalDT值中纳秒精度数。
clear() 清除DmdbIntervalDT值。
  1. DmdbIntervalYM
方法名 功能说明
DmdbIntervalYM (byte[]) 利用字节数组作为参数的构造函数。
DmdbIntervalYM (byte[],int) 利用字节数组、精度作为参数构造函数,精度默认为0。
DmdbIntervalYM (String) 利用字符串作为参数的构造函数。
DmdbIntervalYM (String,int) 利用字符串、精度作为参数的构造函数,精度默认为0。
getByteArrayValue() 把DmdbIntervalYM的值以字节数组的形式返回。
getYMString() 把DmdbIntervalYM的值以字符串的形式返回。
getYMType() 获取DmdbIntervalYM值类型。
getYear() 返回DmdbIntervalYM值中的年值。
getMonth() 返回DmdbIntervalYM值中的月值。
getLoadPrec() 获取DmdbIntervalYM的引导精度。
toString() 把DmdbIntervalYM的值转化为字符串的形式。
clear() 清除DmdbIntervalYM值信息。
  1. DmdbTimestamp
方法名 功能说明
valueOf(Date) 根据指定的java.util.Date类型构造一个DmdbTimestamp类型
valueOf(String) 根据指定的时间类型字符串构造一个DmdbTimestamp类型
getDt() 返回一个此对象表示的时间值数组,内容为[年,月,日,时,分,秒,微秒,时区]
getTime () 返回此对象表示的自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数
setTime(long) 使用给定毫秒时间值设置现有 DmdbTimestamp对象
getTimezone() 获取DmdbTimestamp值中的时区值
setTimezone(int) 设置此对象的时区
getNano() 获取此对象的纳秒值
setNano(long) 设置此对象的纳秒值
toString() 把DmdbTimestamp的值转化为字符串的形式
  1. 访问方式

为了能够在DM JDBC驱动程序中访问这些特有的数据类型,我们在DmdbPreparedStatement、DmdbCallableStatement和DmdbResultSet类中增加了许多方法。客户如果想使用这些方法,必须把对象强制转化为DmdbXXX类型:

PreparedStatement pstmt = connection.prepareStatement("insert into testInterval “ + “values(?)");
((DmdbPreparedStatement)pstmt).setINTERVALYM(1, new DmdbIntervalYM("Interval '0015-08' year to month"));
int updateCount = pstmt.executeUpdate();

其它方法的使用方式与此类同。而且,时间间隔类型可以利用setString和getString方法进行存取,带纳秒的时间类型也可以当作普通的时间类型(不带纳秒)来进行操作。

4.4.2 读写分离集群下的错误信息

当使用JDBC接口连接DM读写分离集群系统时,对返回的报错信息进行了扩展。在报错信息前增加了一个字符前缀用于标识报错信息是来自读写分离集群的主库还是备库:

  • 前缀[P]表示是来自主库的报错信息;
  • 前缀[S]表示是来自备库的报错信息。

4.5 建立JDBC连接

通常有两种途径来获得JDBC的连接对象,一种是通过驱动管理器DriverManager的getConnection(String url, String user, String
password)方法来建立,另外一种就是通过数据源的方式来建立。JDBC 3.0支持这两种建立连接的方式。

4.5.1 通过DriverManager建立连接

这种建立连接的途径是最常用的,也称作编程式连接。利用这种方式来建立连接通常需要以下几个步骤:

注册数据库驱动程序(driver)。可以通过调用java.sql.DriverManager类的registerDriver方法显式注册驱动程序,也可以通过加载数据库驱动程序类隐式注册驱动程序。

//显示注册
DriverManager.registerDriver(new dm.jdbc.driver.dmDriver());
//隐式注册
Class.forName("dm.jdbc.driver.DmDriver");

隐式注册过程中加载实现了java.sql.Driver的类,该类中有一静态执行的代码段,在类加载的过程中向驱动管理器DriverManager注册该类。而这段静态执行的代码段其实就是上述显式注册的代码。

建立连接。注册驱动程序之后,就可以调用驱动管理器的getConnection方法来建立连接。建立数据库连接需要指定连接数据库的url、登录数据库所用的用户名user和密码password。

通过DriverManager建立连接的具体过程如下:

  1. 加载DM JDBC驱动程序

dm.jdbc.driver.DmDriver类包含一静态部分,它创建该类的实例。当加载驱动程序时,驱动程序会自动调用DriverManager.registerDriver方法向DriverManager注册自己。通过调用方法Class.forName(String str),将显式地加载驱动程序。以下代码加载DM的JDBC驱动程序:

Class.forName("dm.jdbc.driver.DmDriver");
  1. 建立连接

加载DM 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、用户名、口令来连接数据库。

以下代码建立与数据库的连接:

Class.forName("dm.jdbc.driver.DmDriver"); // 加载驱动程序
String url = "jdbc:dm://223.254.254.19"; // 主库IP = 223.254.254.19
String userID = "SYSDBA";
String passwd = "SYSDBA";
Connection con = DriverManager.getConnection(url, userID, passwd);

利用这种方式来建立数据库连接,连接数据库所需要的参数信息都被硬编码到程序中,这样每次更换不同的数据库或登录用户信息都要对应用进行重新改写、编译,不够灵活。而且,当用户同时需要多个连接时,就不得不同时建立多个连接,造成资源浪费和性能低下。为了解决这些问题,SUN公司在JDBC 2.0的扩展包中定义了数据源接口,提供了一种建立连接的新途径。

4.5.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方法具有很多优点,该方法成为获得连接的推荐方法。

4.5.3 数据源与连接池

利用数据源可以增强代码的可移植性,方便代码的维护。而且还可以利用连接池的功能来提高系统的性能。连接缓冲池的工作原理是:当一个应用程序关闭一个连接时,这个连接并不真正释放而是被循环利用。因为建立连接是消耗较大的操作,循环利用连接可以减少新连接的建立,能够显著地提高性能。

JDBC规范为连接池定义了两个接口,一个客户端接口和一个服务器端接口。客户端接口就是javax.sql.DataSource,这样客户先前采用数据源来获得连接的代码就不需要任何的修改。通过数据源所获得的连接是否是缓冲的,这取决于具体实现的JDBC驱动程序是否实现了连接池的服务器端接口javax.sql.ConnectionPoolDataSource。DM JDBC实现了连接缓冲池,在实现连接缓冲池的过程中采用了新水平的高速缓存。一般说来,连接高速缓存是一种在一个池中保持数目较小的物理数据库连接的方式,这个连接池由大量的并行用户共享和重新使用,从而避免在每次需要时建立一个新的物理数据库连接,以及当其被释放时关闭该连接的昂贵的操作。连接池的实现对用户来说是透明的,用户不需为其修改任何代码。

4.5.4 DM扩展连接属性的使用

除了标准JDBC接口功能,DM扩展了一些具有自身特点的功能处理特性,这些特性可以通过在连接串上设置连接属性进行控制。另外,这些连接串属性也可以在dm_svc.conf中使用,而dm_svc.conf中的属性(通用配置项和JDBC配置项)也可以在JDBC连接串上设置。另外,dm_svc.conf中dexp配置项、dpc_new配置项、.Net provider配置项、DPI配置项与JDBC连接串无关,无须设置。

连接串的书写格式有以下三种:

格式一 host、port不作为连接属性,此时只需输入值即可。

格式:

jdbc:dm [: //host][:port][?propName1=propValue1][& propName2=propValue2]….

参数介绍:

host:数据库所在IP地址,缺省为‘localhost’。若host为ipv6地址,则应包含在[]中;

port:数据库端口号,缺省为‘5236’。若host 不设置,则port一定不能设置;

?:是参数分隔符,表示后面的都是参数;

&:是参数间隔符,多个参数用&分开;

propName:连接串属性名称。详见表4.1;

propValue:连接串属性值。

例如:

jdbc:dm://192.168.0.96:5236?LobMode=1
  1. host、port作为连接属性,此时必须按照表4.1中说明进行设置,且属性名称大小写敏感

格式二 host、port作为连接属性,此时必须按照表4.1中说明进行设置,且属性名称大小写敏感。

jdbc:dm:// [?propName1=propValue1] [ & propName2=propValue2] [&…]…

参数介绍:

host和port:设置与否,以及在属性串中的位置没有限制;

其它参数:和格式一相同。

例如:

  1. host、port设置与否,以及在属性串中的位置没有限制
  2. 若user、password没有单独作为参数传入,则必须在连接属性中传入

例:

jdbc:dm:// ?host=192.168.0.96&port=5236

格式三 使用自定义服务名,可指定多个数据库节点

格式:

jdbc:dm://GroupName?GroupName=(host1:port1,host2:port2,…) [&propName1=propValue1] [ & propName2=propValue2] [&…]…

参数介绍:

GroupName:数据库服务名;

其它参数:和格式一相同。

例如:

jdbc:dm:// test?test=(192.168.0.96:5236, 192.168.0.96:5237)

JDBC连接串中可设置的属性中除了user和password是必须要设置的,其它属性均为可选项。如果同一个属性在JDBC连接串中和dm_svc.conf配置项中均有设置,但值却不同,则以JDBC连接串优先。

连接串中可以设置的属性及其说明见下表。

表4.1 JDBC连接串属性

属性名称 说明 是否必须设置
host 主库地址,包括IP地址、localhost或者配置文件中主库地址列表对应的变量名,如dm_svc.conf中的’o2000’
port 端口号,服务器登录端口号
unixSocketFile LINUX系统中,当服务器与客户端之间使用UNIXSOCETUNIX-IPC方式通信时,用于指定客户端连接的UNIXSOCKET路径文件名。使用unixSocketFile时,不需要指定host和port。
例如:jdbc:dm://?user=SYSDBA&password=SYSDBA&unixSocketFile=/home/te/foo.sock
user 登录用户
password 登录密码
appName 客户端应用程序名称
osName 操作系统名称
socketTimeout 网络通信链路超时时间;单位ms,有效值范围0~2147483647,0表示无限制;默认0;
sessionTimeout 会话超时时间;单位ms,有效值范围0~2147483647,0表示无限制;默认0;
connectTimeout 连接数据库超时时间;单位ms,有效值范围0~2147483647,0表示无限制;默认0;
StmtPoolSize 语句句柄池大小;有效值范围0~2147483647,0表示关闭;默认15;
PStmtPoolSize prepare语句句柄池大小;有效值范围0~2147483647,0表示关闭;默认0;
pstmtPoolValidTime prepare语句缓存的有效时间;单位ms, 有效值范围0~2147483647,0表示无限制;默认0;
escapeProcess 是否进行语法转义处理;取值1/0 或 true/false;默认true;
autoCommit 是否自动提交;取值1/0 或 true/false;默认true;
alwayseAllowCommit 在自动提交开关打开时,是否允许手动提交回滚;取值1/0 或 true/false;默认true;
localTimezone 指定客户端本地时区,对于本地时区相关时间类型会自动完成服务器时区与本地时区的转换;单位分钟,有效值范围-720~720;默认为当前系统时区;
maxRows 结果集行数限制,超过上限结果集截断;有效值范围0~2147483647,0表示无限制;默认0;
bufPrefetch 结果集fetch预取消息buffer大小;单位KB,有效值范围32~65535。默认0表示按服务器配置,若结果集上指定了fetchSize会自动预估大小;
LobMode 大字段数据获取模式;1 表示get数据时从服务器段获取,2 表示结果集生成时将大字段数据完整缓存到本地;默认1;
ignoreCase 结果集列名是否忽略大小写;取值1/0 或 true/false;默认true;
continueBatchOnError 批量执行出错时是否继续执行;默认false;取值(true/True,false/False)
batchType 批处理模式;1 表示批量绑定执行 2 表示一行一行执行;默认1;
resultSetType 指定默认创建结果集类型;取值为java标准中的ResultSet.TYPE_FORWARD_ONLY,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.TYPE_SCROLL_SENSITIVE,默认ResultSet.TYPE_FORWARD_ONLY
dbmdChkPrv 编目函数是否进行权限检测;取值1/0 或 true/false;默认true;
isBdtaRS 是否使用列模式结果集,需同步服务器开启该功能;取值1/0 或 true/false;默认true;
clobAsString clob类型列调用resultSetMetaData的getColumnType()映射为Types.VARCHAR类型;取值1/0 或 true/false;默认false;
columnNameUpperCase 列名转换为大写字母;取值1/0 或 true/false;默认false;
compatibleMode 兼容其他数据库;取值为数据库名称:oracle表示兼容oracle,mysql表示兼容mysql;
schema 指定用户登录后的当前模式,默认为用户的默认模式
loginMode 指定优先登录的服务器模式。0:优先连接PRIMARY模式的库,NORMAL模式次之,最后选择STANTBY模式;1:只连接主库;2:只连接备库;3:优先连接STANDBY模式的库,PRIMARY模式次之,最后选择NORMAL模式;4:优先连接NORMAL模式的库,PRIMARY模式次之,最后选择STANDBY模式;默认4;
loginStatus 服务名方式连接数据库时只选择状态匹配的库; 0表示不限制;3表示mount状态;4 表示open状态;5 表示suspend状态;默认0;
loginDscCtrl 服务名连接数据库时只选择dsc control节点的库;取值1/0 或 true/false;默认false;
epSelector 服务名连接数据库时采用何种模型建立连接;0表示依次选取列表中的不同节点建立连接,使得所有连接均匀地分布在各个节点上;1表示选择列表中最前面的节点建立连接,只有当前节点无法建立连接时才会选择下一个节点进行连接;默认0;
doSwitch 若使用服务名方式连接数据库,且服务名中配置了多个ip,当连接发生异常时是否自动切换到其他库,无论切换成功还是失败都会抛一个SQLException,用于通知上层应用进行事务执行失败时的相关处理;0表示关闭连接;1表示自动切换到其他库;2 配合epselector=1使用,如果服务名列表前面的节点恢复了,将当前连接切换到前面的节点上;4开启动态负载均衡;默认0;
switchTimes 服务名连接数据库时,若未找到符合条件的库成功建立连接,将尝试遍历服务名中库列表的次数;有效值范围1~2147483647;默认1;
switchInterval 服务名连接数据库时,若遍历了服务名中所有库列表都未找到符合条件的库成功建立连接,等待一定时间再继续下一次遍历;单位ms,有效值范围0~2147483647;默认1000;
cluster 配合auto_reconnect=2、epselector=1使用,用于检测DSC集群节点故障恢复是否成功。取值:DSC,说明用于DSC环境中;
dbAliveCheckFreq 检测数据库是否存活的频率;单位ms,有效值范围0~2147483647,0表示不检测;默认0;
compress 是否压缩消息;0表示不压缩;1表示完全压缩;2表示优化的压缩;默认0;
compressID 消息压缩算法标识,最终与服务器支持情况协商决定;0表示zip;1表示snappy; 默认0;
sslFilesPath 数据库端开启ssl通信加密,该参数指定ssl加密文件的路径
sslKeystorePass 数据库端开启ssl通信加密,该参数指定ssl加密文件的指令
kerberosLoginConfPath 用户名加前缀”///”标识开启Kerberors认证,该参数指定Kerberors认证登录配置文件路径
uKeyName Ukey的用户名
uKeyPin Ukey的口令
cipherPath 第三方加密算法引擎所在路径
OsAuthType 指定操作系统认证用户类型,开启操作系统认证时,用户名使用系统用户名。0 表示关闭;1 表示DBA;2 表示SSO; 3 表示AUDITOR;4 表示自适应。默认0
loginEncrypt 是否进行通信加密;取值1/0 或 true/false;默认true;
loginCertificate 该参数用于指定dmkey工具生成的公钥文件路径,非加密通信的情况下,可对登录用户名密码进行增强加密;
mppLocal 是否MPP本地连接;取值1/0 或 true/false;默认false;
mppOpt Mpp集群批量插入数据的优化处理;范围0~1;默认0;
rwSeparate 是否使用读写分离系统;0表示不启用;1表示启用;2表示启用,备库由客户端进行选择,且只会选择服务名中配置的节点。默认0
rwPercent 分发到主库的事务占主备库总事务的百分比;单位%,范围0~100;默认25;
rwAutoDistribute 读写分离系统事务分发是否由JDBC自动管理;取值1/0 或 true/false;默认true;false:事务分发由用户管理,用户可通过设置连接上的 readOnly属性标记事务为只读事务;
rwHA 是否开启读写分离系统高可用;取值1/0 或 true/false;默认false;
rwStandbyRecoverTime 读写分离系统备库故障恢复检测间隔,单位ms,有效值范围0~2147483647,0表示不回复;默认60000
enRsCache 是否开启结果集缓存;取值1/0 或 true/false;默认false;
rsCacheSize 设置结果集缓冲区大小,以M为单位。有效值范围1~65535,如果设置太大,可能导致空间分配失败,进而使缓存失效
rsRefreshFreq 结果集缓存检查更新的频率,以秒为单位,有效值范围0~10000,如果设置为0,则不需检查更新;
keyWords 标识用户关键字,所有在列表中的字符串,如果以单词的形式出现在sql语句中,则这个单词会被加上双引号;默认为空串
logDir 日志等其他一些JDBC过程文件生成目录,默认为jvm当前工作目录;
logLevel 生成日志的级别,日志按从低到高依次如下(off:不记录;error:只记录错误日志;warn:记录警告信息;sql:记录sql执行信息;info:记录全部执行信息;all:记录全部),高级别同时记录低级别的信息;默认off;
logFlushFreq 日志刷盘频率;单位s,有效值范围0~2147483647;默认60;
statEnable 是否启用状态监控;取值1/0 或 true/false;默认false;
statDir 状态监控信息以文本文件形式输出的目录,默认为jvm当前工作目录;
statFlushFreq 状态监控统计信息写文件刷盘频率;单位s,有效值范围0~2147483647;0表示不写文件;默认10;
statSlowSqlCount 统计慢sql top行数;有效值范围0~1000;默认100;
statHighFreqSqlCount 统计高频sql top行数;有效值范围0~1000;默认100;
statSqlMaxCount 状态监控可以统计不同sql的个数;有效值范围0~100000;默认100000;
statSqlRemoveMode 执行的不同sql个数超过statSqlMaxCount时使用的淘汰方式;取值latest/eldest;latest表示淘汰最近执行的sql,eldest表示淘汰最老的sql;默认eldest;
dmsvcconf 指定url属性配置文件所在路径
dbAliveCheckTimeout 检测数据库是否存活的连接超时时间,如果该时间内未连接成功即认为数据库故障;单位ms,有效值范围1~2147483647;默认10000;
check_freq 服务名连接数据库时,循环检测连接是否需要重置的时间间隔。即每间隔设定时间检测连接对象是否发生改变,若连接对象发生改变,JDBC连接会自动重置到新对象。单位ms,有效值范围0~2147483647。默认值300000
prepareOptimize 是否对预编译SQL做优化,取值true/false。默认false
checkFreq 检测连接动态负载均衡的时间间隔,可以使用单位(h:小时,m:分钟,s:秒,ms:毫秒),不带单位使用时取配置项默认单位ms,有效值范围0~2147483647。默认值5分钟
allowRange 允许动态负载均衡误差范围的百分比,百分比越大表示允许的误差范围越大,有效值范围0~50。默认值5

4.6 Statement/PreparedStatement/CallableStatement

4.6.1 Statement

  1. 概述

Statement 对象用于将SQL 语句发送到数据库服务器。DM JDBC提供三种类型的语句对象:Statement,PreparedStatement,CallableStatement。其中PreparedStatement是Statement的子类,CallableStatement是PreparedStatement的子类。每一种语句对象用来运行特定类型的SQL语句。

Statement对象用来运行简单类型的SQL语句,语句中无需指定参数。

PreparedStatement对象用来运行包含(或不包含)IN类型参数的预编译SQL语句。

CallableStatement对象用来调用数据库存储过程。

  1. 创建Statement对象

建立连接后,Statement对象用Connection对象的createStatement方法创建,以下代码创建Statement对象:

Connection con = DriverManager.getConnection(url, "SYSDBA", "SYSDBA");
Statement stmt = con.createStatement();
  1. 使用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对象的处理。

  1. 关闭 Statement 对象

Statement对象可由Java垃圾收集程序自动关闭。但作为一种良好的编程风格,应在不需要Statement对象时显式地关闭它们。这将立即释放数据库服务器资源,有助于避免潜在的内存问题。

  1. 性能优化调整
  1. 批处理更新

DM JDBC驱动程序提供批处理更新的功能,通过批处理更新可以一次执行一个语句集合。JDBC提供了三个方法来支持批处理更新:通过addBatch方法,向批处理语句集中增加一个语句;通过executeBatch执行批处理更新;通过clearBatch来清除批处理语句集。

批处理更新过程中,如果出现执行异常,DM将立即退出批处理的执行,同时返回已经被执行语句的更新元组数(在自动提交模式下)。

推荐批处理更新在事务的非自动提交模式下执行。同时,批处理更新成功后,需要主动提交以保证事务的永久性。

2)性能优化参数设置

DM JDBC驱动程序提供了setFetchDirection、setFetchSize来向驱动程序暗示用户操作语句结果集的缺省获取方向和一次获取的缺省元组数。这些值的设定可以为驱动程序优化提供参考。

  1. 语句对象

JDBC在创建语句对象时,可以指定语句对象的缺省属性:结果集类型和结果集并发类型。DM JDBC驱动程序支持TYPE_FORWARD_ONLY和TYPE_SCROLL_INSENSITIVE两种结果集类型,不支持TYPE_SCROLL_SENSITIVE结果集类型;支持CONCUR_READ_ONLY、CONCUR_UPDATABLE两种结果集并发类型。

语句对象的缺省属性用来指定执行语句产生的结果集的缺省类型。

注意事项:

DM JDBC驱动程序中当执行的查询语句涉及多个基表时,结果集不能更新,结果集的类型可能被自动转换为CONCUR_READ_ONLY类型。

4.6.2 PreparedStatement

  1. 概述

PreparedStatement 继承 Statement,并与之在两方面有所不同:

1)PreparedStatement 对象包含已编译的 SQL 语句,语句已经“准备好”;

2)包含于PreparedStatement对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在SQL语句创建时未被指定。相反,该语句为每个 IN
参数保留一个问号(“?”)作为占位符。每个问号所对应的值必须在该语句执行之前,通过适当的setXXX 方法来提供。

由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement对象。因此,需要多次重复执行的SQL语句经常创建为PreparedStatement对象,以提高效率。

作为 Statement 的子类,PreparedStatement继承了Statement的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。同时,三种方法execute、executeQuery和executeUpdate能执行设置好参数的语句对象。

  1. 创建 PreparedStatement 对象

以下的代码段(其中con是 Connection 对象)创建一个PreparedStatement对象:

PreparedStatement pstmt = con.prepareStatement(
"UPDATE person.person SET name = ? WHERE personid = ?");

对象pstmt包含语句 "UPDATE person.person SET name = ? WHERE personid =?",该语句带两个IN参数占位符,它已发送给数据库,并由服务器为其执行作好了准备。

  1. 传递 IN 参数

在执行 PreparedStatement对象之前,必须设置占位符(“?”)的值。这可通过调用setXXX方法来完成,其中XXX是与该参数相应的类型。例如,如果参数具有Java类型String,则使用的方法就是setString。setXXX方法的第一个参数是要设置的参数的序号(从1算起),第二个参数是设置给该参数的值。譬如,以下代码将第一个参数设为“张三”,第二个参数设为“18”:

pstmt.setString(1, "张三");
pstmt.setInt(2, 18);

每当设置了给定语句的参数值,就可执行该语句。设置一组新的参数值之前,应先调用clearParameters 方法清除原先设置的参数值。

  1. 使用 setObject

setObject方法可显式地将输入参数转换为特定的JDBC类型。该方法可以接受三个参数,其中第三个参数用来指定目标JDBC类型。将Java Object发送给数据库之前,驱动程序将把它转换为指定的JDBC类型。例如,上面的setXXX语句就可以改写为:

pstmt.setObject(1, "张三", java.sql.Types.VARCHAR);

pstmt.setObject(2, 18, java.sql.Types.INTEGER);

如果没有指定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类型指定为任意值,同时参数的目标精度也不再起作用。

  1. 发送大的 IN 参数

setBytes和setString方法能够发送无限量的数据。但是,内存要足够容纳相关数据。有时程序员更喜欢用较小的块传递大型的数据,这可通过将IN参数设置为Java输入流来完成。当语句执行时,JDBC驱动程序将重复调用该输入流,读取其内容并将它们当作实际参数数据传输。

JDBC提供了四种将IN参数设置为输入流的方法:setBinaryStream用于字节流,setAsciiStream用于ASCII字符流,setUnicodeStream用于Unicode字符流,从JDK1.2起,输入字符流的新方法为setCharacterStream,而setAsciiStream和setUnicodeStream已经很少用。

  1. 获得参数元数据

DM JDBC实现了getParameterMetaData()方法,通过这个方法可以获得有关IN参数的各种属性信息,比如类型、精度、刻度等信息,类似于结果集元数据的内容。通过这些信息,用户可以更准确地设置IN参数的值。

在下面的代码中涉及到了这种方法:

PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM person.person " + "WHERE personid = ?");
...
//获得参数元数据对象
ParameterMetaData pmd = pstmt.getParameterMetaData();
//获得参数的个数
int paramCount = pmd.getParameterCount();
//获得第一参数的类型
int colType = pmd.getParameterType(1);
…
  1. 自定义方法列表

为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现PreparedStatement接口的过程中,增加了一些自定义的扩展方法。用户将获得的PreparedStatement对象反溯成DmdbPreparedStatment类型就可以访问这些方法。这些方法所涉及到的扩展类请参看4.4节。

表4.2 自定义方法列表
方法名 功能说明
setINTERVALYM 设置参数为年-月时间间隔类型值
setINTERVALDT 设置参数为日-时时间间隔类型值
setTIME 设置参数为时间类型(带纳秒)

例如,想要插入一个年月时间间隔类型,代码如下(pstmt为PreparedStatement类型):

DmdbPreparedStatement dmps = (DmdbPreparedStatement)pstmt;
DmdbIntervalYM dmiym = new DmdbIntervalYM("interval '20-10' year to month");
dmps.setINTERVALYM(1, dmiym);

另外,DM中不支持JDBC规范中所要求的标准方法setURL和setRef。

4.6.3 CallableStatement

  1. 概述

CallableStatement用来运行SQL存储过程。存储过程是数据库中已经存在的SQL语句,它通过名字调用。

CallableStatement 是PreparedStatement 的子类。CallableStatement中定义的方法用于处理 OUT 参数或 INOUT 参数的输出部分:注册OUT参数的 JDBC类型(一般SQL类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。

  1. 创建 CallableStatement 对象

CallableStatement对象是用Connection.prepareCall创建的。以下代码创建CallableStatement对象,其中含有对存储过程p1的调用,con为连接对象:

CallableStatement cstmt = con.prepareCall("call p1(?, ?)");

其中"?"占位符为 IN、OUT还是INOUT参数,取决于存储过程p1。

  1. 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 VARCHAR(10), a2 OUT VARCHAR(50)) AS
DECLARE
CURSOR CUR1 FOR SELECT name FROM person.person WHERE personid = a1;
BEGIN
OPEN CUR1;
FETCH CUR1 INTO a2;
END;

以下代码先注册OUT参数,执行由cstmt所调用的存储过程,然后检索通过OUT参数返回的值。方法getString从OUT参数中取出字符串:

CallableStatement cstmt = con.prepareCall("call p1(?, ?)");
cstmt.setString(1, "1");
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString(2);
  1. INOUT 参数

如果参数为既接受输入又接受输出的参数类型(INOUT 参数),那么除了调用registerOutParameter方法外,还要调用对应的setXXX方法(继承自PreparedStatement)。setXXX方法将参数值设置为输入参数,而registerOutParameter方法将它的JDBC类型注册为输出参数。setXXX方法提供一个Java值,驱动程序先把这个值转换为JDBC值,然后将它送到数据库服务器。

该IN值的JDBC类型和提供给registerOutParameter方法的JDBC类型应该相同。如果要检索输出值,就要用对应的getXXX方法。

设有一个存储过程 p2的定义如下:

CREATE OR REPLACE PROCEDURE p2(a1 IN OUT VARCHAR(10)) AS
DECLARE
CURSOR CUR1 FOR SELECT name FROM person.person WHERE personid = a1;
BEGIN
OPEN CUR1;
FETCH CUR1 INTO a1;
END;

以下代码中,方法setString把参数设为“1”。然后,registerOutParameter将该参数注册为JDBC VARCHAR。执行完该存储过程后,将返回一个新的JDBC VARCHAR值。方法getString将把这个新值作为Java的String类型返回。

CallableStatement cstmt = con.prepareCall("call p2(?)");
cstmt.setString(1, "1");
cstmt.registerOutParameter(1, java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString(1);
  1. 利用参数名进行操作

在通常的情况下一般采用参数索引来进行赋值。JDBC3.0规范要求可以利用参数名来对参数进行赋值,DM JDBC驱动程序实现了这一点。如果某个存储过程的一些参数有默认值,这时候采用参数名进行赋值就非常有用,用户可以只对那些没有默认值的参数进行赋值。参数名可以通过调用DatabaseMetaData.getProcedureColumns()来获得。

下面的代码中,存储过程p2为上面那个存储过程p2。利用参数名进行操作:

CallableStatement cstmt = con.prepareCall("CALL p2 (?)");
cstmt.setString("a1", "1");
cstmt.registerOutParameter("a1", java.sql.Types.VARCHAR);
cstmt.executeUpdate();
String x = cstmt.getString("a1");

而且,在读取参数的值和进行参数注册的时候,setXXX、getXXX、registerOutParameter也都可以采用参数名来进行操作。通过调用DatabaseMetaData.supportsNamedParameters()方法就可以确定JDBC驱动程序是否支持利用参数名来进行操作。

在执行同一条语句的过程中,不允许交叉使用参数索引和参数名来进行操作,否则会抛出异常。

  1. 自定义方法列表

为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现CallableStatement接口的过程中,增加了一些自定义的扩展方法。用户将获得的CallableStatement对象反溯成DmdbCallableStatment类型就可以访问这些方法。这些方法所涉及到的扩展类请参看4.4 DM JDBC扩展这一节。

表4.3 自定义方法列表
方法名 功能说明
getINTERVALYM(int) 获得参数的值
getINTERVALYM(String) 获得参数的值
getINTERVALDT(int) 获得参数的值
getINTERVALDT(String) 获得参数的值
getTIME(int) 获得参数的值
getTIME(String) 获得参数的值
setTIME(String,String) 根据参数名来设置DmdbTime类型值。
setINTERVALDT(String,String) 根据参数名来设置DmdbIntervalDT类型值。
setINTERVALYM(String,String) 设置参数为年-月时间间隔类型值

另外,DM中不支持JDBC规范中所要求的标准方法setURL、getURL和getRef。由于CallableStatement是PreparedStatement的子类,因此,它将继承PreparedStatement的所有的方法。

4.7 ResultSet

  1. 概述

ResultSet提供执行SQL语句后从数据库返回结果中获取数据的方法。执行SQL语句后数据库返回结果被JDBC处理成结果集对象,可以用ResultSet对象的next方法以行为单位进行浏览,用getXXX方法取出当前行的某一列的值。

通过Statement,PreparedStatement,CallableStatement三种不同类型的语句进行查询都可以返回ResultSet类型的对象。

  1. 行和光标

ResultSet维护指向其当前数据行的逻辑光标。每调用一次next方法,光标向下移动一行。最初它位于第一行之前,因此第一次调用next将把光标置于第一行上,使它成为当前行。随着每次调用next导致光标向下移动一行,按照从上至下的次序获取ResultSet行。

在ResultSet对象或对应的Statement对象关闭之前,光标一直保持有效。

方法getXXX提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。

列名或列号可用于标识要从中获取数据的列。例如,如果ResultSet对象rs的第二列名为“title”,则下列两种方法都可以获取存储在该列中的值:

String s = rs.getString("title");
String s = rs.getString(2);

注意列是从左至右编号的,并且从1开始。

在DM JDBC驱动程序中,如果列的全名为“表名.列名”的形式。在不引起混淆的情况下(结果集中有两个表具有相同的列名),可以省略表名,直接使用列名来获取列值。

关于ResultSet中列的信息,可通过调用方法ResultSet.getMetaData得到。返回的ResultSetMetaData对象将给出其ResultSet对象各列的名称、类型和其他属性。

  1. NULL 结果值

要确定给定结果值是否是JDBC NULL,必须先读取该列,然后使用ResultSet对象的wasNull方法检查该次读取是否返回JDBC NULL,如下:

String sql="select * from person.person";
ResultSet rs;
rs = stmt.executeQuery(sql);
while (rs.next()){
	if(rs.wasNull())
		System.out.println("Get A Null Value");
}

当使用ResultSet对象的getXXX方法读取JDBC NULL时,将返回下列值之一:

  1. Java null值:对于返回Java对象的getXXX方法(如getString、getBigDecimal、getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、getObject等);
  2. 零值:对于getByte、getShort、getInt、getLong、getFloat 和 getDouble;
  3. false 值:对于getBoolean。
  1. 结果集增强特性

在DM JDBC驱动程序中提供了符合JDBC 2.0标准的结果集增强特性:可滚动、可更新结果集,及JDBC3.0标准的可持有性。

  1. 结果集的可滚动性

通过执行语句而创建的结果集不仅支持向前(从第一行到最后一行)浏览内容,而且还支持向后(从最后一行到第一行)浏览内容的能力。支持这种能力的结果集被称为可滚动的结果集。可滚动的结果集同时也支持相对定位和绝对定位。绝对定位指的是通过指定在结果集中的绝对位置而直接移动到某行的能力,而相对定位则指的是通过指定相对于当前行的位置来移动到某行的能力。DM支持可滚动的结果集。

DM JDBC驱动程序中支持只向前滚结果集(ResultSet.TYPE_FORWARD_ONLY)和滚动不敏感结果集(ResultSet.TYPE_SCROLL_INSENSITIVE)两种结果集类型,不支持滚动敏感结果集(ResultSet.TYPE_SCROLL_SENSITIVE)。当结果集为滚动不敏感结果集时,它提供所含基本数据的静态视图,即结果集中各行的成员顺序、列值通常都是固定的。

  1. 结果集的可更新性

DM JDBC 驱动程序中提供了两种结果集并发类型:只读结果集(ResultSet.CONCUR_READ_ONLY)和可更新结果(ResultSet.CONCUR_UPDATABLE)。采用只读并发类型的结果集不允许对其内容进行更新。可更新的结果集支持结果集的更新操作。

  1. 结果集的可持有性

JDBC 3.0提供了两种结果集可持有类型:

提交关闭结果集(ResultSet.CLOSE_CURSORS_AT_COMMIT)和跨结果集提交(ResultSet.HOLD_CURSORS_OVER_COMMIT)。采用提交关闭结果集类型的结果集在事务提交之后被关闭,而跨结果集提交类型的结果集在事务提交之后仍能保持打开状态。

通过DatabaseMetaData.supportsHoldability()方法可以确定驱动程序是否支持结果集的可持有性。目前DM支持这两种类型。

  1. 性能优化

DM JDBC 驱动程序的结果集对象中提供了方法setFetchDirection和setFetchSize来设置缺省检索结果集的方向和缺省一次从数据库获取的记录条数。它们的含义与用法和语句对象中的同名函数是相同的。

  1. 更新大对象数据

从DM JDBC 2.0驱动程序就支持可更新的结果集,但是对LOB对象只能读取,而不能更新,这也是JDBC 2.0标准所规定的。而JDBC 3.0规范规定用户可以对LOB对象进行更新,DM JDBC 3.0驱动程序中实现了这一点:

假设数据库中存在BOOKLIST表,且含有id、comment两个字段,建表语句如下:

CREATE TABLE BOOKLIST ("ID" INTEGER,"COMMENT" TEXT);
INSERT INTO SYSDBA.BOOKLIST VALUES (1, '测试数据');
Statement stmt = conn.createStatement(
	ResultSet.TYPE_FORWARD_ONLY,
	ResultSet.CONCUR_UPDATABLE);
	ResultSet rs = stmt.executeQuery("select comment from booklist " +"where id = 1"
);
rs.next();
Clob commentClob = new Clob(...);
rs.updateClob("author", commentClob); // commentClob is a Clob Object
rs.updateRow();
  1. 自定义方法列表

为了实现对达梦数据库所提供的时间间隔类型和带纳秒的时间类型的支持,在实现ResultSet接口的过程中,增加了一些自定义的扩展方法。用户将获得的ResultSet对象反溯成DmdbResultSet类型就可以访问这些方法。这些方法所涉及到的扩展类请参看4.4节。

表4.4 自定义方法列表
方法名 功能说明
getTime(int) 根据列号(1开始)获取时间信息,以java.sql.Time类型返回。
getTime(int, Calendar) 根据列号(1开始)、Calendar对象获取时间信息, 以java.sql.Time类型返回。
getTime(String) 根据列名获取时间信息,以java.sql.Time类型返回。
getTime(String, Calendar) 根据列名、Calendar对象获取时间信息, 以java.sql.Time类型返回。
getTimestamp(int) 根据列号(1开始)获取时间信息,以java.sql.Timestamp类型返回。
getTimestamp(int, Calendar) 根据列号(1开始)、Calendar对象获取时间信息, 以java.sql.Timestamp类型返回。
getTimestamp(String) 根据列名获取时间信息,以java.sql.Timestamp类型返回。
getTimestamp(String, Calendar) 根据列名、Calendar对象获取时间信息, 以java.sql.Timestamp类型返回。
updateINTERVALYM(int, DmdbIntervalYM ) 设置列为年-月时间间隔类型值
updateINTERVALYM(String, DmdbIntervalYM) 设置列为年-月时间间隔类型值
updateINTERVALDT(int, DmdbIntervalDT) 设置列为日-时时间间隔类型值
updateINTERVALDT(String, DmdbIntervalDT) 设置列为日-时时间间隔类型值

另外,DM中不支持getURL和getRef方法。

4.8 流与大对象

4.8.1 Stream使用

为了获取大数据量的列,DM JDBC驱动程序提供了四个获取流的方法:

  1. getBinaryStream 返回只提供数据库原字节而不进行任何转换的流;
  2. getAsciiStream 返回提供单字节 ASCII 字符的流;
  3. getUnicodeStream 返回提供双字节 Unicode 字符的流;
  4. getCharacterStream 返回提供双字节 Unicode 字符的java.io.Reader流。

在这四个函数中,JDBC规范不推荐使用getUnicodeStream方法,其功能可以用getCharacterStream代替。以下是采用其它三种方法获取流的示例代码:

  1. 采用getBinaryStream 获取流
// 查询语句
String sql = "SELECT description FROM production.product WHERE productid=1";
// 创建语句对象
Statement stmt = conn.createStatement();
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 显示结果集
while (rs.next()) {
	try {
		InputStream stream = rs.getBinaryStream("description");
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int num = -1;
		while ((num = stream.read()) != -1) {
			baos.write(num);
		}
		System.out.println(baos.toString());
	} catch (IOException e) {
		e.printStackTrace();
	}
}
// 关闭结果集
rs.close();
// 关闭语句
stmt.close();
  1. 采用getAsciiStream获取流
// 查询语句
String sql = "SELECT description FROM production.product WHERE productid=1";
// 创建语句对象
Statement stmt = conn.createStatement();
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 显示结果集
while (rs.next()) {
	try {
		InputStream stream = rs.getAsciiStream("description");
		StringBuffer desc = new StringBuffer();
		byte[] b = new byte[1024];
		for (int n; (n = stream.read(b)) != -1;) {
			desc.append(new String(b, 0, n));
		}
		System.out.println(desc.toString());
	} catch (IOException e) {
		e.printStackTrace();
	}
}
// 关闭结果集
rs.close();
// 关闭语句
stmt.close();
  1. 采用getCharacterStream获取流
// 查询语句
String sql = "SELECT description FROM production.product WHERE productid=1";
// 创建语句对象
Statement stmt = conn.createStatement();
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 显示结果集
while (rs.next()) {
	try {
		Reader reader = rs.getCharacterStream("description");
		BufferedReader br = new BufferedReader(reader);
		String thisLine;
		while ((thisLine = br.readLine()) != null) {
			System.out.println(thisLine);
		}
		 
	} catch (IOException e) {
		e.printStackTrace();
	}
}
// 关闭结果集
rs.close();
// 关闭语句
stmt.close();

4.8.2 LOB对象使用

  1. 概述

JDBC标准为了增强对大数据对象的操作,在JDBC3.0标准中增加了java.sql.Blob和java.sql.Clob这两个接口。这两个接口定义了许多操作大对象的方法,通过这些方法就可以对大对象的内容进行操作。

  1. 产生Lob对象

在ResultSet和CallableStatement对象中调用getBlob()和getClob()方法就可以获得Blob对象和Clob对象:

Blob blob = rs.getBlob(1);
Clob clob = rs.getClob(2);
  1. 设置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)、updateBlob(String,Blob)和updateClob(int,Clob)、updateClob(String,Clob)来更新当前行。

  1. 改变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();

目前LOB内容的更新和其当前所处的事务之间没有直接的联系。更新LOB时,会直接写入到数据库中,即便当前事务最终回滚也不能恢复。

4.9 元数据

4.9.1 ResultSetMetaData

  1. 概述

ResultSetMetaData提供许多方法,用于读取ResultSet对象返回数据的元信息。包括:列名、列数据类型、列所属的表、以及列是否允许为NULL值等,通过这些方法可以确定结果集中列的一些信息。

  1. 创建结果集元数据对象

结果集元数据是用来描述结果集的特征,所以,需要首先执行查询获得结果集,才能创建结果集元数据对象。

  1. 创建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);
}

4.9.2 DatabaseMetaData

  1. 概述

DatabaseMetaData提供了许多方法,用于获取数据库的元数据信息。包括:描述数据库特征的信息(如是否支持多个结果集)、目录信息、模式信息、表信息、表权限信息、表列信息、存储过程信息等。DatabaseMetaData有部分方法以ResultSet对象的形式返回结果,可以用ResultSet对象的getXXX()方法获取所需的数据。

  1. 创建数据库元数据对象

数据库元数据对象由连接对象创建。以下代码创建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);

4.9.3 ParameterMetaData

  1. 概述

参数元数据是JDBC 3.0标准新引入的接口,它主要是对PreparedStatement、CallableStatement对象中的占位符(“?”)参数进行描述,例如参数的个数、参数的类型、参数的精度等信息,类似于ResultSetMetaData接口。通过引入这个接口,就可以对参数进行较为详细、准确的操作。

  1. 创建参数元数据对象

通过调用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));
}

4.10 RowSet

RowSet 接口扩展了标准java.sql.ResultSet接口。RowSetMetaData接口扩展了java.sql.ResultSetMetaData 接口。JDK 5.0 定义了5个标准的 JDBCRowSet接口,DM实现了其中的CachedRowSet和JdbcRowSet。

RowSet对象可以建立一个与数据源的连接并在其整个生命周期中维持该连接,在此情况下,该对象被称为连接的Rowset。Rowset还可以建立一个与数据源的连接,从其获取数据,然后关闭它。这种Rowset 被称为非连接Rowset。非连接Rowset可以在断开时更改其数据,然后将这些更改发送回原始数据源,不过它必须重新建立连接才能完成此操作。相比较java.sql.ResultSet而言,RowSet的离线操作能够有效的利用计算机越来越充足的内存,减轻数据库服务器的负担,由于数据操作都是在内存中进行然后批量提交到数据源,灵活性和性能都有了很大的提高。RowSet默认是一个可滚动,可更新,可序列化的结果集,而且它作为JavaBeans,可以方便地在网络间传输,用于两端的数据同步。

4.10.1 CachedRowSet

CachedRowSet是非连接的RowSet,数据行均被缓冲至本地内存,但并未保持与数据库服务器的连接。DmJdbcDriver15.jar和DmJdbc16.jar中dm.jdbc.rowset.DmdbCachedRowSet类是达梦对于接口javax.sql.rowset.CachedRowSet的实现。

1.使用URL、用户名、密码和一个查询SQL语句作为设置属性,创建一个DmdbCachedRowSet对象。RowSet使用execute方法完成CachedRowSet对象的填充。完成execute方法执行后,可以像使用java.sql.ResultSet对象方法一样,使用RowSet对象返回、滚动、插入、删除或更新数据。

/* 创建DmdbCachedRowSet对象示例 */
String sql = "SELECT productid,name,author FROM production.product";
CachedRowSet crs = new DmdbCachedRowSet();
crs.setUrl("jdbc:dm://localhost:5236");
crs.setUsername("SYSDBA");
crs.setPassword("SYSDBA");
crs.setCommand(sql);
crs.execute();
while (crs.next())
{
	System.out.println("productid: " + crs.getInt(1));
	System.out.println("name: " + crs.getString(2));
	System.out.println("author: " + crs.getString(3));
}

2、CachedRowSet对象也可以通过调用populate方法,使用一个已经存在的ResultSet对象填充。完成填充后,便可以像操作ResultSet对象一样,返回、滚动、插入、删除或更新数据。

/* 使用populate方法填充代码片段 */
// 执行查询,获取ResultSet对象
String sql = "SELECT productid,name,author FROM production.product";
ResultSet rs = stmt.executeQuery(sql);

// 填充CachedRowSet
CachedRowSet crs = new DmdbCachedRowSet();
crs.populate(rs);

3、其他功能特点

创建一个CachedRowSet的拷贝:

CachedRowSet copy = crs.createCopy();

创建一个CachedRowSet的共享:

CachedRowSet shard = crs.createShared();

4、CachedRowSet限制

  • 仅支持单表查询,且无连接操作;
  • 因数据缓存在内存,故不支持大数据块;
  • 连接属性,如事务隔离级等,不能在填充(执行execute或populate)后设置,因为此时已经断开了与数据库服务器的连接,不能将这些属性设置到返回数据的同一个连接上。

4.10.2 JdbcRowSet

JdbcRowSet是对ResultSet对象的封装,是连接的RowSet。达梦关于JdbcRowSet的实现是dm.jdbc.rowset.DmdbJdbcRowSet类。DmdbJdbcRowSet在DmJdbcDriver15.jar和DmJdbcDriver16.jar中实现标准接口javax.sql.rowset.JdbcRowSet。

JdbcRowSet在其生命期中始终保持着与数据库服务器的连接。其所有调用操作,均渗透进对JDBC Connection、statement和ResultSet调用。而CachedRowSet则不存在于打开数据库服务器的任何连接。

CachedRowSet在其操作过程中不需要JDBC驱动的存在,而JdbcRowSet需要。但两者在填充RowSet和提交数据修改的过程中,均需JDBC驱动的存在。

/* 使用JdbcRowSet接口示例 */
String sql = "SELECT name,author,publisher FROM production.product";
JdbcRowSet jrs = new DmdbJdbcRowSet();
jrs.setUrl("jdbc:dm://localhost:5236");
jrs.setUsername("SYSDBA");
jrs.setPassword("SYSDBA");
jrs.setCommand(sql);
jrs.execute(); 
int numcolsSum = jrs.getMetaData().getColumnCount();
while (jrs.next()) {
	for (int i = 1; i <= numcolsSum; i++) {
		System.out.print(jrs.getString(i) + "\t");
	}
	System.out.println();
}
jrs.close();

4.11 分布式事务支持

DM对分布式事务的支持是依据X/OPEN分布式事务处理模型XA规范实现的。

DM数据库系统实现了X/OPEN DTP模型中的RM组件,通过JDBC接口与第三方TM工具配合完成分布式事务处理。JDBC标准中,对XA协议进行了部分剪裁,仅支持TM对RM的单向调用,不允许RM向TM的动态注册。因此DM系统对XA协议的支持也仅为单向调用。

一个会话同时只能处理一个分支事务,一个分支事务同时也只能被一个会话绑定处理。

DM JDBC支持XA标准接口,包括:

javax.sql.XADataSource
javax.sql.XAConnection
javax.transaction.xa. XAResource
javax.transaction.xa.Xid

4.11.1 XADataSource

1. 概述

XADataSource是分布式连接的数据源,通过其可以获取分布式连接。

2. 获取分布式连接

XADataSource提供了两个方法用于获取分布式连接:

public XAConnection getXAConnection() throws SQLException
public XAConnection getXAConnection(String user, String password) throws
SQLException

第一个方法取给数据源设置的用户名和口令进行连接并返回得到的分布式连接,第二个方法则可以给定用户名和口令。

4.11.2 XAConnection

1. 概述

XAConnection是对分布式事务进行支持的对象。XAConnection对象通常是以XAResource对象的方式被纳入到分布式事务的管理中。

2. 获取XAResource对象

XAConneciton提供了javax.transaction.xa.XAResource getXAResource() throws SQLException 方法用于获取该分布式连接对应的XAResource对象,这个XAResource对象将代表XAConnection对象参加分布式事务的管理。

4.11.3 XAResource

1. 概述

XAResource对象是代表XAConnection对象参与分布式事务管理的,真正对分布式事务进行操控的方法都在这个接口中实现。

2. 主要方法介绍

  1. public void start(Xid xid, int flags) throws XAException

该方法用于开始一个事务分支。第一个参数xid用于指定事务分支的id,第二个参数flags用于指明开始这个事务分支的方式,其合法值及意义如下:

TMJOIN:这个标志指名该事务分支将被合并到之前的具有相同xid的事务分支上,如果资源管理器发现之前没有这个xid的事务分支,则抛出异常。

TMRESUME:这个标志指明该事务分支将重新开始之前用TMSUSPEND标志结束的一个事务分支。

TMNOFLAGS:当没有特殊操作而只是开始一个普通的事务分支时,将flags置为TMNOFLAGS。

  1. public void end(Xid xid, int flags) throws XAException

该方法用于结束一个事务分支。第一个参数xid用于指定事务分支的id,第二个参数flags用于指明结束这个事务分支的方式,其合法值及意义如下:

TMSUSPEND:这个标志指明暂时终止该事务分支,之后该事务分支应该被用TMRESUME重新开始或者用RMSUCCESS或TMFAIL正式结束。

TMSUCCESS:这个标志指明该事务分支成功结束。

TMFAIL:这个标志指明这个事务已经失败,则这个事务分支被资源管理器标记为只能回滚。

  1. public int prepare(Xid xid) throws XAException

该方法用来预提交一个事务分支。参数xid用于指定事务分支的id。

可能的返回值有:

XA_OK:该标志表明预提交成功。

XA_RDONLY:该标志表明事务分支具有只读属性并已被提交。

错误码:预提交失败并返回具体的错误码对应不同的失败信息。

  1. public void commit(Xid xid, boolean onePhase) throws XAException

该方法用来提交一个事务分支。第一个参数xid用于指定事务分支的id。第二个参数onePhase用来指定是否进行一阶段提交。

  1. public void rollback(Xid xid) throws XAException

该方法用来回滚一个事务分支。参数xid用于指定事务分支的id。

  1. public boolean isSameRM(XAResource xares) throws XAException

该方法用来判断指定的xares与当前XAResource对象是否是指向同一数据源的两个实例。

  1. public Xid[] recover(int flag) throws XAException

该方法用于返回资源管理器中所有已被预提交的事务分支的id。

flags的合法值及意义为:

TMSTARTRSCAN | TMENDRSCAN:一次获取到所有的已预提交的事务分支的id。

TMSTARTRSCAN:开始扫描并返回已预提交的事务分支的id,如果资源管理器中的已预提交事务分支过多,可能返回的不是全部。

TMNOFLAGS:用于继续扫描已预提交的事务分支的id。使用这一标志时,之前应该已经用TMSTARTRSCAN开始了扫描。

TMENDRSCAN:这个标志表明在此次扫描并返回对应的已预提交的事务分支的id后,结束此次扫描。

  1. public void forget(Xid xid) throws XAException

该方法用来“遗忘”一个自主完成的事务分支,这里的“遗忘”表示真正释放事务分支,之前即使一个事务分支已经自主完成,其事务分支对象仍然保留。对于非自主完成的事务分支使用此方法时将返回异常。

3. DM扩展方法介绍

DmdbXAResource在实现了标准接口定义的方法的基础上,还扩展实现了heurCommit()和heurRollback()方法。

  1. public void heurCommit(Xid xid) throws XAException

该方法用于自主提交一个事务分支。参数xid用于指定事务分支的id。

  1. public void heurRollback(Xid xid) throws XAException

该方法用于自主回滚一个事务分支。参数xid用于指定事务分支的id。

4.11.4 Xid

1. 概述

Xid接口是X/Open事务标识符XID结构的Java映射,由全局事务格式 ID、全局事务 ID 和分支限定符构成。

2. 使用介绍

DmdbXid提供了构造函数public DmdbXid(int fmtId, byte[] gTranId, byte[] bQual) throws XAException供用户生成一个Xid实例。

同时DmdbXid也实现了Xid标准接口中的三个用于获取XID 的格式标识符部分、XID的全局事务标识符部分作为字节数组和XID的事务分支标识符部分作为字节数组的三个方法。

4.11.5 实例解析

以下是一个使用JDBC的XA接口用两阶段提交协议来提交一个事务分支的例子。

DmdbXADataSource xaDS; 
XAConnection xaCon; 
XAResource xaRes; 
Xid xid; 
Connection con; 
Statement stmt; 
int ret; 

DmdbXADataSource xaDS = new DmdbXADataSource(); 

/* 获取分布式连接 */
xaCon = xaDS.getXAConnection("jdbc_user", "jdbc_password"); 

/* 获取代表分布式连接的XAResource实例 */
xaRes = xaCon.getXAResource(); 
con = xaCon.getConnection(); 
stmt = con.createStatement(); 

xid = new DmdbXid(100, new byte[]{0x01}, new byte[]{0x02}); 
try { 
/* 开始一个事务分支 */
  xaRes.start(xid, XAResource.TMNOFLAGS); 
  stmt.executeUpdate("insert into test_table values (100)"); 
/* 结束一个事务分支 */
  xaRes.end(xid, XAResource.TMSUCCESS); 
/* 预提交该事务分支 */
  ret = xaRes.prepare(xid); 
if (ret == XAResource.XA_OK) { 
	/* 提交事务分支 */
    xaRes.commit(xid, false); 
   } 
} 
catch (XAException e) { 
 e.printStackTrace(); 
} 
finally { 
 stmt.close(); 
 con.close(); 
 xaCon.close(); 
}
微信扫码
分享文档
扫一扫
联系客服