第 9章 访问数据库
9.1 JDBC 简介
9.2 建立数据库连接
9.3 执行 SQL语句
9.4 使用 Prepared Statement
9.5 事务处理
9.6 编写数据库工具类
9.7 一个实例
9.1 JDBC 简介 -1
? 数据库的应用目前已经非常普遍, 在应用程序的开发
过程中, 经常会涉及到访问数据库 。 Java语言为访问
数据库提供了方便的技术 。
? Java使用 JDBC(Java Database Connectivity)技术进行数
据库的访问, 如图 9.1.1所示 。 使用 JDBC技术进行数据
库访问时, Java应用程序通过 JDBC API和 JDBC驱动程
序管理器之间进行通信, 例如 Java应用程序可以通过
JDBC API向 JDBC驱动程序管理器发送一个 SQL查询语
句 。 JDBC驱动程序管理器又可以以两种方式和最终的
数据库进行通信:一是使用 JDBC/ODBC桥接驱动程序
的间接方式;另一是使用 JDBC驱动程序的直接方式 。
9.1 JDBC 简介 -2
图 9.1.1 JDBC示意图
9.1 JDBC 简介 -3
JDBC所采用的这种数据库访问机制使得 JDBC驱动程序管理器以及
底层的数据库驱动程序对于开发人员来说是透明的。对于开发人
员来说,访问不同类型的数据库时使用的是同一套 JDBC API。此
外,使用这种机制还有另一个重要的意义:当有新类型的数据库
出现时,只要该数据库的生产厂商提供相应的 JDBC驱动程序,已
有的 Java应用程序不用做任何修改。
! ODBC (开放式数据库连接 )是一个编程接口,它允许程序访问
使用 SQL (结构化查询语言 ) 作为数据访问标准的 DBMS(数据库管
理系统 )中的数据。 SUN公司认为 ODBC难以掌握、使用复杂并且在
安全性方面存在问题,所以 Java中没有直接采用 ODBC 模式。
9.2 建立数据库连接 -1
? 9.2.1 使用 JDBC/ODBC桥接驱动程序
? 9.2.2 使用 JDBC驱动程序
? 9.2.3 使用配置文件
9.2 建立数据库连接 -2
? 要想对数据库进行访问,必须先和数据库建立连接。建立一个数
据库连接总是需要两个步骤:
l、载入驱动程序
Class.forName("驱动程序名称 ");
2、建立连接
Connection con = DriverManager.getConnection(url,“用户名称 ","用
户密码 ");
? JDBC驱动程序管理器可以以两种方式进行数据库访问:一是使用
JDBC/ODBC桥接驱动程序;另一种方式是使用 JDBC驱动程序直
接和数据库连接。下面将使用两个实例来分别讲解如何使用这两
种方式进行数据库访问。
? !不同的驱动程序,驱动程序名称以及子协议名称是可以不一样
的。在随驱动程序提供的文档中能够找到具体使用方法。
9.2.1 使用 JDBC/ODBC桥接驱
动程序
使用 MS Access建立一个名为 bookTest.mdb的数据库, 该数据库中
有一张表 bookInfo,该表的的字段名称, 数据类型和字段含义参
见教材表 9.2.1。
使用 ODBC管理工具为 bookTest.mdb的建立一个名为 Book的数据
源 。 设定好访问该数据源的用户名称和密码 (本例中分别设定为
admin 和 xyz)。
(1) 载入驱动程序
? 使用 JDBC/ODBC桥接驱动程序,该驱动程序的名称为
,sun.jdbc.odbc.JdbcOdbcDriver,,使用下面的语句将载入
JDBC/ODBC桥接驱动程序:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
(2) 建立连接
? 使用下面的语句建立一个和数据库的连接:
Connection con=DriverManager.getConnection(
"jdbc:odbc:Book","admin","xyz");
JDBC/ODBC桥连接 Access的硬件设置
打开控制面板,选中管理工具。如图
JDBC/ODBC桥连接 Access的硬件设置
? 打开管理工具,选中 (ODBC)数据源
JDBC/ODBC桥连接 Access的硬件设置
? 打开数据源管理器,选择系统 DNS,并单击
‘添加’按钮。
JDBC/ODBC桥连接 Access的硬件设置
? 选中 Driver do Microsoft Access (*mdb)
JDBC/ODBC桥连接 Access的硬件设置
? 在 ODBC Microsoft Access安装界面中,填写数
据源 (如 Book),并选择数据库,选好过后如图
单击确定,不完成了设置。
JDBC/ODBC桥连接 SQL的硬件设置
? 在创建数据源界面中,选择 SQL Server并单击
‘完成’按钮。
JDBC/ODBC桥连接 SQL的硬件设置
? 在创建到 SQL Server的数据源界面中,命名数
据源 (如 book),注意:服务器后一定要选择 Local
JDBC/ODBC桥连接 SQL的硬件设置
? 在创建 SQL Server的新数据源中选择如下图
JDBC/ODBC桥连接 SQL的硬件设置
? 在创建到 SQL Server的新数据源界面中选择所
需数据库 (如 BookTest)单击‘下一步’如图
JDBC/ODBC桥连接 SQL的硬件设置
? 在创建到 SQL Server的新数据源界面中设置如
下,单击‘完成’
JDBC/ODBC桥连接 SQL的硬件设置
? 在 ODBC Microsoft SQL Server安装界面中单击
‘测试数据源’按钮,如果出现右图测试成功界
面,则单击‘完成’结束设置
例 9.2.1 JdbcOdbc.java
import java.sql.*;
public class JdbcOdbc{
public static void main(String[] args){
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection(
"jdbc:odbc:Book","admin","abc");
Statement stmt = con.createStatement();
ResultSet rs=stmt.executeQuery("select * from bookInfo");
while(rs.next()){
System.out.println(rs.getString(1)+" "+rs.getString(2)+" "
+rs.getFloat(3)+" "+rs.getString(4));
}
rs.close();
stmt.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
9.2.2 使用 JDBC驱动程序 -1
本小节介绍如何使用 JDBC驱动程序直接和运行在 MS
SQLSERVER 2000服务器上的数据库建立连接
9.2.2 使用 JDBC驱动程序 -2
? 这里使用一个 MS SQLSERVER 2000上的数据库作为例子。先在
MS SQLSERVER 2000上建立一个名为 BookTest的数据库,并且
在该数据库中创建一个名为 bookInfo的表。表 bookInfo的字段名、
数据类型及字段含义见表 9.2.2。使用企业管理器为该数据库创建
一个合法的用户,用户名为 admin,密码为 xyz。
? 下载并安装 MS SQLSERVER 2000的 JDBC驱动程序。在安装目录
的 lib子目录中,会发现有三个,jar文件 (msbase.jar,mssqlserver.jar
及 msutil.jar),就是 MS SQLSERVER 2000的 JDBC驱动程序。注意:
要使得 Java应用程序能够访问 MS SQLSERVER 2000上的数据库,
必须使得在类路径中能够找到这三个,jar文件。您可以在环境变量
中设置好 CLASSPATH;或是更简单一点,直接将这三个,jar文件
解压缩到应用程序所在的目录。
(1) 载入驱动程序
MS SQLSERVER 2000 JDBC驱动程序的名称为 (参看该驱动程序
安装目录下的文档 )
“com.microsoft.jdbc.sqlserver.SQLServerDriver”。
使用下面的语句将载入 MS SQLSERVER 2000 JDBC驱动程序:
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
(2) 建立连接
使用下面的语句建立一个和数据库的连接:
String url ="jdbc:microsoft:sqlserver,//127.0.0.1:1433";
Connection con=DriverManager.getConnection(url,"admin","xyz" );
con.setCatalog("bookTest");
本例中使用 JDBC驱动程序直接和数据库服务器建立连
接,url子协议的书写方式与上例中有所不同 (参看随驱
动程序提供的帮助文档 )。例 9.2.2完整显示了使用
JDBC驱动程序直接访问 MS SQL SERVER 2000数据库
的源代码,完成和例 9.2.1相似的功能。
例 9.2.2 Jdbc.java
? import java.sql.*;
? public class Jdbc{
? public static void main(String[] args){
? try{
? Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
? String url ="jdbc:microsoft:sqlserver://127.0.0.1:1433";
? Connection con=DriverManager.getConnection(url,"admin","xyz" );
? con.setCatalog("bookTest");
? Statement stmt = con.createStatement();
? ResultSet rs=stmt.executeQuery("select * from bookInfo");
? while(rs.next()){
? System.out.println(rs.getString(1)+" "+rs.getString(2) +" "+rs.getFloat(3)+" "+rs.getString(4));
? }
? rs.close();
? stmt.close();
? }catch(Exception e){
? e.printStackTrace();
? }
? }
}
9.2.3 使用配置文件
使用 JDBC的一个优点就是:数据库编程独立于平台和数据库类
型。也就是数据库类型改变后,访问数据的代码不需要改变 (数据
库驱动程序名称和数据库 url需要做相应的变动 )。在例 9.2.1和 9.2.2,
驱动程序名称和数据库 url都已经被, 硬, 编码到应用程序中。一
旦所访问的数据库类型改变后,必须修改程序中的驱动程序名称
和数据库 url,重新编译后才能运行。这对于应用程序的用户是不
能接受的,另一方面也削减了 JDBC数据库编程独立于数据库类型
的优点。可以通过使用配置文件来解决这个问题:提供一个设置
界面,用户可以在该界面中指定驱动程序的名称以及数据库 url,
并将结果保存到一个配置文件中。应用程序进行数据库连接时使
用配置文件中的信息,这样可以提高应用程序的灵活性。
9.2.3 使用配置文件
? 为了简单起见,这里的例子只演示了如何从配置文件中读取信息。
在第 11章的项目实例中演示了如何完整地读写配置文件。
? 在应用程序所在的目录中创建一个配置文件 db.cfg,该文件中的内
容为:
dbDriver=com.microsoft.jdbc.sqlserver.SQLServerDriver
dbIP=127.0.0.1
dbPort=1433
dbUserName=admin
dbPassword=xyz
defaultDbName=bookTest
9.2.3 使用配置文件
? Java语言中提供了一个类 java.util.Properties,该类中提供了 load()
方法可以从输入流中读入属性值。下面的语句从配置文件 db.cfg中
读入配置信息存放到对象 prop中:
Properties prop=new Properties();
prop.load(new FileInputStream("db.cfg"));
? 从配置文件中读入的配置信息是以 (关键字,属性值 )对的形式存
放在对象 prop中的。例如要取得关键字 dbDriver的属性值,使用
getProperty()方法:
String driver=prop.getProperty("dbDriver");
这时候 driver中的值为
,com.microsoft.jdbc.sqlserver.SQLServerDriver”。
? !为了提高应用程序的安全性,可以对配置文件中的用户名和用
户密码进行加密操作。
9.3 执行 SQL语句
? 和数据库建立连接的目的是为了让应用程序能够和数
据库进行交互。首先使用连接对象中 createStatement()
方法创建一个 Statement对象,然后就可以通过
Statement对象向数据库发送各种 SQL语句了。
? Statement类型的对象中提供了几种不同的执行 SQL语
句的方法,如:
executeUpdate(SQL),executeQuery(SQL)、
execute(SQL) 以及 executeBatch()
9.3.1 executeUpdate
? executeUpdate(SQL)方法用来执行那些会修改数据库的 SQL语句,
例如 insert,update,delete以及 create等命令。
例如,如果要向数据库 bookTest的 bookInfo表中插入一条记录:
stmt.executeUpdate("insert into bookInfo values ('B0002','程序设计
',23.45,'XX出版社 ')")
9.3.2 executeQuery
? 如果对数据库进行查询操作,那么使用方法
executeQuery(SQL),该方法将返回一个 ResultSet类型
的结果集对象,该对象中包含了所有查询结果。如:
ResultSet rs=stmt.executeQuery("select * from bookInfo");
要访问结果集中的一条记录,需要定位到该记录。
ResultSet类型的对象中提供了 next()方法用于依次定位
结果集中的每条记录。
9.3.3 executeBatch
? executeBatch()方法用来批量执行 SQL语句。需要注意的是,这些
要批量执行的 SQL语句是更新类型 (如 insert,update,delete以及
create等 ) 的,即会对数据库进行修改操作的 SQL语句,并且其中
不能包含查询类型 (select)的 SQL语句。
下面的一段代码演示了如何使用 executeBatch()方法:
Statement stmt=con.createStatement();
stmt.addBatch(updateSql_1);
stmt.addBatch(updateSql_2);
stmt.addBatch(updateSql_3);
int []results=smt.executeBatch();
上面的代码片断中,向 stmt对象中添加了三条更新类型的 SQL语句。
调用 executeBatch()方法后,这三条 SQL语句将批量执行。该方法
返回的是一个整型数组,其中依次存放了每条 SQL语句对数据库
产生影响的行数。
9.4 使用 Prepared Statement
? 前面的介绍中,使用数据库连接对象创建 Statement对
象,然后通过 Statement对象向 DBMS发送 SQL语句。其
实还可以通过数据库连接对象创建 PreparedStatement类
型的对象,然后通过它向 DBMS发送 SQL语句。
? 在有些情形下,PreparedStatement类型的对象与
Statement类型对象相比,有如下两个优点:
1、效率高
2、使用方便
效率高
? 使用数据库连接对象创建 PreparedStatement类型的对象时,作为
参数的 SQL语句会立刻被发送到 DBMS并进行编译。这样
PreparedStatement类型的对象中包含的是预编译好的 SQL语句。这
样,当需要再次执行 PreparedStatement类型对象中的 SQL语句时,
DBMS立刻就可以执行其中已经编译好的 SQL语句。
使用方便 -1
? 使用数据库连接对象创建 PreparedStatement类型的对象时,作为参数的
SQL语句中允许使用参数占位符 (???)。这样,在每次运行这条 SQL语句
时,可以通过赋给参数占位符不同的参数值,从而完成不同的功能。
? 如果需要向表 bookInfo中插入一条新的记录 (?B0003?,?c++?,78.50,?清华大
学出版社 ’ ),根据上面所讲的知识,下列的代码片断能够完成该功能:
PreparedStatement update=con.prepareStatement("insert into bookInfo values(?,?,?,?)");
update.setString(1,"B0003");
update.setString(2,"c++");
update.setFloat(3,78.50f);
update.setString(4,“清华大学出版社, );
update.executeUpdate();
上述的代码片断中, 使用了 setXXX()方法逐个设置占位符的值 。 在实
际的应用程序中, 一条记录的字段往往有几十个, 那么使用这种方式
去做的话, 程序就会写得很长, 效率不高 。 这时候, 可以使用
setObject()方法结合循环语句来设置占位符的值 。
使用方便 -2
? 同样以上述插入一条记录为例:
PreparedStatement update=con.prepareStatement("insert into bookInfo values(?,?,?,?)");
Object []line={"B0003","C++",new Float(78.50),"清华大学 "};
for(int i=1;i<=line.length;i++){
update.setObject(i,line[i-1]);
}
update.executeUpdate();
setObject()方法中有两个参数:第一个参数是占位符的索引;第二个参数
是对象类型的值, 赋值给占位符所指的参数 。 需要注意的是, 所赋的对
象类型必须和占位符所指参数的 SQL数据类型相匹配 。 例如上述代码片
断中, 将 Float类型的对象赋值给一个 SQL数据类型为了 float的参数, 将
String类型的对象赋值给 SQL数据类型为 varchar的参数 。
9.5 事务处理 -1
? 先来看一个帐户转移的问题:假设存在两个帐户 A 和 B,现在需要将帐户
A上的部分资金转移到帐户 B中。使用下面的代码:
PreparedStatement stmt_1 = con.preparedStatement(SQL1);
PreparedStatement stmt_2 = con.preparedStatement(SQL2);
stmt_1.executeUpdate();
// stmt_1语句的作用是:从帐户 A减去资金 x
stmt_2.executeUpdate();
// stmt_2语句的作用是:将帐户 B加上资金 x
? 如果一切正常,上面的代码片断完成资金转移的功能。然而,实际情况
可能不是这么简单。例如,如果语句 stmt_1正常执行完毕,而语句 stmt_2
在执行时出现异常,那么就会出现数据的不一致性:帐户 A上的资金减
少了 x,而帐户 B上资金并没有增加。这种情况显然是不能接受的。
9.5 事务处理 -2
? 要解决这个问题,我们希望:语句 stmt_1和语句 stmt_2组成一个执
行单元,并且只有在 stmt_1和 stmt_2均正确执行完毕后,才对数
据库产生影响;任何一个语句出错都退回到这个执行单元执行之
前的状态。这个执行单元就被称之为事务。
? 在缺省状态下,创建的连接是处于自动递交 (auto commit)模式:
每条语句执行完毕后,立即向 DBMS递交执行结果。亦即每条语
句独立构成一个事务。因此为了让若干条语句构成一个事务,在
执行第一条语句前先关闭自动递交模式,使用如下方法:
con.setAutoCommit(false);
? 在将自动递交模式设置为 false后,所执行的语句不会将执行结果
递交给 DBMS,直到调用如下的递交语句:
con.commit();
9.5 事务处理 -3
? 因此,要在上述的帐户转移定制事务,使用下面的代码片断:
con.setAutoCommit(false); // 设置为非自动递交模式
PreparedStatement stmt_1 = con.preparedStatement(SQL1);
PreparedStatement stmt_2 = con.preparedStatement(SQL2);
stmt_1.executeUpdate(); // 执行完毕后不立刻递交
stmt_2.executeUpdate(); // 执行完毕后不立刻递交
con.commit(); // 递交事务
con.setAutoCommit(true); // 恢复自动递交模式
stmt_1.close();
stmt_2.close();
9.5 事务处理 -4
? 再回到上述的问题:语句 stmt_1正常执行完毕,而语
句 stmt_2在执行时出现异常。这时候,就需要放弃该事
务,并且恢复到事务开始时的状态。为此,可以把事
务放在一个 try块中,在对应的 catch块中捕获事务执行
过程中所出现的异常。一旦有异常出现,可以调用
rollBack()方法进行事务回滚,恢复到事务开始时的状
态。这样就可以有效地保持数据库数据的完整性和一
致性。如:
9.5 事务处理 -5
try{
con.setAutoCommit(false); // 设置为非自动递交模式
PreparedStatement stmt_1 = con.preparedStatement(SQL1);
PreparedStatement stmt_2 = con.preparedStatement(SQL2);
stmt_1.executeUpdate(); // 执行完毕后不立刻递交
stmt_2.executeUpdate(); // 执行完毕后不立刻递交
con.commit(); // 递交事务
con.setAutoCommit(true); // 恢复自动递交模式
stmt_1.close();
stmt_2.close();
}catch(SQLException e){
e.printStackTrace();
if (con != null) {
try{
con.rollback(); //事务回滚
con.setAutoCommit(true); // 恢复自动递交模式
} catch(SQLException ex) {
ex.printStackTrace();
}
}
}
9.6 编写数据库工具类 -1
? Java编程语言中提供了用于数据库访问的各种 API。有的时候,一
些 API总是要组合在一起使用。例如,要建立一个数据录连接,
总是需要先载入数据库驱动程序,然后使用驱动程序管理器建立
连接。为此,我们可以编写一个方法 (如例 9.6.1中的
acquireConnection(),该方法完成载入数据库驱动程序并使用驱动
程序管理器建立连接 ),然后将该方法封装到一个自定义的类中
(如例 9.6.1中的 SqlUtil类 )。这样,要创建一个数据库连接,只需
要一个语句:
Connection con=SqlUtil.acquireConnection(...);
这样可以更加高效、简洁地编写出应用程序。
9.6 编写数据库工具类 -2
? 在例 9.6.1中,类 SqlUtil被打包到 edu.njust.cs。因此,在其他的类
中需要使用类 SqlUtil的时候,必须先使用:
import edu.njust.cs.*;
或是:
import edu.njust.cs.SqlUtil;
来引入 (import)SqlUtil类。
? SqlUtil类中集成了读写数据库和表格的一些方法,包括将数据库
中的记录读入表格以及将表格中的数据写入数据库等等。本节中
SqlUtil类中的方法还相当少,读者可以逐步向其中添加更多的实
用方法来丰富该类的内容。在第 11章中,读者将会看到 SqlUtil类
中会拥有更多的实用方法。
9.6 编写数据库工具类 -3
? 我们希望数据库中存储的字段值不出现空值 (null),这样可以在应用程序
中减少很多烦人的空值条件判断。为此,当字段值为空值时,可以考虑
使用特殊值来代替。例如,一个 Double类型的字段,可以用
Double.NEGATIVE_INFINITE这个特殊值来代替空值。也就是说,在数
据库中,如果一个数据字段的值为 Double.NEGATIVE_INFINITE,表示
该字段值为空值。使用这种处理方式后,当需要将字段值为空值 (已经由
特殊值表示 )的字段读入到表格中显示时,需要将表示空值的特殊值转化
为真正的空值 (null),这样才能使得表格正确显示。 SqlUtil类中提供了一
个方法 getLineForTableFromLineForDB(),该方法将适合数据库存储的一
行数据 (空值由特殊值表示 )转化为适合表格显示的数据:依次判断字段值,
如果是一个表示空值的特殊值,则将其转换为空值 (null)。由于不同的数
据类型,所定义的代表空值的特殊值是不同的 (例如,整型可以是
Integer.MIN_VALUE),因此,需要依据每个字段的数据类型来判断是否
使用了特殊值代替空值。出于演示目的,本节中的所给出的方法
getLineForTableFromLineForDB()目前还很不完善,只考虑了 3种数据类型
String,Double 和 Integer。
9.7 一个实例 -1
? 本小节编写一个程序 SimpleBookManager.java,该程序
可以向数据库中录入图书信息 (图 9.7.1)、使用不同方式
查询图书信息 (图 9.7.2)以及删除指定的图书信息 ((图
9.7.3)。
? 该应用程序中用到了几个打包到 edu.njust.cs中的自定义
类,如表模型类 CustomTableModel.java、布局工具类
LayoutUtil.java、字体工具类 SetFont.java、数据库工具
类 SqlUtil.java以及按钮类 TextAndPicButton.java。
9.7 一个实例 -2
? 9.7.1数据库
? 9.7.2布局及功能简介
? 9.7.3源代码
9.7.1数据库
? 使用 9.2.2小节中所述的运行在 MS
SQLSERVER上的数据库 BookTest,该数
据库中只有一张表 bookInfo,字段名称和
数据类型见表 9.2.2。
9.7.2布局及功能简介 -1
? 该应用程序中的主画面包括一个工具条和一个表格:
? 工具条 ―― 包含 4个按钮,分别执行不同的功能。
? 表格 ―― 用于显示相关图书记录的信息。
? 布局 ―― 面板 p布局为 BorderLayout,工具条置于面板 p的
NORTH 方位;表格在滚动窗格中 (JScrollPane),滚动窗格置于面
板 p的 CENTER方位;面板 p添加到 Jframe 的内容窗格 (contentPane)
中。
? 增加 ―― 点击“增加”按钮后,会弹出一个图书信息录入对话框。
? 查询 ―― 点击“查询”按钮后,会弹出查询方式选择对话框 。
? 删除 ―― 用户选定表格中的一条记录后,点击“删除”按钮,会
弹出确认对话框,用户确认后将删除表格中所选定的图书记录。
9.7.2布局及功能简介 -2
图 9.7.1 录入图书信息
9.7.2布局及功能简介 -3
图 9.7.2 查询图书信息
9.7.2布局及功能简介 -4
图 9.7.3 删除图书信息
9.7.3源代码
具体源代码请参见教材。