- 5.1.1 JDBC
- 5.1.1.1 连接到数据库
- 5.1.1.2 向数据库系统中传递SQL语句
- 5.1.1.3 获取查询结果
- 5.1.1.4 预备语句
- 5.1.1.5 可调用语句
- 5.1.1.6 元数据特性
- 5.1.1.7 其他特性
5.1.1 JDBC
JDBC
标准定义了Java
程序连接数据库服务器的应用程序接口(Application Program Interface,API
),JDBC
原来是Java
数据库连接(Java Database Connectivity
)的缩写,但其全称现在已经不用了)。Java
程序必须引用java.sql.*
,它包含了JDBC
所提供功能的接口定义。
5.1.1.1 连接到数据库
要在Java
程序中访问数据库,首先要打开一个数据库连接。这一步需要选择要使用哪个数据库,例如,可以是你的机器上的一个Oracle
实例,也可以是运行在另一台机器上的一个PostgreSQL
数据库。只有在打开数据库连接以后,Java
程序才能执行SQL
语句。
如何打开一个数据库连接
可以通过调用java.sql
包中的DriverManager
类的getConnection
方法来打开一个数据库连接。该方法有三个参数:
URL
:getConnection
方法的第一个参数是以字符串类型表示的URL
,指明服务器所在的主机名称以及可能包含的其他信息,例如:- 与数据库通信所用的协议,
- 数据库系统用来通信的端口号,
- 还有服务器端使用的特定数据库。
注意JDBC
只是指定API
而不指定通信协议。一个JDBC
驱动器可能支持多种协议,我们必须指定一个数据库和驱动器都支持的协议。协议的详细内容是由提供商
设定的。
2. 数据库用户:getConnection
方法的第二个参数用于指定一个数据库用户标识,它为字符串类型
3. 密码:getConnection
方法的第三个参数是密码,它也是字符串类型。(注意,把密码直接写在JDBC
代码中会增加安全性隐患,因为你的代码有可能会被某些未被授权的用户所访问。)
必须在连接数据库之前完成驱动程序的加载
每个支持JDBC
的数据库产品都会提供一个JDBC
驱动程序(JDBC driver
),该驱动程序必须被动态加载才能实现Java
对数据库的访问。事实上,必须在连接数据库之前完成驱动程序的加载。
Class.forName加载驱动
调用Class.forName
方法完成驱动程序的加载,在调用时需要通过参数来指定一个实现了java.sql.Driver
接口的实体类。java.sql.Driver
接口的功能是为了实现不同层面的操作之间的转换,一边是与产品类型无关的JDBC
操作,另一边是与产品相关的、在所使用的特定数据库管理系统中完成的操作。
要实现在java类路径中放入驱动程序jar包
Oracle
的驱动程序:oracle.jdbe.driver.OracleDriver
。该驱动程序包含在一个.jar
文件里,可以从提供商的网站下载,然后放在Java
的类路径( classpath
)里,用于Java
编译器访问。
其他数据库的驱动名称
数据库 | 对应的驱动名称 |
---|---|
IBM DB2 数据库驱动 |
com.ibm.db2.jdbe.app.DB2Driver ; |
Microsoft SQL Server 数据库驱动 |
com.microsoft.sqlserver.jdbc.SQLServerDriver ; |
Postgre SQL 数据库驱动 |
org.postgresqL.Driver ; |
MySQL 数据库驱动 |
com.mysql.jdbc.Driver ; |
Sun
公司还提供了一种”桥接驱动器“,可以把JDBC
调用转换成ODBC
。该驱动仅是用于那些支持ODBC
但不支持JDBC
的厂商。
协议
用来与数据库交换信息的具体协议并没有在JDBC
标准中定义,而是由所使用的驱动程序决定的。有些驱动程序支持多种协议,使用哪一种更合适取决于所连接的数据库支持什么协议。
我们的例子里在打开一个数据库连接时,字符串jdbe:oracle:thin
指定了Oracle
支持的一个特定协议。
5.1.1.2 向数据库系统中传递SQL语句
通过数据库连接创建Statement对象
旦打开了一个数据库连接,程序就可以利用该连接来向数据库发送SQL
语句用于执行。这是通过Statement
类的一个实例来完成的。一个Statement
对象并不代表SQL
语句本身,而是实现了可以被Java
程序调用的一些方法,通过参数来传递SQL
语句并被数据库系统所执行。我们的例子在连接变量conn
上创建了一个Statement
句柄(stmt
)。
使用
executeQuery
方法执行查询语句
用executeUpdate
方法来执行查询语句,返回一个结果集。
executeUpdate
方法执行费非查询语句 更新 插入删除 建表等
executeUpdate
方法可以执行像更新(update
)、插入(insert
)、删除(delete
)、创建表(create table
)等这样的非查询性语句。
- 对于更新插入或删除
executeUpdate
方法它返回一个整数,表示被插入、更新或者删除的元组个数。 - 对于
DDL
语句,executeUpdate
方法返回值是0
5.1.1.3 获取查询结果
executeQuery方法返回结果集
示例程序用stmt.executeQuery
来执行一次查询。它可以把结果中的元组集合提取到Resultset
对象变量rset
中并每次取出一个进行处理。
结果集的next方法
结果集的next
方法用来查看在集合中是否还存在至少一个尚未取回的元组,如果存在的话就取出。next
方法的返回值是一个布尔变量,表示是否从结果集中取回了个元组。
结果集的get方法
可以通过一系列的名字以get
为前缀的方法来得到所获取元组的各个属性。方法setstring
可以返回所有的基本SQL
数据类型的属性(被转换成Java
中的String
类型的值),当然也可以使用像getFloat
那样一些约束性更强的方法。
结果集get方法的参数
这些不同的get
方法的参数
既可以是一个字符串类型的属性名称,又可以是一个整数,用来表示所需获取的属性在元组中的位置。图5-1给出了两种在元组中提取属性值的办法:
利用属性名提取( dept_name
)或者利用属性位置提取(2,代表第二个属性)。
java成宿结束后一定要关闭数据库连接
Java
程序结束的时候语句和连接都将被关闭。注意关闭连接是很重要的,因为数据库连接的个数是有限制的;未关闭的连接可能导致超过这一限制。如果发生这种情况,应用将不能再打开任何数据库连接。
5.1.1.4 预备语句
我们也可以通过以”?
“来代表以后再给出的实际值,而创建一个预备语句。
数据库系统在准备查询语句的时候对它进行编译。在每次执行该语句时(用新值替换”?”),数据库可以重用预先编译的查询的形式,应用新值进行查询。
图5-2的代码框架给出了如何使用预备语句的示例。
使用Connection类的prepareStatement方法创建PreparedStatement对象
可以使用Connection
类的prepareStatement
方法来提交SQL
语句用于编译。它返回一个PreparedStatement
类的对象。此时还没有执行SQL
语句。执行需要PreparedStatement
类的两个方法executeQuery
和executeUpdate
。
但是在它们被调用之前,我们必须使用PreparedStatement
类的方法来为”?
“参数设定具体的值。
setString
方法以及诸如setInt
等用于其他的SQL
基本类型的其他类似的方法使我们能够为参数指定值。
set
第一个参数用来确定我们为哪个”?
“设定值,- 如果给第1个问号设置值,则
set
方法的第一个参数设置为1
- 如果给第1个问号设置值,则
set
方法的第二个参数是我们要设定的值。
优先使用预备语句
预备语句更加高效
在同一查询编译一次然后设置不同的参数值执行多次的情况下,预备语句使得执行更加高效。
预备语句可检查用户输入
然而,预备语句有一个更加重要的优势,预备语句可以用户输入,
即使是只运行一次,预备语句都是执行SQL
查询的首选方法。假设我们读取了一个用户输入的值,然后使用Java
的字符串操作来构造SQL
语句。如果用户输入了某些特殊字符,例如一个单引号,除非我们采取额外工作对用户输入进行检查,否则生成的SQL
语句会出现语法错误。setString
方法为我们自动完成检查,并插入需要的转义字符,以确保语法的正确性。
预备语句可防止SQL注入
一种叫做SQL
注入(SQL injecton
)的技术可以被恶意黑客用来窃取数据或损坏数据库。
假设一个Java
程序输入一个字符串name
,并且构建下面的查询:
1 | "select * from instructor where name='"+name+"';" |
如果用户没有输人一个名字,而是输入:x' or 'y'='y'
这样,产生的查询语句就变成:
1 | select * from instructor where name='x' or 'y'='y'; |
在生成的查询中, where
子句总是真,所以查询结果返回整个instructor
关系。
1 | mysql> select * from instructor where name='x' or 'y'='y'; |
更诡计多端的恶意用户甚至可以编写输入值以输出更多的数据。使用预备语句就可以防止这类问题,因为输入的字符串setString
方法将被插入转义字符
,因此最后的查询变为:
1 | select * from instructor where name ='x\' or \'Y\'=\'Y'; |
这是无害的查询语句,返回结果为空集。
1 | mysql> select * from instructor where name ='x\' or \'Y\'=\'Y'; |
一次执行多条SQL语句的情况
比较老的系统允许多个由分号隔开的语句在一次调用里被执行。
此功能正逐渐被淘汰,因为恶意的黑客会利用SQL
注入技术插入整个SQL
语句。由于这些语句在Java
程序所有者的权限上运行,像删除表(drop table
)这样毁灭性的SQL
语句会被执行。SQL
应用程序开发者必须警惕这种潜在的安全漏洞。
5.1.1.5 可调用语句
JDBC
还提供了CallableStatement
接口来允许调用SQL
的存储过程和函数(稍后在5.2节描述)。此接口对函数和过程所扮演的角色跟prepareStatement
对查询所扮演的角色一样。
函数返回值和过程的对外参数的数据类型必须先用方法registerOutParameter()
注册,它们可以用与结果集用的方法类似的get
方法获取。请参看JDBC
手册以获得更细节的信息。
5.1.1.6 元数据特性
正如我们此前提到的,一个Java
应用程序不包含数据库中存储的数据的声明。这些声明是SQL
数据定义语言(DDL
)的一部分。因此,使用JDBC
的Java
程序必须
- 要么将关于数据库模式的假设硬编码到程序中,
- 要么直接在运行时从数据库系统中得到那些信息。
后一种方法更可取,因为它使得应用程序可以更健壮地处理数据库模式的变化。
结果集元数据 ResultsetMetaData
回想一下,当我们提交一个使用executeQuery
方法的查询时,查询结果被封装在一个Resultset
对象中。
如何获取ResultsetMetaData对象 Resultset.getMetaData
方法
接口Resultset
有一个getMetaData()
方法,它返回一个包含结果集元数据的ResultsetMetaData
对象。 ResultSetMetaData
进一步又包含查找结果集元数据的方法,例如结果的列数、某个特定列的名称,或者某个特定列的数据类型。这样,即使不知道结果的模式,我们也可以方便地执行查询。
实例省略
ResultSetMetaData
对象的getColumnCount
方法返回结果关系的属性个数(元数)。
这使得我们能够遍历每个属性(请注意,和JDBC
的惯例一致,我们从1开始)。对于每一个属性,我们采用getColumnName
和getColumnTypeName
两个方法分别得到属性的名称和属性的数据类型。
数据库元数据 DatabaseMetaData
DatabaseMetaData
接口提供了查找数据库元数据的机制。
如何获取DatabaseMetaData
Connection实例.getMetaData
方法
接口Connection
包含一个getMetaData
方法用于返回一个DatabaseMetaData
对象。接口DatabaseMetaData
进一步又含有大量的方法可以用于获取程序所连接的数据库和数据库系统的元数据。
例如,有些方法可以返回数据库系统的产品名称和版本号。另外一些方法可以用来查询数据库系统所支持的特性。
其他获取数据库本省信息的方法
还有其他可以返回数据库本身信息的方法,下面的代码显示了如何找出数据库中的关系的列(属性)信息。变量com
假定存储了一个已经打开的数据库连接。方法getColumns
有四个参数:
- 一个目录名称(
null
表示目录名称被忽略)、 - 一个模式名称模板、
- 一个表名称模板,
- 以及一个列名称模板。
模板
模式名称
、表名称
和列名称
的模板
可以用于指定一个名字或一个模板。
模板可以使用SQL
字符串匹配特殊字符如”%
“和”_
“;例如模板”%
“匹配所有的名字。
只有满足特定名称或模板的模式中的表的列才会被检索到。
结果集中的每行包含一个列的信息
。
结果集中的行包括若干个列,如目录名称、模式、表和列、列的类型,等等。
1 | DatabaseMetaData dbmd = conn.getMetaData(); |
获取数据库其他信息
DatabaseMetaData
还提供了获取数据库信息的一些其他方法,比如,可以用来获取关系(get Tables()
),外码参照( getCrossReference()
)、授权、数据库限制如最大连接数等的元数据。
元数据接口可以用于许多不同的任务。例如,它们可以用于编写数据库的浏览器,该浏览器允许用户找出一个数据库中的关系表,检查它们的模式,检查表中的行,用选择操作查看想要的行,等等。
元数据信息可以是代码更通用
元数据信息可以用于使这些任务的代码更通用;
- 例如,用来显示一个关系中的行的代码可以用这样的方法编写使得它能够在所有可能的关系上工作,无论这些关系的模式是什么。
- 类似地,可以编写这样的代码,它获得一个査询字符串,执行查询,然后把结果打印成一个格式化的表;无论提交了的实际查询是什么,这段代码都可以工作。
5.1.1.7 其他特性
JDBC
提供了一些其他特性,如可更新的结果集(updatable result sets
)。它可以从一个在数据库关系上执行选择和/或投影操作的查询中创建一个可更新的结果集。然后,一个对结果集中的元组的更新将引起对数据库关系中相应元组的更新。
回想一下4.3节,事务把多个操作封装成一个可以被提交或者回滚的原子单元
默认情况下,每个SQL
语句都被作为一个自动提交的独立的事务来对待。
打开或关闭自动提交
JDBC
的Connection
接口中的方法setAutoCommit()
允许打开或关闭这种行为。
关闭自动提交 开启事务
因此,如果con
是一个打开的连接,则conn.setAutoCommit(false)
将关闭自动提交。然后事务必须用conn.commit()
显式提交或用conn.rollback()
回滚。
开始自动提交
自动提交可以用conn.setAutoCommit(true)
来打开。
处理大对象数据
读取数据库中的大对象数据
JDBC
提供处理大对象的接口而不要求在内存中创建整个大对象。为了获取大对象, Resultset
接口提供方法getBlob()
和getClob()
,它们与getString()
方法相似,但是分别返回类型为Blob
和Clob
的对象。这些对象并不存储整个大对象,而是存储这些大对象的定位器,即指向数据库中实际大对象的逻辑指针。从这些大对象中获取数据与从文件或者输入流中获取数据非常相似,可以采用像getBytes
和getSubString
这样的方法来实现。
将大对象数据写入数据库中
反向操作时,为了向数据库里存储大对象,可以用PreparedStatement
类的方法setBlob(int parameterIndex, InputStream inputStream)
把一个类型为二进制大对象(blob
)的数据库列与一个输入流关联起来。
当预备语句被执行时,数据从输入流被读取,然后被写入数据库的二进制大对象中。与此相似,使用方法setClob
可以设置字符大对象(clob
)列,该方法的参数包括该列的序号和一个字符串流。
行集row set
JDBC
还提供了行集( row set
)特性,允许把结果集
打包起来发送给其他应用程序。行集既可以向后又可以向前扫描,并且可被修改。行集一旦被下载下来就不再是数据库本身的内容了,所以我们在这里并不对其做详细介绍。
原文链接: 5.1.1 JDBC