学习目标
n 对数据库进行SQL语句进行基本操作
n 预编译语句
n 使用事务
n 事务的级别控制
n 使用存储过程
n 操作元数据
n 可滚动的和可更新的结果集
n 批处理更新
n 字符大对象CLOB
n 二进制大对象BLOB
n RowSet 新特性
使用DDL,DML语言对数据库进行基本操作。
n 创建表并插入数据及修改数据:
import java.sql.Connection;
import java.sql.Statement;
public class CreateTable {
public static void main(String[] args) {
Connection con = null;
try {
// 通过连接池来获得一个连接
con = DBCon
.getConnectionFromPooledDataSource("jdbcPool/mydatasource");
// 创建语句对象
Statement st = con.createStatement();
// 创建表的SQL语句
String sql = "create table student(id int,name char(30),age int)";
// 执行完SQL语句的结果
boolean b = st.execute(sql);
if (b) {
System.out.println("create success");
} else {
System.out.println("create fail");
}
// 插入数据到student表
sql = "insert into student values(1,'andy',47)"
+ "insert into student values(2,'jacky',53)"
+ "insert into student values(3,'周润发',51)"
+ "insert into student values(4,'谢贤',60)";
// 执行完SQL语句的结果
b = st.execute(sql);
if (b) {
System.out.println("insert success");
} else {
System.out.println("create fail");
}
// 更新表数据
sql = "update student set name='刘德华' where id=1";
int rows = st.executeUpdate(sql);
// 如果更新成功,rows肯定是大于1的值
if (rows > 0)
System.out.println("update success");
else
System.out.println("update fail");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
查询数据库里的数据
Statement对象的executeQuery()方法一般用于执行一个select语句,它只返回一个结果集,要想把查询结果最后显示给用户,必须对ResultSet对象进行处理。ResultSet对象包括一个由查询语句返回的一个表,这个表中包含所有的查询结果。对ResultSet对象的处理必须逐行进行。ResultSet对象维持一个指向当前行的指针(类似于Iterator的用法)。最初,这个指针指向第一行之前。ResultSet的next()方法使这个指针移向下一行。因此,第一次使用next()方法将指针指向结果集的第一行,这是可以对第一行的数据进行处理。处理完毕后,使用next()方法,将指针移向下一行,继续处理第二行数据。next()方法的返回值是一个boolean值,若为true,则说明指针成功地移向下一行,可以对该行进行处理。若返回值是false,则说明没有下一行,即结果集已经处理完毕。按从左至右的顺序对各列进行处理可以获得较高的执行效率。ResultSet接口的getXXX()方法可以从某列中获得结果,XXX表示JDBC的数据类型。
请看下例:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
public class ResultSetTest {
public static void main(String[] args) {
Connection con = null;
try {
con = DBCon
.getConnectionFromPooledDataSource("jdbcPool/mydatasource");
Statement st = con.createStatement();
String query = "select id,name from student";
// 获得一个结果集
ResultSet rs = st.executeQuery(query);
// 获得结果集的元数据(表及相关的信息)
ResultSetMetaData rsmt = rs.getMetaData();
// 得到结果集有几列
int num = rsmt.getColumnCount();
String[] columns = new String[num];
// 列的序号是从1开始的
for (int i = 0; i < num; i++)
columns[i] = rsmt.getColumnName(i + 1);
// 先输出列名
for (int i = 0; i < num; i++)
System.out.print(columns[i] + " ");
// 输出列名之后换行
System.out.println();
// 取出结果
while (rs.next()) {
// 输出每一行的值
for (int i = 1; i <= num; i++) {
String temp = rs.getString(i);
System.out.print(temp + " ");
}
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 用完后要关闭连接,释放资源
if (con != null)
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
输出结果为:(Eclipse环境)
预编译语句(PreparedStatement)
Statement对象在每次执行SQL语句时都将该语句传给数据库,在多次执行同一语句时,这样做效率较低。这时可以使用PreparedStatement对象.如果数据库支持预编译,它可以将SQL语句传给数据库作预编译,以后每次执行这个SQL语句时,速度就可以提高很多。如果数据库不支持预编译,则在语句执行时,才将其传给数据库。这对用户来说是透明的。PreparedStatement对象的SQL语句还可以接受参数。在语句中指出需要接受哪些参数,然后进行预编译。在每一次执行时,可以给SQL语句传输不同的参数,这样就大大提高了灵活性。PreparedStatement接口是Statement接口派生的子接口,因此它可以使用Statement接口中的方法。
代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
public class PrepareStatementTest {
public static void main(String[] args) {
Connection con = null;
try {
con = DBCon
.getConnectionFromPooledDataSource("jdbcPool/mydatasource");
// 创建修改表的PrepareStatement SQL语句
String sql = "update student set name=? where id=?";
// 创建预编译语句对象
PreparedStatement st = con.prepareStatement(sql);
String[] names = new String[] { "梁朝伟", "贝壳汗母", "小罗", "霍元甲" };
for (int i = 0; i < names.length; i++) {
st.setString(1, names[i]);
st.setInt(2, i + 1);
st.executeUpdate();
}
st.close();
// 打印执行完SQL语句的结果
Statement stq = con.createStatement();
// 定义一个查询的SQL语句
String query = "select id,name from student";
// 获得一个结果集
ResultSet rs = stq.executeQuery(query);
// 获得结果集的元数据(表及的信息)
ResultSetMetaData rsmt = rs.getMetaData();
// 得到有几列,保存在num变量里
int num = rsmt.getColumnCount();
String[] columns = new String[num];
// 列的序号是从1开始的
for (int i = 0; i < num; i++)
columns[i] = rsmt.getColumnName(i + 1);
// 先输出列名
for (int i = 0; i < num; i++)
System.out.print(columns[i] + " ");
// 输出列名之后换行
System.out.println();
// 取出结果
while (rs.next()) {
// 输出每一行的值
for (int i = 1; i <= num; i++) {
String temp = rs.getString(i);
System.out.print(temp + " ");
}
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用事务
下面介绍如何把一系列语句组织成一个事务?如果事务中所有命令都能正确执行,就可以提交这个事务;否则,如果事务中有一个命令出现错误,回滚这个事务,并返回到提交之前的状态,好象什么也没有发生。把命令组合成事务的主要原因是保证数据库的完整性。对于一个事务而言,要么事务中语句全部得到正确执行,事务就可被提交了,要么它中间出现错误。后一种情况,可以调用rollback()方法,数据库将自动放弃上一次提交事务以来的全部变化。一个数据库连接的缺省模式是autocommit模式,每个SQL命令一执行就会提交给数据库。一旦某个命令已提交,就不能把它回退。可以用Connection接口的getAutocommit()方法,检验数据库的目前自动提交模式设置。用命令con.setAutoCommit(false)方法关闭自动提交模式。用con.commit()命令提交事务。用con.rollback()回滚一个事务。
代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.naming.NamingException;
public class UseTran {
public static void main(String[] args) {
Connection con = null;// 方法里临时变量要手动初始化
PreparedStatement updateAge = null;
String updateString = "update student "
+ "set age = ? where name like ?";
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
updateAge = con.prepareStatement(updateString);
int[] age = { 45, 39, 25, 96 };
String[] names = { "梁朝伟%", "贝壳汗母%", "小罗%", "霍元甲%" };
int len = age.length;
// 设置事务提交模式为非自动提交
con.setAutoCommit(false);
for (int i = 0; i < len; i++) {
updateAge.setInt(1, age[i]);
updateAge.setString(2, names[i]);
updateAge.executeUpdate();
}
// 上面执行的语句,如果不出现异常则提交 SQL 语句
con.commit();
System.out.println("update success!");
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException: " + ex.getMessage());
if (con != null) {
try {
System.err.print("Transaction is being ");
System.err.println("rolled back");
// 如果出现异常则事务回滚
con.rollback();
} catch (SQLException excep) {
System.err.print("SQLException: ");
System.err.println(excep.getMessage());
}
}
} finally {// 不管发生不发生异常,要关闭连接,释放资源
try {
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
事务的级别控制
在SQL术语中,事务是逻辑工作单元(logical unit of work,LUW)构成的一个或多个语句。这在某种含义上意味着,一切都是事务。不过,通常而言,术语事务用来表示或全或无的系列操作;也就是说,要么一切十分成功,要么什么也没有发生。典型的事务是从银行帐户提款,并存放到另一个。只要提款完成,金额就消失了。另一个范例是复式薄记记帐法中的借方和贷方:借方和贷方都必须完成。
在单用户模式,事务非常容易理解-它们只是和保存或忘记应用程序的状态有关。在多用户模式中,事务变得复杂多了。多用户事务的经典说明是银行帐户,其中一个应用程序试图在借记帐户,同时另一个应用程序试图贷记同一个帐户。在前面已经介绍过多线程编程(并发编程)。类似的,事务的根本问题是保持两个事务相互隔离,否则一个应用程序就可能影响另一个,从而导致错误的程序状态。当处理多个访问相同数据的用户时,通常可能出现三种问题:
n 脏读:当应用程序使用了被另一个应用程序修改过的数据,而这个数据处于未提交状态时,就会发生脏读。第二个应用程序随后会请求回滚被其修改的数据。第一个事务使用的数据就会被损坏,或者"变脏"。
n 不可重复的读:当一个事务获得了数据,而该数据随后被一个单独的事务所更改时,若第一个事务再次读取更改后的数据,就会发生不可重复的读。这样,第一个事务进行了一个不可重复的读。
n 虚读:当事务通过某种查询获取了数据,另一个事务修改了部分该数据,原来的事务第二次获取该数据时,就会发生虚读。第一个事务现在会有不同的结果集,它可能包含虚读。
为了解决与"多个线程请求相同数据"相关的问题,事务之间用锁相互隔开。多数主流的数据库支持不同类型的锁;因此,JDBC API支持不同类型的事务,它们由Connection对象指派或确定。在JDBC API中可以获得下列事务级别:
n TRANSACTION_NONE 说明不支持事务
n TRANSACTION_READ_UNCOMMITTED说明在提交前一个事务可以看到另一个事务的变化。这样脏读,不可重复的读和虚读都是允许的。
n TRANSACTION_READ_COMMITTED说明读取未提交的数据是不允许的。这个级别仍然允许不可重复的读和虚读产生。
n TRANSACTION_REPEATABLE_READ说明事务保证能够再次读取相同的数据而不会失败,但虚读仍然会出现。
n TRANSACTION_SERIALIZABLE是最高的事务级别,它防止脏读,不可重复读和虚读。
为什么不是所有事务都运行在TRANSACTION_SERIALIZABLE模式以保证最高程度的数据完整性呢?问题在于,和处理多线程编程有关的问题相似,事务保护的级别越高,性能损失就越大。假定您的数据库和JDBC驱动程序支持这个特性,则给定一个Connection对象,您可以明确地设置想要的事务级别:con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
您还可以确定当前事务的级别:
int transactionIsolation = con.getTransactionIsolation();
if (transactionIsolation == Connection.TRANSACTION_NONE)
System.out.println("当前事务级别为:TRANSACTION_NONE。");
代码:
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.NamingException;
public class TranTest {
public static void main(String[] args) {
String jndiname = "jdbcPool/mydatasource";
Connection con = null;
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
int transactionIsolation = con.getTransactionIsolation();
if (transactionIsolation == Connection.TRANSACTION_NONE) {
System.out.println("当前事务级别为:TRANSACTION_NONE。");
} else if (transactionIsolation == Connection.TRANSACTION_READ_COMMITTED) {
System.out.println("当前事务级别为:TRANSACTION_READ_COMMITTED。");
} else if (transactionIsolation == Connection.TRANSACTION_READ_UNCOMMITTED) {
System.out.println("当前事务级别为:TRANSACTION_READ_UNCOMMITTED。");
} else if (transactionIsolation == Connection.TRANSACTION_REPEATABLE_READ) {
System.out.println("当前事务级别为:TRANSACTION_REPEATABLE_READ。");
} else if (transactionIsolation == Connection.TRANSACTION_SERIALIZABLE) {
System.out.println("当前事务级别为:TRANSACTION_SERIALIZABLE。");
}
con.setAutoCommit(false);
con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
transactionIsolation = con.getTransactionIsolation();
if (transactionIsolation == Connection.TRANSACTION_NONE) {
System.out.println("当前事务级别为:TRANSACTION_NONE。");
} else if (transactionIsolation == Connection.TRANSACTION_READ_COMMITTED) {
System.out.println("当前事务级别为:TRANSACTION_READ_COMMITTED。");
} else if (transactionIsolation == Connection.TRANSACTION_READ_UNCOMMITTED) {
System.out.println("当前事务级别为:TRANSACTION_READ_UNCOMMITTED。");
} else if (transactionIsolation == Connection.TRANSACTION_REPEATABLE_READ) {
System.out.println("当前事务级别为:TRANSACTION_REPEATABLE_READ。");
} else if (transactionIsolation == Connection.TRANSACTION_SERIALIZABLE) {
System.out.println("当前事务级别为:TRANSACTION_SERIALIZABLE。");
}
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException : " + ex.getMessage());
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("程序执行结束!");
}
}
使用存储过程
很多数据库都支持在数据库内部执行的函数。这种方法有几个好处,包括更快的性能和改进的安全性。这些函数称为存储过程。存储过程是用来封装SQL语句来完成一个完整的业务功能,类似于面向对象里面方法的概念。虽然它们通常是用SQL编写的,但也可以用数据库支持的任何编程语言编写。随着Java语言日趋流行,几个数据库厂商---Oracle(Oracle数据库)和IBM(db2数据库)都起用了Java语言创建存储过程,还可以在不同数据库之间移动存储过程。
存储过程可以支持三种类型的参数:IN,OUT和INOUT,这对于存储过程在数据库内部真正能做什么来说,带来了很大的灵活性。不管存储过程是用什么语言编写的,它都能以一种标准的方式从Java应用程序调用。
首先,您需要创建一个CallableStatement对象。为了标识存储过程和过程需要的参数的类型和数量,还要允许使用三种类型的调用。下面的清单说明了这三种类型(假定我们正在调用一个名为StudentList的存储过程):
n {call StudentList}如果过程不需要参数
n {call StudentList(?,?)}如果过程需要两个参数
n {?=call StudentList(?,?)}如果参数需要两个参数并返回一个
要想使用存储过程,首先应该创建一个存储过程!
代码的实现
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.NamingException;
public class CreateStoredProceduresofSQLServer {
public static void main(String[] args) {
Connection con = null;
Statement stmt = null;
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
stmt = con.createStatement();
// 1.创建存储过程show_students
String createProcedure1 = "create procedure show_students " + "as " + "select id, name,age " + "from students " + "order by id";
// 删除数据库中存在的同名过程
stmt.executeUpdate("if exists(select name from sysobjects "
+ "where name='show_students'and type='p') "
+ "drop procedure show_students");
stmt.executeUpdate(createProcedure1);
// 2.创建储存过程onestudent
String createProcedure2 = "create procedure onestudent "
+ "@stu_id int = null, " + "@name varchar(20) output, "
+ "@age int output " + "as " + "if @stu_id = null "
+ "BEGIN "
+ " PRINT 'ERROR: You must specify a stu_id value.' "
+ " RETURN "
+ "END "
+
// Get the sales for the specified cof_name and " +
// assign it to the output parameter. " +
"SELECT @name = name, @age = age " + "FROM coffees "
+ "WHERE id = @stu_id " + "RETURN ";
stmt.executeUpdate("if exists(select name from sysobjects "
+ "where name='onestudent'and type='p') "
+ "drop procedure onestudent");
stmt.executeUpdate(createProcedure2);
// 3.创建函数
String createProcedure3 = "CREATE FUNCTION pubuse.ageofstu "
+
// Input cof_name
"(@stu_name varchar(20)) "
+ "RETURNS int "
+ // return sales
"AS " + "BEGIN " + " DECLARE @age int "
+ " SELECT @age = age " + " FROM student "
+ " WHERE name like @stu_name " + " RETURN @age "
+ "END ";
stmt.executeUpdate("if exists(select name from sysobjects "
+ "where name='ageofstu') "
+ "drop function pubuse.ageofstu");
stmt.executeUpdate(createProcedure3);
stmt.close();
con.close();
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException : " + ex.getMessage());
}
System.out.println("程序执行结束!");
}
}
下面是使用存储过程的代码:
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import javax.naming.NamingException;
public class InvokeStoreProcdureofSQLServer {
public static void main(String[] args) {
Connection con = null;
String jndiname = "jdbcPool/mydatasource";
// 定义调用存储过程和函数的 SQL 语句
String callSQL1 = "{call show_students}";
String callSQL2 = "{call onestudent(?,?,?)}";
String callSQL3 = "{? = call ageofstu(?)}";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
// 调用第 1 个存储过程
CallableStatement cs = con.prepareCall(callSQL1);
ResultSet rs = cs.executeQuery();
System.out.println("第一个存储过程调用结果");
while (rs.next()) {
String id = rs.getString(1);
String name = rs.getString(2);
String age = rs.getString(3);
System.out.println(id + " " + name + " " + age);
}
// 调用第 2 个存储过程
cs = con.prepareCall(callSQL2);
cs.setString(1, "2");
cs.registerOutParameter(2, Types.CHAR);
cs.registerOutParameter(3, Types.INTEGER);
cs.execute();
String name = cs.getString(2);
int age = cs.getInt(3);
System.out.println("第二个存储过程调用结果");
System.out.println("This student's name is " + name
+ " and age is " + age);
// 调用函数
cs = con.prepareCall(callSQL3);
cs.setString(2, "小罗");
cs.registerOutParameter(1, Types.INTEGER);
cs.execute();
age = cs.getInt(1);
System.out.println("函数调用结果");
System.out.println("This student's name is " + age + ".");
cs.close();
con.close();
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException : " + ex.getMessage());
}
System.out.println("调用结束!");
}
}
操作元数据
JDBC Meta Data(元数据)----描述数据的数据。它的接口有:
n DatabaseMetaData(数据库元数据)
n ResultSetMetaData(结果集元数据)
DatabaseMetaData(数据库元数据)
在对数据源进行连接以后,得到一个Connection对象,可以从这个对象获得有关数据源的各种信息,包括关于数据库中的各个表,表中的各个列,数据类型和存储过程等各方面的信息。根据这些信息,JDBC程序可以访问一个事先并不了解的数据库。获取这些信息的方法都是在DatabaseMetaData的对象上实现的,而DatabaseMetaData对象是在Connection对象之上获得的。
下面的语句可以在一个连接的基础上创建一个DatabaseMetaData 对象:
DatabaseMetaData dm=con.getMetaData();
数据库的一些常用信息可通过DatabaseMetaData对象的下列方法获得。
n getURL()//返回一个String对象,代表数据库的URL.
n getUserName()//返回此连接使用的数据库的用户名
n isReadOnly()//返回一个boolean值,指示数据库是否只允许读操作。
n getDatabaseProduceName()//返回数据库的产品名称
n getDatabaseProduceVersion()//返回数据库的版本号
n getDriverName()//返回驱动程序的名称。
n getDriverVersion()//返回驱动程序的版本号
代码示例:
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.NamingException;
public class DataBaseMetaDataTest {
public static void main(String[] args) {
Connection con = null;
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
// 测试数据库信息
DatabaseMetaData dm = con.getMetaData();
System.out.println("1. 数据库的基本信息");
System.out.println("Database is " + dm.getDatabaseProductName());
System.out.println("Database version is "
+ dm.getDatabaseProductVersion());
System.out.println("JDBC Driver is " + dm.getDriverName());
System.out.println("JDBC driver version is "
+ dm.getDriverVersion());
// 获取数据库中目录(数据库)的信息
System.out.println("2. 数据库中目录(数据库)的信息");
ResultSet catalogs = dm.getCatalogs();
while (catalogs.next()) {
System.out.println(catalogs.getString(1));
}
// 获取数据库中模式的信息
System.out.println("3. 数据库中模式的信息");
ResultSet schemas = dm.getSchemas();
while (schemas.next()) {
System.out.println(schemas.getString(1));
}
// 获取数据库中各个表的情况
System.out.println("4. 数据库中各个表的信息");
ResultSet tables = dm.getTables("pubs", null, null, null);
while (tables.next()) {
for (int i = 0; i < 5; i++) {
System.out.print(tables.getString(i + 1));
System.out.print(" | ");
}
System.out.println();
}
// 获取数据库表中各个列的信息
System.out.println("5. 数据库表中各个列的信息");
ResultSet columns = dm.getColumns(null, null,
"student", null);
while (columns.next()) {
for (int i = 0; i < 18; i++) {
System.out.print(columns.getString(i + 1));
System.out.print(" | ");
}
System.out.println();
}
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException : " + ex.getMessage());
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("程序结束!!!");
}
}
ResultSetMetaData(结果集元数据)
根据结果集的元数据,可以得到一个查询结果集关于查询表中列的个数,各个列名,类型以及各个列的宽度等。ResultSetMetaData的对象可以由ResultSet对象的getMetaData()方法得到。
ResultSetMetaData对象的常用方法如下:
n ResultSet rs=stmt.executeQuery();
n ResultSetMetaData rsmd=rs.getMetaData();
n rsmd.getColumnCount()//返回ResultSet对象的列数。
n rsmd.getColumnDisplaySize(int column);//返回column指定的列的最大宽度。
n rsmd.getColumnLabel(int column)//返回column指定列的标签。
n rsmd.getColumnName(int column)//返回column指定列的列名。
请看下例:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.NamingException;
public class ResultSetMetaDataTest {
public static void main(String[] args) {
Statement stmt = null;
ResultSet rs = null;
Connection con = null;
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
stmt = con.createStatement();
String querySQL1 = "select * from student";
rs = stmt.executeQuery(querySQL1);
// 提取结果集的元数据:
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
String[] columnNames = new String[colCount];
String[] columnLabels = new String[colCount];
int[] columnTypes = new int[colCount];
for (int i = 0; i < colCount; i++) {
columnNames[i] = rsmd.getColumnName(i + 1);
columnLabels[i] = rsmd.getColumnLabel(i + 1);
columnTypes[i] = rsmd.getColumnType(i + 1);
}
System.out.println();
System.out.println("提取的数据如下:");
System.out.println();
System.out .println("----------------------");
for (int i = 0; i < colCount; i++) {
System.out.print(columnLabels[i] + "/t");
}
System.out.println();
for (int i = 0; i < colCount; i++) {
System.out.print(columnNames[i] + "/t");
}
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.err.println("SQLException: " + ex.getMessage());
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
}
}
System.out.println("程序结束!!!");
}
}
可滚动的和可更新的结果集
可滚动的结果集表示在结果集中前后滚动显示数据。可更新的结果集表示可以通过结果集来更新数据库里的数据。
可滚动的ResultSet可以使用以下方法:
n absolute()
n afterLast()
n beforeFirst()
n first()
n getRow()
n isAfterLast()
n isBeforeFirst()
n isFirst()
n isLast()
n last()
n moveToCurrentRow()//仅对可更新的ResultSet有效
n previous()
n relative(int rows)//将游标移动相对量的行,或者正方向或者负方向
为了让查询返回可滚动的结果集,你必须用
Statement stmt=con.createStatement(type,concurrency);
得到一个不同的Statement对象。
对于PreparedStatement,调用
PreparedStatement stmt=con.preparedStatement(command,type,concurrency);
例如,如果你只想在一个结果总来回滚动,而不想编辑它的数据,用下列语句:
Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
所有调用方法:
ResultSet rs=stmt.executeQuery(query);
返回的结果集都是可滚动的,一个可滚动的结果集用一个光标指示目前的位置。
ResultSet类型值
n TYPE_FORWARD_ONLY 结果集不可滚动
n TYPE_SCROLL_INSENSITIVE 结果集可滚动,但不反映数据库的变化
n TYPE_SCROLL_SENSITIVE 结果集可滚动,并反映数据库的变化
ResultSet同步值
n CONCUR_READ_ONLY 不能用结果集更新数据库
n CONCUR_UPDATABLE 可以用结果集更新数据库
如果你想编辑ResultSet类型数据,并让这些变化在数据库中自动体现出来,就需要创建一个可更新的结果集。可更新的结果集不一定是可滚动的,但如果想让用户编辑数据,通常这个结果集也是可滚动的。为了得到可更新的结果集,构造语句如下:
Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
接着,调用executeQuery()就可返回可更新的结果集。
注意:不是所有的查询都可返回可更新的结果集。如果某个查询涉及多个表,那么它的返回结果可能是不可更新的。但如果查询只涉及一个表或涉及用主关键字连接的多个表,那么返回结果是可更新的,这可以用ResultSet的getConcurrency()方法验证。
和SQL类型相对应,JDBC 2.0为每个数据类型提供了与它相应的updateXxx()方法,如updateDouble(),updateString()等等。
updateXxx()方法只能改变当前光标的记录值,而不能改变数据库中数据。调用updateRow()方法把修改后的数据保存到数据库中。如果移动到另一行之前,没有调用updateRow()方法,本行中所有的更新将被撤消。也可以调用cancelRowUpdates()方法,撤消本行中的所有修改。
例如:
String query="select * from student";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()){
double age=rs.getInt("age");
rs.updateInt("age",age+1);
rs.updateRow();
往数据库中添加一个新记录:
n 首先用moveToInsertRow()方法把光标移动到指定位置(它称为插入行)。
n 接着,调用 updateXxx()方法,在插入位置创建一个新行。
n 最后,调用insertRow()方法,把这个新行发送给数据库。插入完成后,调用
moveToCourrentRow()方法把光标移回调用moveToInsertRow()方法以前的位置。
例:
rs.moveToInsertRow();
rs.updateString("name","李雨春");
rs.updateInt("age",21);
rs.insertRow();
rs.moveToCurrentRow();
调用下面的方法,可以删除位于光标下面的一行:
rs.deleteRow();
deleteRow() 方法会立即把这行从结果集和数据库中删除。
代码的实现
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.NamingException;
public class ResultSetDemo {
public static void main(String[] args) {
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
String querySQL = "select name,age from student";
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs = stmt.executeQuery(querySQL);
// 显示修改之前的数据
System.out.println("修改之前的数据:");
while (rs.next()) {
for (int i = 1; i <= 2; i++) {
System.out.print(rs.getObject(i));
System.out.print(" ");
}
System.out.println();
}
// 过年了,把记录里年龄都加1,虽然写一个SQL语句就可以实现
// update student set age=age+1
// 但是为了测试rs的用法我们还使用JDBC。
rs.beforeFirst();
while (rs.next()) {
int age = rs.getInt("age");
rs.updateInt("age", age + 1);
rs.updateRow();
}
// 显示修改后的数据
rs = stmt.executeQuery(querySQL);
System.out.println("修改之后的数据:");
while (rs.next()) {
for (int i = 1; i <= 2; i++) {
System.out.print(rs.getObject(i));
System.out.print(" ");
}
System.out.println();
}
// 增加一条数据
rs.moveToInsertRow();
rs.updateString("name", "李雨春");
rs.updateInt("age", 21);
rs.insertRow();
rs.moveToCurrentRow();
// 显示增加后的数据
System.out.println("增加之后的数据:");
rs = stmt.executeQuery("select name,age from student");
while (rs.next()) {
for (int i = 1; i <= 2; i++) {
System.out.print(rs.getObject(i));
System.out.print(" ");
}
System.out.println();
}
// 删除最后一行数据
if (rs.last())
rs.deleteRow();
System.out.println("删除之后的数据:");
rs = stmt.executeQuery(querySQL);
while (rs.next()) {
for (int i = 1; i <= 2; i++) {
System.out.print(rs.getObject(i));
System.out.print(" ");
}
System.out.println();
}
stmt.close();
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} catch (SQLException ex) {
System.out.println("/n--- SQLException caught ---/n");
while (ex != null) {
System.out.println("Message: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("ErrorCode: " + ex.getErrorCode());
ex = ex.getNextException();
System.out.println();
}
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
}
}
System.out.println();
System.out.println("End.");
}
}
打印结果如图
批处理更新
批处理更新功能可以一次向数据库提交多个更新操作,要求数据库进行处理。一起提交多个更新(而非一个一个单独地提交更新)在某些情况下将大大提高性能。
可以利用Statement,PreparedStatement,CallableStatement对象来提交批处理更新。
代码的实现
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.NamingException;
public class BatchUpdateTest {
public static void main(String[] args) {
Connection con = null;
Statement stmt = null;
String jndiname = "jdbcPool/mydatasource";
try {
con = DBCon.getConnectionFromPooledDataSource(jndiname);
stmt = con.createStatement();
con.setAutoCommit(false);
stmt.addBatch("insert into student values('拉登',56)");
stmt.addBatch("insert into student values('张学友',49)");
stmt.addBatch("insert into student values('刘德华',51)");
stmt.addBatch("insert into student values('猪八戒',128)");
int[] updateCounts = stmt.executeBatch();
con.commit();
for (int i = 0; i < updateCounts.length; i++) {
System.out.println("Line" + (i + 1) + " update counts is : "
+ updateCounts[i]);
}
con.setAutoCommit(true);
ResultSet uprs = stmt.executeQuery("select * from student");
System.out.println("table student after insertion:");
while (uprs.next()) {
String name = uprs.getString("name");
int age = uprs.getInt("age");
System.out.print(name + " " + age);
System.out.println();
}
uprs.close();
stmt.close();
} catch (BatchUpdateException b) {
System.err.println("SQLException: " + b.getMessage());
System.err.println("SQLState: " + b.getSQLState());
System.err.println("Message: " + b.getMessage());
System.err.println("Vendor: " + b.getErrorCode());
System.err.print("Update counts: ");
int[] updateCounts = b.getUpdateCounts();
for (int i = 0; i < updateCounts.length; i++) {
System.err.print(updateCounts[i] + " ");
}
} catch (SQLException ex) {
System.err.println("SQLException: " + ex.getMessage());
System.err.println("SQLState: " + ex.getSQLState());
System.err.println("Message: " + ex.getMessage());
System.err.println("Vendor: " + ex.getErrorCode());
} catch (NamingException ex) {
System.err.println("Name Not Bound : " + ex.getMessage());
} finally {
try {
if (con != null)
con.close();
} catch (Exception e) {
}
}
System.out.println("程序执行结束!");
}
}
打印结果如图
字符大对象CLOB
在mssqlserver 2000里面并没有字符大对象这个数据类型,但是可以通过nchar类型来代替字符大对象数据,但是只能存放3k字符以下的。在ORACLE数据库里面存在字符大对象CLOB数据类型。该数据类型用来存放文本类型的数据。
SQL代码:
ORACLE的实现,在运行该例子之前,首先把ORACLE的驱动文件classes12.jar加到classpath里面。然后打开ORACLE数据库,建立一张表。SQL语句如下:
CREATE TABLE MESSAGES(ID VARCHAR2(20),TITLE VARCHAR2(30),AUTHOR VARCHAR(20),MESSAGE CLOB);)
JAVA代码:
package com.app;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Connection;
import oracle.sql.CLOB;
public class OracleClob {
public static void main(String[] args) {
String jndiname = "jdbcPool/mydatasource";
Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);
String insertSQL = "Insert INTO messages VALUES('1001','my message', 'xsw', empty_clob())";
try {
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.execute(insertSQL);
String cmd = "SELECT message FROM messages WHERE id='1001'";
ResultSet rset = stmt.executeQuery(cmd);
rset.next();
CLOB clob = (CLOB) rset.getClob(1);
File file = new File("myfile.txt");
FileInputStream fis = new FileInputStream(file);
OutputStream outstream = clob.getAsciiOutputStream();
int size = clob.getBufferSize();
byte[] buffer = new byte[size];
int length = -1;
while ((length = fis.read(buffer)) != -1)
outstream.write(buffer, 0, length);
fis.close();
outstream.close();
con.commit();
con.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面看下MSSQLSERVER数据库的实现(首先在查询分析器里面运行下列SQL语句:
CREATE TABLE messages (
id char(30),
title char(40) ,
author char(20) ,
message ntext
)
然后运行下面的代码:
package com.app;
import java.io.File;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class MSSQLClob {
public static void main(String[] args) {
String jndiname = "jdbcPool/mydatasource";
Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);
String insertSQL = "Insert INTO messages VALUES(?,?,?,?)";
PreparedStatement stmt = con.prepareStatement(insertSQL);
File file = new File("myfile.txt");
FileInputStream fis = new FileInputStream(file);
stmt.setString(1, "1001");
stmt.setString(2, "my message");
stmt.setString(3, "xsw");
stmt.setAsciiStream(4, fis, (int) file.length());
stmt.executeUpdate();
fis.close();
}
}
二进制大对象BLOB
该数据类型是用来存放图片文件的。在MSSQLSERVER 2000没有该数据类型,那么可以用binary数据类型代替,但是该数据只能存放小7k的图片。在ORACLE数据库里,有BLOB数据类型,来直接存放图片文件,直接操作该数据类型。对不支持BLOB数据类型的MSSQLSERVER 2000数据库,我们可以用二进制流进行读取图片文件。
ORACLE的实现:
SQL语句:
CREATE TABLE AUTHORS(AUTHOR VARCHAR2(40),PHOTO BLOB);
JAVA代码:
package com.app;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import oracle.sql.BLOB;
public class OracleBlob {
public static void main(String[] args) {
String jndiname = "jdbcPool/mydatasource";
Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);
String insertSQL = "Insert INTO authors VALUES('1001', empty_blob())";
try {
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.execute(insertSQL);
String cmd = "SELECT photo FROM authors WHERE author='1001'";
ResultSet rset = stmt.executeQuery(cmd);
rset.next();
BLOB blob = (BLOB) rset.getBlob(1);
File file = new File("mypic.jpg");
FileInputStream fis = new FileInputStream(file);
OutputStream outstream = blob.getAsciiOutputStream();
int size = blob.getBufferSize();
byte[] buffer = new byte[size];
int length = -1;
while ((length = fis.read(buffer)) != -1)
outstream.write(buffer, 0, length);
fis.close();
outstream.close();
con.commit();
con.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MSSQLSERVER 2000的实现
SQL语句:
CREATE TABLE authors(
author char(50) ,
photo image NOT NULL
)
代码:
package com.app;
import java.io.File;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class MSSQLBlob {
public static void main(String[] args) {
String jndiname = "jdbcPool/mydatasource";
Connection con = DBCon.getConnectionFromPooledDataSource(jndiname);
String insertSQL = "Insert INTO authors VALUES(?,?)";
PreparedStatement stmt = con.prepareStatement(insertSQL);
File file = new File("mypic.jpg");
FileInputStream fis = new FileInputStream(file);
stmt.setString(1, "1001");
stmt.setBinaryStream(2, fis, (int) file.length());
stmt.executeUpdate();
fis.close();
}
}
RowSet 新特性
Java 5在Java Database Connectivity (JDBC)方面加强了支持,其中加入了新的包javax.sql.rowset,javax.sql.rowset.serial,javax.sql.rowset.spi。从RowSet接口继承规定了五个新的接口:
n CachedRowSet:可以不用与数据源建立长期的连接,只有当从数据库读取数据或是往数据库写入数据的时候才会与数据库建立连接,它提供了一种轻量级的访问数据库的方式,其数据均存在内存中。
n JdbcRowSet:对ResultSet的对象进行包装,使得可以将ResultSet对象做为一个JavaBeans 组件。
n FilteredRowSet:继承自CachedRowSet,可以根据设置条件得到数据的子集。
n JoinRowSet:继承自CachedRowSet,可以将多个RowSet对象进行SQL Join语句的合并。
n WebRowSet:继承自CachedRowSet,可以将WebRowSet对象输出成XML格式。
CachedRowSet
CachedRowSet可以通过调用populate(ResuletSet rs)来生成数据,一旦获得数据,CachedRowSet就可以断开与数据库的连接,直到往数据库写入数据的时候才需建立连接。
下面的代码演示了如何根据ResultSet建立一个CachedRowSet对象:
package com.itjob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.rowset.CachedRowSet;
import com.sun.rowset.CachedRowSetImpl;
public class CachedRSTest {
private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
private final static String URL = "jdbc:odbc:myodbc";
private final static String USER = "student";
private final static String PASSWORD = "student";
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException, SQLException {
Class.forName(DRIVER);
Connection con = DriverManager.getConnection(URL, USER, PASSWORD);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM student");
CachedRowSet crset = new CachedRowSetImpl();
crset.populate(rs);
rs.close();
stmt.close();
con.close();
System.out.println("name");
while (crset.next()) {
System.out.println(crset.getString("name"));
}
}
}
JdbcRowSet
JdbcRowSet功能与ResultSet类似,与CachedRowSet不同,JdbcRowSet在操作时保持与数据库的连接。可以将与数据库连接的URL,用户名,密码以及执行的SQL语句通过setXXX形式绑定。另外,JdbcRowSet返回的结果默认是可以上下滚动和可更新的,当然这需要数据库厂商提供的JDBC Driver支持。下面的代码演示了如何通过set方法设定数据库连接参数,以及如何操作JdbcRowSet对象。
package com.itjob;
import java.sql.SQLException;
import javax.sql.rowset.JdbcRowSet;
import com.sun.rowset.JdbcRowSetImpl;
public class JdbcRSDemo {
private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
private final static String URL = "jdbc:odbc:myodbc";
private final static String USER = "student";
private final static String PASSWORD = "student";
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName(DRIVER);
JdbcRowSet jrs = new JdbcRowSetImpl();
// 设置连接数据库的URL
jrs.setUrl(URL);
// 设置连接数据库的用户名
jrs.setUsername(USER);
// 设置连接数据库的密码
jrs.setPassword(PASSWORD);
// 设置执行数据库的SQL语句
jrs.setCommand("select * from student");
jrs.execute();
System.out.println("name");
while (jrs.next()) {
System.out.println(jrs.getString("name"));
}
jrs.close();
}
}
FilteredRowSet
FilteredRowSet接口中规定了可以设定过滤器,其过滤接口为Predicate接口,必须实现Predicate接口中的evaluate方法。具体的代码如下:
package com.itjob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.FilteredRowSet;
import com.sun.rowset.CachedRowSetImpl;
import com.sun.rowset.FilteredRowSetImpl;
public class FilteredRSDemo {
private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
private final static String URL = "jdbc:odbc:myodbc";
private final static String USER = "student";
private final static String PASSWORD = "student";
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException, SQLException {
Class.forName(DRIVER).newInstance();
Connection con = DriverManager.getConnection(URL, USER, PASSWORD);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM student");
FilteredRowSet frs = new FilteredRowSetImpl();
frs.populate(rs);
con.close();
MyDBFilter filter = new MyDBFilter(1, 4);
frs.setFilter(filter);
System.out.println("name");
while (frs.next()) {
System.out.println(frs.getString("name"));
}
frs.close();
}
}
package com.itjob;
import java.sql.SQLException;
import javax.sql.RowSet;
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.Predicate;
class MyDBFilter implements Predicate {
private int low;
private int high;
public MyDBFilter(int low, int high) {
this.low = low;
this.high = high;
}
public boolean evaluate(RowSet rs) {
CachedRowSet crs=(CachedRowSet)rs;
//如果id在low和high之间返回真
try {
int idValue = crs.getInt("id");
if (low < idValue && idValue < high) {
return true;
}
} catch (SQLException e) {
}
return false;
}
public boolean evaluate(Object arg0, int arg1) throws SQLException {
return false;
}
public boolean evaluate(Object arg0, String arg1) throws SQLException {
return false;
}
}
WebRowSet
WebRowSet继承自CachedRowSet,支持XML格式的查询,更新等操作,下面的代码将WebRowSet对象输出成XML格式到文件。
package com.itjob;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.rowset.WebRowSet;
import com.sun.rowset.WebRowSetImpl;
public class WebRSDemo {
private final static String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
private final static String URL = "jdbc:odbc:myodbc";
private final static String USER = "student";
private final static String PASSWORD = "student";
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException, SQLException, IOException {
Class.forName(DRIVER).newInstance();
Connection con = DriverManager.getConnection(URL, USER, PASSWORD);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM student");
// 关闭与数据库的连接
con.close();
WebRowSet wrs = new WebRowSetImpl();
wrs.populate(rs);
FileOutputStream out = new FileOutputStream("data.xml");
wrs.writeXml(out);
wrs.close();
}
}
内容总结
n 对数据库进行SQL语句进行基本操作(包括create, insert, update,delete)
n 使用预编译语句可以提高数据库访问效率及加强安全性
n 可以在应用程序级别使用事务
n 通过事务的级别控制,可以满足不同的业务方面的需求
n 使用存储过程优化查询,提高查询效率
n 操作元数据
n 可滚动的和可更新的结果集
n 批处理更新
n 字符大对象CLOB
n 二进制大对象BLOB