Java的数据库连接编程(JDBC)技术
[本讲的知识要点]:JDBC、JDBC的工作原理,访问数据库的方法、Statement、PreparedStatement、CallableStatement,ResultSet等对象的编程使用9.1 基本知识
9.1.1 JDBC:Java DataBase Connectivity(Java 数据库连接技术),它是将Java与SQL结合且独立于特定的数据库系统的应用程序编程接口(API--它是一种可用于执行SQL语句的Java API,即由一组用Java语言编写的类与接口所组成)。
有了JDBC从而可以使Java程序员用Java语言来编写完整的数据库方面的应用程序。另外也可以操作保存在多种不同的数据库管理系统中的数据,而与数据库管理系统中数据存储格式无关。同时Java语言的与平台的无关性,不必在不同的系统平台下编写不同的数据库应用程序。
9.1.2 JDBC设计的目的
(1)ODBC:微软的ODBC是用C编写的,而且只适用于Windows平台,无法实现跨平台地操作数据库。
(2)SQL语言:SQL尽管包含有数据定义、数据操作、数据管理等功能,但它并不是一个完整的编程语言,而且不支持流控制,需要与其它编程语言相配合使用。
(3)JDBC的设计:由于Java语言具有健壮性、安全、易使用并自动下载到网络等方面的优点,因此如果采用Java语言来连接数据库,将能克服ODBC局限于某一系统平台的缺陷;将SQL语言与Java语言相互结合起来,可以实现连接不同数据库系统,即使用JDBC可以很容易地把SQL语句传送到任何关系型数据库中。
(4)JDBC设计的目的:它是一种规范,设计出它的最主要的目的是让各个数据库开发商为Java程序员提供标准的数据库访问类和接口,使得独立于DBMS的Java应用程序的开发成为可能(数据库改变,驱动程序跟着改变,但应用程序不变)。
9.1.3 JDBC的主要功能:(1)创建与数据库的连接;(2)发送SQL语句到任何关系型数据库中;(3)处理数据并查询结果。
编程实例:
try
{ Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //(1)创建与数据库的连接
Connection con=DriverManager.getConnection("jdbc:odbc:DatabaseDSN","Login","Password");
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from DBTableName");//(2)发送SQL语句到数据库中
while(rs.next())
{ String name=rs.getString("Name") ; //(3)处理数据并查询结果。
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
}
rs.close(); //(4)关闭
stmt.close();
con.close();
}
catch(SQLException e)
{ System.out.println("SQLState:"+ e.getSQLState());
System.out.println("Message:" + e.getMessage());
System.out.println("Vendor:" + e.getErrorCode());
}
9.1.4 JDBC与ODBC的对比,从而体会JDBC的特点
(1)ODBC是用C语言编写的,不是面向对象的;而JDBC是用Java编写的,是面向对象的。
(2)ODBC难以学习,因为它把简单的功能与高级功能组合在一起,即便是简单的查询也会带有复杂的任选项;而JDBC的设计使得简单的事情用简单的做法来完成。
(3)ODBC是局限于某一系统平台的,而JDBC提供Java与平台无关的解决方案。
(4)但也可以通过Java来操作ODBC,这可以采用JDBc-ODBC桥接方式来实现(因为Java不能直接使用ODBC,即在Java中使用本地C的代码将带来安全缺陷)。
9.1.5 JDBC驱动程序的类型: 目前比较常见的JDBC驱动程序可分为以下四个种类:
(1)JDBC-ODBC桥加ODBC驱动程序
JavaSoft桥产品利用ODBC驱动程序提供JDBC访问。注意,必须将ODBC二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装不是主要问题),或者是用Java编写的三层结构的应用程序服务器代码。
JDBC-ODBC 桥接方式利用微软的开放数据库互连接口(ODBC API)同数据库服务器通讯,客户端计算机首先应该安装并配置ODBC driver 和JDBC-ODBC bridge两种驱动程序。
(2)本地API
这种类型的驱动程序把客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其它DBMS的调用。注意,象桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户机上。
这种驱动方式将数据库厂商的特殊协议转换成Java代码及二进制类码,使Java 数据库客户方与数据库服务器方通信。例如:Oracle用SQLNet协议,DB2用IBM 的数据库协议。数据库厂商的特殊协议也应该被安装在客户机上。
(3)JDBC网络纯Java驱动程序
这种驱动程序将JDBC转换为与DBMS无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS协议。这种网络服务器中间件能够将它的纯Java客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet用的产品。为了使这些产品也支持Internet访问,它们必须处理Web所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC驱动程序加到他们现有的数据库中间件产品中。
这种方式是纯Java driver。数据库客户以标准网络协议(如HTTP、SHTTP)同数据库访问服务器通信,数据库访问服务器然后翻译标准网络协议成为数据库厂商的专有特殊数据库访问协议(也可能用到ODBC driver)与数据库通信。对Internet 和Intranet 用户而言这是一个理想的解决方案。Java driver 被自动的,以透明的方式随Applets自Web服务器而下载并安装在用户的计算机上。
(4)本地协议纯Java驱动程序
这种类型的驱动程序将JDBC调用直接转换为DBMS所使用的网络协议。这将允许从客户机机器上直接调用DBMS服务器,是Intranet访问的一个很实用的解决方法。
这种方式也是纯Java driver。数据库厂商提供了特殊的JDBC协议使Java数据库客户与数据库服务器通信。然而,将把代理协议同数据库服务器通信改用数据库厂商的特殊JDBC driver。这对Intranet 应用是高效的,可是数据库厂商的协议可能不被防火墙支持,缺乏防火墙支持在Internet 应用中会存在潜在的安全隐患。
9.2 JDBC的工作原理
JDBC的设计基于X/Open SQL CLI(调用级接口)这一模型。它通过定义出一组 API对象和方法以用于同数据库进行交互。
在Java程序中要操作数据库,一般应该通过如下几步(利用JDBC访问数据库的编程步骤):
(1)加载连接数据库的驱动程序 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
(2)创建与数据源的连接
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url,"Login","Password");
(3)查询数据库:创建Statement对象并执行SQL语句以返回一个ResultSet对象。
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from DBTableName");
(4)获得当前记录集中的某一记录的各个字段的值
String name=rs.getString("Name");
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
(5)关闭查询语句及与数据库的连接(注意关闭的顺序先rs再stmt最后为con)
rs.close();
stmt.close();
con.close();
9.3 JDBC的结构
JDBC主要包含两部分:面向Java程序员的JDBC API及面向数据库厂商的JDBC Drive API。
(1)面向Java程序员的JDBC API:Java程序员通过调用此API从而实现连接数据库、执行SQL语句并返回结果集等编程数据库的能力,它主要是由一系列的接口定义所构成。
java.sql.DriveManager:该接口主要定义了用来处理装载驱动程序并且为创建新的数据库连接提供支持。
java.sql.Connection:该接口主要定义了实现对某一种指定数据库连接的功能。
java.sql.Statement:该接口主要定义了在一个给定的连接中作为SQL语句执行声明的容器以实现对数据库的操作。它主要包含有如下的两种子类型。
java.sql.PreparedStatement:该接口主要定义了用于执行带或不带 IN 参数的预编译 SQL 语句。
java.sql.CallableStatement:该接口主要定义了用于执行数据库的存储过程的雕用。
java.sql.ResultSet:该接口主要定义了用于执行对数据库的操作所返回的结果集。
(2)面向数据库厂商的JDBC Drive API:数据库厂商必须提供相应的驱动程序并实现JDBC API所要求的基本接口(每个数据库系统厂商必须提供对DriveManager、Connection、Statement、ResultSet等接口的具体实现),从而最终保证Java程序员通过JDBC实现对不同的数据库操作。
9.4 数据库应用的模型
(1)两层结构(C/S):在此模型下,客户端的程序直接与数据库服务器相连接并发送SQL语句(但这时就需要在客户端安装被访问的数据库的JDBC驱动程序),DBMS服务器向客户返回相应的结果,客户程序负责对数据的格式化。
client端 ODBC/JDBC Server端(DBMS)
或数据库专用协议
主要的缺点:受数据库厂商的限制,用户更换数据库时需要改写客户程序;受数据库版本的限制,数据库厂商一旦升级数据库,使用该数据库的客户程序需要重新编译和发布;对数据库的操作与处理都是在客户程序中实现,使客户程序在编程与设计时较为复杂。
(2)三(或多)层结构(B/S):在此模型下,主要在客户端的程序与数据库服务器之间增加了一个中间服务器(可以采用C++或Java语言来编程实现),隔离客户端的程序与数据库服务器。客户端的程序(可以简单为通用的浏览器)与中间服务器进行通信,然后由中间服务器处理客户端程序的请求并管理与数据库服务器的连接。
客户端程序 HTTP RMI CORBA 中间服务器 JDBC 数据库服务器
9.5 通过JDBC 实现对数据库的访问
(1)引用必要的包
import java.sql.*; //它包含有操作数据库的各个类与接口
(2)加载连接数据库的驱动程序类
为实现与特定的数据库相连接,JDBC必须加载相应的驱动程序类。这通常可以采用Class.forName()方法显式地加载一个驱动程序类,由驱动程序负责向DriverManager登记注册并在与数据库相连接时,DriverManager将使用此驱动程序。
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
注意:这条语句直接加载了sun公司提供的JDBC-ODBC Bridge驱动程序类。
(3)创建与数据源的连接
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url,"Login","Password");
注意:采用DriverManager类中的getConnection()方法实现与url所指定的数据源建立连接并返回一个Connection类的对象,以后对这个数据源的操作都是基于该Connection类对象;但对于Access等小型数据库,可以不用给出用户名与密码。
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url);
System.out.println(con.getCatalog()); //取得数据库的完整路径及文件名
JDBC借用了url语法来确定全球的数据库(数据库URL类似于通用的URL),对由url所指定的数据源的表示格式为
jdbc::[ database locator]
jdbc---指出要使用JDBC
subprotocal---定义驱动程序类型
database locator---提供网络数据库的位置和端口号(包括主机名、端口和数据库系统名等) jdbc:odbc://host.domain.com:port/databasefile
主协议jdbc 驱动程序类型为odbc,它指明JDBC管理器如何访问数据库,该例指名为采用JDBC-ODBC桥接方式;其它为数据库的位置表示。
例如:装载mySQL JDBC驱动程序
Class.forName("org.gjt.mm.mysql.Driver ");
String url
="jdbc:mysql://localhost/softforum?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1"
//testDB为你的数据库名
Connection conn= DriverManager.getConnection(url);
例如:装载Oracle JDBC OCI驱动程序(用thin模式)
Class.forName("oracle.jdbc.driver.OracleDriver ");
String url="jdbc:oracle:thin:@localhost:1521:orcl";
//orcl为你的数据库的SID
String user="scott";
String password="tiger";
Connection conn= DriverManager.getConnection(url,user,password);
注意:也可以通过con.setCatalog("MyDatabase")来加载数据库。
例如:装载DB2驱动程序
Class.forName("com.ibm.db2.jdbc.app.DB2Driver ")
String url="jdbc:db2://localhost:5000/sample";
//sample为你的数据库名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
例如:装载MicroSoft SQLServer驱动程序
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver ");
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=pubs";
//pubs为你的数据库的
String user="sa";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
(4)查询数据库的一些结构信息
这主要是获得数据库中的各个表,各个列及数据类型和存储过程等各方面的信息。根据这些信息,从而可以访问一个未知结构的数据库。这主要是通过DatabaseMetaData类的对象来实现并调用其中的方法来获得数据库的详细信息(即数据库的基本信息,数据库中的各个表的情况,表中的各个列的信息及索引方面的信息)。
DatabaseMetaData dbms=con.getMetaData();
System.out.println("数据库的驱动程序为 "+dbms.getDriverName());
(5)查询数据库中的数据:
在JDBC中查询数据库中的数据的执行方法可以分为三种类型,分别对应Statement (用于执行不带参数的简单SQL语句字符串),PreparedStatement(预编译SQL语句)和CallableStatement(主要用于执行存储过程)三个接口。
9.5.1、实现对数据库的一般查询Statement
1、创建Statement对象(要想执行一个SQL查询语句,必须首先创建出Statement对象,它封装代表要执行的SQL语句)并执行SQL语句以返回一个ResultSet对象,这可以通过Connection类中的createStatement()方法来实现。
Statement stmt=con.createStatement();
2、执行一个SQL查询语句,以查询数据库中的数据。Statement接口提供了三种执行SQL语句的方法:executeQuery()、executeUpdate() 和execute()。具体使用哪一个方法由SQL语句本身来决定。
l 方法 executeQuery 用于产生单个结果集的语句,例如 SELECT 语句等。
l 方法 executeUpdate 用于执行INSERT、UPDATE或DELETE 语句以及SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于 CREATE TABLE 或DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
l 方法 execute 用于执行返回多个结果集、多个更新计数或二者组合的语句。一般不会需要该高级功能。
下面给出通过Statement类中的executeQuery()方法来实现的代码段。executeQuery()方法的输入参数是一个标准的SQL查询语句,其返回值是一个ResultSet类的对象。
ResultSet rs=stmt. executeQuery ("select * from DBTableName");
要点:①JDBC在编译时并不对将要执行的SQL查询语句作任何检查,只是将其作为一个String类对象,直到驱动程序执行SQL查询语句时才知道其是否正确。对于错误的SQL查询语句,在执行时将会产生 SQLException。
②一个Statement对象在同一时间只能打开一个结果集,对第二个结果集的打开隐含着对第一个结果集的关闭。
③如果想对多个结果集同时操作,必须创建出多个Statement对象,在每个Statement对象上执行SQL查询语句以获得相应的结果集。
④如果不需要同时处理多个结果集,则可以在一个Statement对象上顺序执行多个SQL查询语句,对获得的结果集进行顺序操作。
import java.sql.*;
public class ResultSetTest
{ public static void main(String args[])
{ try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection("jdbc:odbc:studlist");
Statement stmt=con.createStatement();
ResultSet rs1=stmt.executeQuery("select name from student");
ResultSet rs2=stmt.executeQuery("select age from student");
//此时rs1已经被关闭
while(rs2.next())
{
System.out.println(rs2.getObject(1));
}
rs2.close();
stmt.close();
con.close();
}
catch(Exception e)
{
System.out.println(e);
}
}
}
注意:
此时显示出的将是姓名还是年龄?(将显示的是rs2的结果集的内容,即学生的年龄,因为采用JDBC-ODBC方式的驱动程序时,并且是采用同一个Statement对象,它只会保留最新的结果集,rs1中的内容将会被新的结果集所取代)。
3、 关闭Statement对象:每一个Statement对象在使用完毕后,都应该关闭。
stmt.close();
9.5.2、预编译方式执行SQL语句PreparedStatement
由于Statement对象在每次执行SQL语句时都将该语句传给数据库,如果需要多次执行同一条SQL语句时,这样将导致执行效率特别低,此时可以采用PreparedStatement对象来封装SQL语句。如果数据库支持预编译,它可以将SQL语句传给数据库作预编译,以后每次执行该SQL语句时,可以提高访问速度;但如果数据库不支持预编译,将在语句执行时才传给数据库,其效果类同于Statement对象。
另外PreparedStatement对象的SQL语句还可以接收参数,可以用不同的输入参数来多次执行编译过的语句,较Statement灵活方便(详见后文介绍)。
1、 创建PreparedStatement对象:从一个Connection对象上可以创建一个PreparedStatement对象,在创建时可以给出预编译的SQL语句。
PreparedStatement pstmt=con.prepareStatement("select * from DBTableName");
2、 执行SQL语句:可以调用executeQuery()来实现,但与Statement方式不同的是,它没有参数,因为在创建PreparedStatement对象时已经给出了要执行的SQL语句,系统并进行了预编译。
ResultSet rs=pstmt.executeQuery(); // 该条语句可以被多次执行
3、关闭PreparedStatement
pstmt.close(); //其实是调用了父类Statement类中的close()方法
9.5.3、执行存储过程CallableStatement
CallableStatement类是PreparedStatement类的子类,因此可以使用在PreparedStatement类及Statement类中的方法,主要用于执行存储过程。
1、 创建CallableStatement对象:使用Connection类中的prepareCall方法可以创建一个CallableStatement对象,其参数是一个String对象,一般格式为:
l 不带输入参数的存储过程“{call 存储过程名()}”。
l 带输入参数的存储过程“{call存储过程名(?, ?)}”
l 带输入参数并有返回结果参数的存储过程“{? = call 存储过程名(?, ?, ...)}”
CallableStatement cstmt=con.prepareCall("{call Query1()}");
2、 执行存储过程:可以调用executeQuery()方法来实现。
ResultSet rs=cstmt.executeQuery();
3、关闭CallableStatement
cstmt.close(); //其实是调用了父类Statement类中的close()方法
(6)检索记录集以获得当前记录集中的某一记录的各个字段的值
9.5.4、ResultSet对象:
① 执行完毕SQL语句后,将返回一个ResultSet类的对象,它包含所有的查询结果。但对ResultSet类的对象方式依赖于光标(Cursor)的类型,而对每一行中的各个列,可以按任何顺序进行处理(当然,如果按从左到右的顺序对各列进行处理可以获得较高的执行效率);
ResultSet类中的Course方式主要有:
ResultSet.TYPE_FORWARD_ONLY(为缺省设置):光标只能前进不能后退,也就是只能从第一个一直移动到最后一个。
ResultSet.TYPE_SCROLL_SENSITIVE:允许光标前进或后退并感应到其它ResultSet的光标的移动情形。
ResultSet.TYPE_SCROLL_INSENSITIVE:允许光标前进或后退并不能感应到其它ResultSet的光标的移动情形。
ResultSet类中的数据是否允许修改主要有:
ResultSet.CONCUR_READ_ONLY(为缺省设置):表示数据只能只读,不能更改。
ResultSet.CONCUR_UPDATABLE:表示数据允许被修改。
可以在创建Statement或PreparedStatement对象时指定ResultSet的这两个特性。
Statement stmt=con.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
或
PreparedStatement pstmt=con.PrepareStatement("insert into bookTable values (?,?,?)",ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
② ResultSet类的对象维持一个指向当前行的指针,利用ResultSet类的next()方法可以移动到下一行(在JDBC中,Java程序一次只能看到一行数据),如果next()的返回值为false,则说明已到记录集的尾部。另外JDBC也没有类似ODBC 的书签功能的方法。
③ 利用ResultSet类的getXXX()方法可以获得某一列的结果,其中XXX代表JDBC中的Java数据类型,如 getInt()、getString()、getDate()等。访问时需要指定要检索的列(可以采用 int值作为列号(从1开始计数)或指定列(字段)名方式,但字段名不区别字母的大小写)。
while(rs.next())
{ String name=rs.getString("Name"); //采用“列名”的方式访问数据
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
String homeAddress=rs.getString(4); //采用“列号”的方式访问数据
}
9.5.5、数据转换
利用ResultSet类的getXXX()方法可以实现将ResultSet中的SQL数据类型转换为它所返回的Java数据类型。
9.5.6、NULL结果值
要确定给定结果值是否是JDBC NULL,必须先读取该列,然后使用ResultSet.wasNull
方法检查该次读取是否返回JDBC NULL。
当使用ResultSet.getXXX方法读取JDBC NULL时,方法wasNull将返回下列值之一:
(1)Javanull值
对于返回Java对象的getXXX方法(例如getString、getBigDecimal、getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、getObject等)。
(2)零值:对于getByte、getShort、getInt、getLong、getFloat和getDouble。
(3)false值:对于getBoolean
9.5.6、获得结果集中的结构信息:利用ResultSet类的getMetaData()方法来获得结果集中的一些结构信息(主要提供用来描述列的数量、列的名称、列的数据类型。利用ResulSetMetaData类中的方法)。
ResultsetMetaData rsmd=rs.getMetaData();
rsmd.getColumnCount(); //返回结果集中的列数
rsmd.getColumnLabel(1); //返回第一列的列名(字段名)
例如:
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from TableName");
for(int i=1; i<=rs.getMetaData().getColumnCount(); i++) //跟踪显示各个列的名称
{ System.out.print(rs. getColumnName (i)+"\t");
}
while(rs.next())
{ //跟踪显示各个列的值
for(int j=1; j<=rs.getMetaData().getColumnCount(); j++)
{ System.out.print(rs.getObject(j)+"\t");
}
}
9.6、更新数据库
前面主要介绍如何实现对数据库的查询操作,但在许多应用中需要实现对数据库的更新,这主要涉及修改、插入和删除等(即SQL语句中的Insert、Update、Delete、Creat、Drap等)。仍然通过创建Statement对象来实现,但不再调用executeQuery()方法,而是使用executeUpdate()方法。
要点F:正确区分Statement类中的executeQuery()、execute()和executeUpdate()方法的用法:(1)
executeQuery() 执行一般的SQL查询语句(即SELECT语句)并返回Resultset对象;(2)execute()可以执行各种SQL查询语句,并可能返回多个结果集(这一般主要发生在执行了返回多个结果集的存储过程时),此时可以采用Resultset类的getResultSet()来获得当前的结果集;(3)executeUpdate()执行对数据库的更新的SQL语句或DDL语句。
9.6.1 对表中的记录进行操作
对一个表中的记录可以进行修改、插入和删除等操作,分别对应SQL的Update、 Insert、Delete操作;executeUpdate()方法的输入参数仍然为一个String对象(即所要执行的SQL语句),但输出参数不是ResultSet对象,而是一个整数(它代表操作所影响的记录行数)。
Statement stmt=con.createStatement();
stmt.executeUpdate("Update bookTable set Title='Java2' where Author='zhang'");
stmt.executeUpdate("Delete from bookTable where Author='zhang'");
stmt.executeUpdate("Insert into bookTable(BookID,Author,Title) values(1,'Li Ming','Java2')"); //未给出的列,其值为NULL
程序实例:对数据库中的表进行更新操作并显示操作前后的结果
import java.sql.*;
public class DBUpdateSetTest
{ public static void main(String args[])
{ try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection("jdbc:odbc:studlist");
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from student");
System.out.println("Result before executeUpdate");
while(rs.next())
{
System.out.println(rs.getString("name"));
System.out.println(rs.getString("age"));
}
stmt.executeUpdate("Update student set name='Yang' where id=0");
stmt.executeUpdate("Delete from student where id=2");
stmt.executeUpdate("Insert into student(id,name,age,sex) values(2,'zhang',30,true)");
rs=stmt.executeQuery("select * from student");
System.out.println("Result After executeUpdate");
while(rs.next())
{
System.out.println(rs.getString("name"));
System.out.println(rs.getString("age"));
}
rs.close();
stmt.close();
con.close();
}
catch(Exception e)
{
System.out.println(e);
}
}
}
9.6.2 创建和删除表
创建和删除一个表主要对应于SQL的Create Table和Drop Table语句。这可以通过Statement对象的executeUpdate()方法来完成。
① 创建表
Statement stmt=con.createStatement();
stmt.executeUpdate("create table TableName(ID integer, Name VARCHAR(20), Age integer)");
stmt.executeUpdate("Insert into TableName(ID, Name, Age) values(1,'Yang Ming',30)");
② 删除表
Statement stmt=con.createStatement();
stmt.executeUpdate("Drop Table TableName");
9.6.3 增加和删除表中的列
对一个表的列进行更新操作主要是使用SQL的ALTER Table语句。对列所进行的更新操作会影响到表中的所有的行。
① 增加表中的一列
Statement stmt=con.createStatement();
stmt.executeUpdate("Alter Table TableName add Column Address VarChar(50)");
stmt.executeUpdate("Update TableName set Address='Beijing,China' where ID=1");
② 删除表中的一列
Statement stmt=con.createStatement();
stmt.executeUpdate("Alter Table TableName Drop Column Address");
stmt.executeQuery("Select * from TableName");
9.6.4 利用PreparedStatement对象实现数据更新
同SQL查询语句一样,对数据更新语句时也可以在PreparedStatement对象上执行。使用PreparedStatement对象,只需传递一次SQL语句,可以多次执行它,并且可以利用数据库的预编译技术,提高执行效率。另外也可以接受参数。
PreparedStatement pstmt=con.prepareStatement("Update TableName set Address='Beijing,China' where ID >1");
pstmt.executeUpdate();
9.7 参数的输入与输出
要实现使用SQL语句的输入与输出参数,必须在PreparedStatement类的对象上进行操作;同时由于CallableStatement类是PrepareStatement类的子类,所以在CallableStatemen对象上的操作也可以使用输入与输出参数;其主要的编程原理是在生成CallableStatement或PreparedStatement类的对象时,可以在SQL语句中指定输入或输出参数,在执行这个SQL语句之前,要对输入参数进行赋值。
(1)使用PreparedStatement类的对象
通过prepareStatement类的对象可以实现在查询语句与数据更新语句方面都可以设置输入参数。
具体的方法是在SQL语句中用“?”标明参数,在执行SQL语句之前,使用setXXX方法给参数赋值,然后使用executeQuery()或executeUpdate()来执行这个SQL语句。每次执行SQL语句之前,可以给参数重新赋值。
setXXX方法用于给相应的输入参数进行赋值,其中XXX是JDBC的数据类型,如:Int、String等。setXXX方法有两个参数,第一个是要赋值的参数在SQL语句中的位置, SQL语句中的第一个参数的位置为1,第二个参数的位置为2;setXXX方法的第二个参数是要传递的值,如100、“Peking”等,随XXX的不同而为不同的类型。
PreparedStatement pstmt=con.prepareStatement("Update TableName set Name=? where ID=?");
pstmt.setString(1,"zhang Hua"); //设置第一个参数(Name)为 “zhang Hua”
for(int i=1;i<3;i++)
{ pstmt.setInt(2,i); //设置第二个参数(ID)为 1,2
pstmt.executeUpdate();
}
要点:最终实现 Update TableName set Name=zhang Hua where ID=1 与Update TableName set Name=zhang Hua where ID=2的效果。
(2)使用CallableStatement对象
如果要求调用数据库的存储过程,要使用CallableStatement对象。另外还有些存储过程要求用户输入参数,这可以在生成CallableStatement对象的存储过程调用语句中设置输入参数。在执行这个存储过程之前使用setXXX方法给参数赋值,然后再执行这个存储过程。
CallableStatement cstmt=con.prepareCall("{call Query(?)}"); //Query为存储过程名
cstmt.setString(1,"输入参数"); //为存储过程提供输入参数
ResultSet rs=cstmt.executeQuery();
(3)接收输出参数
某些存储过程可能会返回输出参数,这时在执行这个存储过程之前,必须使用CallableStatement的registerOutParameter方法首先登记输出参数,在registerOutParameter方法中要给出输出参数的相应位置以及输出参数的SQL数据类型。在执行完存储过程以后,必须使用getXXX方法来获得输出参数的值。并在getXXX方法中要指出获得哪一个输出参数(通过序号来指定)的值。
实例:存储过程getTestData有三个输入参数并返回一个输出参数,类型分别为VARCHAR。在执行完毕后,分别使用getString()方法来获得相应的值。
CallableStatement cstmt = con.prepareCall(“{? = call getTestData (?,?,?)}”);
cstmt.setString(1,Value); //设置输入参数
cstmt.setInt(2,Value);
cstmt.setFloat(3,Value);
cstmt.registerOutParameter(1,java.sql.Types.VARCHAR); //登记输出参数
ResultSet rs = cstmt.executeQuery(); //执行存储过程
rs.getString(1); //获得第一个字段的值
String returnResult=cstmt.getString(1); //获得返回的输出参数的值
要点:由于getXXX方法不对数据类型作任何转换,在registerOutParameter方法中指明数据库将返回的SQL数据类型,在执行完存储过程以后必须采用相应匹配的getXXX方法来获得输出参数的值。
9.8 批量处理JDBC语句提高处理速度
有时候JDBC运行得不够快,这可以使用数据库相关的存储过程。当然,作为存储过程的一个替代方案,可以试试使用Statement 的批量处理特性以提高速度。
存储过程的最简单的形式就是包含一系列SQL语句的过程,将这些语句放在一起便于在同一个地方管理也可以提高速度。Statement 类可以包含一系列SQL语句,因此允许在同一个数据库事务执行所有的那些语句而不是执行对数据库的一系列调用。
使用批量处理功能涉及下面的两个方法:
addBatch(String) 方法
executeBatch方法
如果你正在使用Statement 那么addBatch 方法可以接受一个通常的SQL语句,或者如果你在使用PreparedStatement ,那么也可以什么都不向它增加。
executeBatch 方法执行那些SQL语句并返回一个int值的数组,这个数组包含每个语句影响的数据的行数。
注意:如果将一个SELECT语句或者其他返回一个ResultSet的SQL语句放入批量处理中就会导致一个SQLException异常。
关于java.sql.Statement 的简单范例可以是:
con = DriverManager.getConnection(url,"myLogin", "myPassword");
con.setAutoCommit(false);
stmt = con.createStatement();
stmt.addBatch("INSERT INTO student " + "VALUES(4,'Yang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(5,'li',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(6,'zhang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(7,'wang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(8,'liu',20,True)");
int [] updateCounts = stmt.executeBatch();
con.commit();
con.setAutoCommit(true);
PreparedStatement 有些不同,它只能处理一部分SQL语法,但是可以有很多参数,因此重写上面的范例的一部分就可以得到下面的结果:
// 注意这里没有删除语句
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO student VALUES(?,?,?,?)"
);
User[ ] users = ...;
for(int i=0; i
stmt.setInt(1, users .getID());
stmt.setString(2, users.getName());
stmt.setInt(3, users.getAge());
stmt.setBoolean(4, users.getSex());
stmt.addBatch( );
}
int[ ] counts = stmt.executeBatch();
如果你不知道你的语句要运行多少次,那么这是一个很好的处理SQL代码的方法。在不使用批量处理的情况下,如果添加50个用户,那么性能就有影响,如果某个人写了一个脚本添加一万个用户,程序可能变得很糟糕。添加批处理功能就可以帮助提高性能,而且在后面的那种情况下代码的可读性也会更好。