JDBC使用

7.2 创建JDBC应用

7.2.1 创建JDBC应用程序的步骤
使用JDBC操作数据库中的数据包括6个基本操作步骤:
(1)载入JDBC驱动程序:
首先要在应用程序中加载驱动程序driver,使用Class.forName()方法加载特定的驱动程序,每种数据库管理系统的驱动程序不同,由数据库厂商提供。
(2)定义连接URL,建立数据库连接对象:
通过DriverManager类的getConnection()方法获得表示数据库连接的Connection类对象。
(3)创建Statement对象
获取Connection对象以后,可以用Connection对象的方法创建一个Statement对象的实例。
(4)执行查询或更新:
Statement对象可以执行SELECT语句的executeQuery()方法或执行INSERT、UPDATE、DELETE语句的executeUpdate()方法。
(5)操作结果集:
利用ResultSet对象对数据库操作返回的结果进行处理。ResultSet包含一些用来从结果集中获取数据并保存到Java变量中的方法。主要包括next()方法,用于移动结果集游标,逐行处理结果集,getString()、getInt()、getDate()、getDouble()等方法,用于将数据库中的数据类型转换为Java的数据类型。
(6)释放资源:
使用与数据库相关的对象非常耗内存,因此在数据库访问后要关闭与数据库的连接,同时还应该关闭ResultSet、Statement和Connection等对象。可以使用每个对象自己的close()方法完成。

7.2.2 JDBC应用程序的实现
(1)载入JDBC驱动程序
在JavaAPI中的Class类提供了加载驱动程序的方法,方法格式如下:

参数className表示驱动类的描述符字符串,例如加载Oracle驱动的语句描述符为:
老版本驱动,一般指5.x版本

新版本驱动,一般指8.x版本

Mysql数据库的JDBC驱动程序文件可到Mysql官方网站下载。注意加载驱动程序的时候会抛出ClassNotFoundException,SQLException异常。
(2)定义连接URL,建立数据库连接对象
DriverManager类提供了getConnection()方法可获得指定数据库的连接对象,方法格式如下:

MySQL数据库的url参数格式为:

下面语句是建立MySQL数据库连接对象的语句:

更多的创建Connection对象的方法,同学们可以查阅JavaAPI文档。
(3)创建Statement对象
Connection接口中提供了获得Statement对象的方法,方法格式如下:

(4)执行查询或更新
获取Statement对象后,就可以通过Statement对象的executeQuery()或exeucteUpdate()方法执行查询或者更新操作,有关方法的详细解释,同学们可以查阅JavaAPI文档。
(5)操作结果集
ResultSet接口提供对结果集进行操作的方法,主要包括:
1boolean next() throws SQLException:移动结果集操作指针。
2getXxx(String columnName) throws SQLException:根据传入列的名字获取指定列的值。
3getXxx(1) throws SQLException:根据传入列的编号获取指定列的值。
SQL类型与Java数据类型不同,下表列出了SQL类型与Java数据类型的对应关系。

SQLType``JavaType``
CHAR``String``
VARCHAR``String``
LONGVARCHAR``String``
NUMERIC``java.math.BigDecimal``
DECIMAL``java.math.BigDecimal``
BIT``boolean``
TINYINT``byte``
SMALLINT``short``
INTEGER``int``
BIGINT``long``
REAL``float``
FLOAT``double``
DOUBLE``double``
BINARY``byte[]``
VARBINARY``byte[]``
LONGVARBINARY``byte[]``
DATE``java.sql.Date``
TIME``java.sql.Time``
TIMESTAMP``java.sql.Timestamp``

更多的操作结果集的方法,同学们可以查阅JavaAPI文档。
(6)释放资源
数据库操作完成后,需要调用ResultSet、Statement、Connection接口中的关闭方法,释放相关资源,关闭顺序如下:
首先关闭结果集ResultSet对象
然后关闭Statement对象
最后关闭Connection对象。

例: 回收JDBC资源示例代码

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    **/****

** * 回收JDBC资源示例代码**

** */**

**public** **static** **void** **close**(**ResultSet** **resultSet**, **Statement** **statement**, **Connection** **connection**)**{**

**try**

**{**

**if** **(** **resultSet**!=**null**)**{**

**resultSet**.**close**(**)**;

**}**

**}**catch**(**SQLException **e**)**{**

**System**.**out**.**println**(**e**.**getMessage**(**)**)**;**

**}**

**try**

**{**

**if** **(** **statement**!=**null**)**{**

**statement**.**close**(**)**;

**}**

**}**catch**(**SQLException **e**)**{**

**System**.**out**.**println**(**e**.**getMessage**(**)**)**;**

**}**

**try**

**{**

**if** **(**connection **!=**null**)**{

**connection**.**close**(**)**;

**}**

**}**catch**(**SQLException **e**)**{**

**System**.**out**.**println**(**e**.**getMessage**(**)**)**;**

**}**

**}**

7.2.3 JDBC中主要的类及常用方法
使用JDBC编写访问数据库的应用程序,需要经过加载数据库驱动程序、创建连接、创建Statement对象、发送SQL语句、解析操作结果等步骤,它们由JDBC API中一组类的方法实现。主要的类如下:
(1)Class类(了解即可,后续内容会讲)
Class类全称java.lang.Class,Java程序运行时会自动创建程序中的每个类的Class对象,通过Class类的方法,可以得到程序中每个类的信息。Class类方法主要包括:
public static Class forName(String className):该方法根据给定的字符串参数返回相应的Class对象。例:Class.forName(“com.mysql.jdbc.Driver”)的作用是加载Oracle驱动。
public String getName():该方法返回类名,例str.getClass().getName())。
如果想了解更多Class类的其它方法,可以查阅Java API文档。
(2)DriverManager类
DriverManager类在用户程序和数据库系统之间维护着与数据库驱动程序之间的连接。它实现驱动程序的装载、创建与数据库系统连接的Connection类对象。DriverManager类的方法主要包括:

Java

public static Connection getConnection(String url, String user, String password)

//根据url、数据库登录的用户名、密码获取一个数据库的连接对象。

(3)Connection接口
Connection用于管理到指定数据库的连接。
Connection con=DriverManager.getConnection (url, username, password);
Connetction类中重要的成员方法包括:
createStatement()方法:创建Statement类的实例。
prepareStatement()方法:创建PreparedStatement类的实例。
close():立即释放此Connection对象的数据库和JDBC资源,而不是等待它们被自动释放。
(4)Statement接口
Statement数据库操作类提供执行数据库操作的方法,如更新、查询数据库记录等。
Statement对象的创建方式如下:
Statement stmt=con.createStatement();
Statement类中重要的成员方法包括:
executeQuery()方法:它用来执行一个查询语句,参数是一个String对象,就是一个SELECT语句。它的返回值是ResultSet类的对象,查询结果封装在该对象中。
例:stmt.executeQuery(“select * from users where username=’张三’ and password=’123’ “);
executeUpdate()方法:它用来执行更新操作,参数是一个String对象,即一个更新数据表记录的SQL语句。使用它可以对表中的记录进行修改、插入和删除等操作。例:

Java

复制代码

stmt.executeUpdate(“INSERT INTO users(username,password) values(‘刘青’**,** ‘aaa’**)** ”**)**;

stmt.executeUpdate(“UPDATE users set password=‘bbb’ where username**=‘张三’ ”)**;

stmt.executeUpdate(“DELETE from users where username=‘李四’ ”**)**;

插入后想立即获取返回的主键,通过设置Statement.RETURN_GENERATED_KEYS来实现:

1
java

//执行INSERT语句,说明要返回数据库生成的主键

int count = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);

System.out.println(“成功插入”+count+“条记录!”);

//产生的主键以结果集的形式返回

rs = stmt.getGeneratedKeys();

//遍历结果集,输出主键,实际上结果集只有一条记录

while(rs.next()){

**long** **id** **=** **rs**.**getLong**(**1**)**;**

**System**.**out**.**println**(**"产生的主键是:"**+**id**)**;**

}

使用它还可以创建和删除数据表及修改数据表结构。例:

close():关闭Statement对象。
Statement接口的好多方法都是重载的,如果想了解更多其它方法,可以查阅JavaAPI文档。
(5)ResultSet接口
ResultSet结果集类提供对查询结果集进行处理的方法。例:
ResultSet rs=stmt.executeQuery(“ select * from users “);
ResultSet对象维持着一个指向表格的行的指针,开始时指向表格的起始位置(第一行之前)。 ResultSet类常用的方法包括:
next()方法:光标移到下一条记录,返回一个boolean值。
previous()方法:光标移到前一条记录。
getXXX()方法:获取指定类型的字段的值。调用方式 getXXX(“字段名”) 或 getXXX(int i)。i值从1开始表示结果集中第一列的字段。
close():关闭ResultSet对象。
例:

ResultSet接口提供的getXxx方法如下表所示

Method``JavaTechnologyTypeReturned``
getASCIIStream``java.io.InputStream``
getBigDecimal``java.math.BigDecimal``
getBinaryStream``java.io.InputStream``
getBoolean``boolean``
getByte``byte``
getBytes``byte[ ]``
getDate``java.sql.Date``
getDouble``double``
getFloat``float``
getInt``int``
getLong``long``
getObject``Object``
getShort``short``
getString``java.lang.String``
getTime``java.sql.Time``
getTimestamp``java.sql.Timestamp``
getUnicodeStream``java.io.InputStream of Unicode characters``

下面以用户表t_user为例,说明使用JDBC对数据库进行操作的方法,用户表结构如表所示:

名称``数据类型``主键``是否为空``说明``
ID``number``是``用户编号``
NAME``Varchar2(50)``用户名``
AGE``varchar2(5)``用户年龄``
BIRTH``date``用户生日``
PWD``varchar2(20)``否``用户密码``

【例】使用JDBC查询数据库表t_user的所有数据。

程序运行结果如下:
7:zhangsan:age:2015-09-01
8:lisi:24:2015-09-01
9:wangwu:25:2015-09-01
10:wang:23:2015-09-01
以上给大家粗略的介绍了一下JDBC中涉及到的常用相关类和接口,每个类和接口包含的方法介绍的不是十分全面,希望大家在后续的学习过程中,能充分的利用JavaAPI这个工具,不断提升自己的学习能力。

7.2.4 JDBC日期时间处理
上面例题中取出来的日期与数据库中存储的有所不同,数据库中birth列包含日期和时间,而上例中只显示了日期,没有时间。这是因为获取日期时使用的是getDate()。对于数据库中不同的时间类型,要分别采用与之相对应的Java包装类来存取: 
1日期类型用java.sql.Date 
2时间类型用java.sql.Time 
3日期/时间类型用java.sql.Timestamp;
getTimestamp()可以把年月日时分秒都取出来,getDate()只能取出年月日,getTime()只能取出时分秒。
要把JDBC的日期/时间类型转换为字符串,则可以使用下列方法:

如何把java.sql.Timestamp转换为java.util.Date ?java.sql.Timestamp是java.util.Date的子类,不需要做任何转换直接赋值即可,看下面程序段:

反过来,如何把java.util.Date转换为java.sql.Timestamp呢?java.util.Date是java.sql.Timestamp的父类,要这样转换:

7.2.5 JDBC封装工具类
通常,无论是对数据进行查询操作,还是进行增删改操作,都需要打开连接,关闭资源等操作,因此,可以把对把打开连接和关闭连接封装到一个工具类里。本章后面所有例子对数据访问所用连接都是一样的。下面的DBUtil类封装了打开连接和关闭连接方法。
【例】封装打开连接和关闭资源的DBUtil类。

程序分析:关闭连接会抛出异常,若遇到异常程序会意外终止,因此要处理异常。而为了保证关闭资源语句在出现异常时也会执行到,因此要把它放到finally语句中。另外,因为查询操作才需要关闭ResultSet资源,增删改是不需要关闭ResultSet资源的,因此DBUtil类中对关闭操作进行了重载。
刚才我们定义了数据库通用处理类DBUtil,接下来我们通过一个实例,来看一下这个类应该怎么应用。
【例7-3】使用DBUtil类操作数据库

调用query()方法输出结果:
7 $ zhangsan $ 23 $ 2015年09月01日 15:15:06
8 $ lisi $ 24 $ 2015年09月01日 15:15:23
9 $ wangwu $ 25 $ 2015年09月01日 15:15:52
10 $ hello1 $ 500 $ 2015年09月01日 15:16:03

7.2.6 SQL注入问题
在使用Statement对象查询数据库时,由于定义的SQL语句是拼接的,有可能出现SQL注入问题。所谓SQL注入,就是通过把SQL命令插入到查询字符串,最终达到欺骗服务器执行恶意的SQL命令。接下来我们通过一段代码来演示一下SQL注入案例。
【例】登录功能SQL注入示例。

程序分析:上例中若传入的用户名为sadfsdf or 1=1,则不管用户名和密码是否正确,都能成功登录,因为1=1永远为真,和其它条件进行or操作,也永远为真,所以不管用户名和密码是否正确,都能成功登录,这就是SQL 注入问题。

7.3 PreparedStatement对象
PreparedStatement对象表示预编译的SQL语句的对象,为解决Statement静态拼接所产生的SQL注入问题,引入了PreparedStatement接口。PreparedStatement接口是Statement接口的子接口,允许使用不同的参数多次执行同样的SQL语句。Connection接口提供创建PreparedStatement对象的方法,可指定SQL语句:

PreparedStatement对象继承了Statement,但PreparedStatement语句中包含了警告预编译的SQL语句,因此可以获得更高的执行效率。虽然使用Statement可以对数据库进行操作,但它只适用于简单的SQL语句。如果需要执行带参数的SQL语句时,我们必须利用PreparedStatement类对象。PreparedStatement对象用于执行带或不带输入参数的预编译的SQL语句,语句中可以包含多个用问号?代表的字段,在程序中可以利用setXxx()方法设置该字段的内容,从而增强了程序设计的动态性。         
例如,在案例中要查询编号为1的人员信息,可用以下代码段:

接着当我们需查询编号为2的人员信息时,我们仅需以下代码:
ps.setInt(1,2);
PreparedStatement同Statement对象一样提供了很多基本的数据库操作方法,下面列出了执行SQL命令的3种方法。

【例】使用PreparedStatement解决例上面例子登录功能的SQL注入问题。

程序分析:采用以上方式,登录不能成功,解决了sql注入问题。 PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。
PreparedStatement接口setXxx()方法如表所示。

Method``SQLType``
setASCIIStream``LONGVARCHAR produced by an ASCII stream``
setBigDecimal``NUMERIC``
setBinaryStream``LONGVARBINARY``
setBoolean``BIT``
setByte``TINYINT``
setBytes``VARBINARY or LONGVARBINARY  (depending on the size relative to  the limits on VARBINARY)``
setDate``DATE``
setDouble``DOUBLE``
setFloat``FLOAT``
setInt``INTEGER``
setLong``BIGINT``
setNull``NULL``
setObject``The given object that is converted to the target SQL type  before being sent``
setShort``SMALLINT``
setString``VARCHAR or LONGVARCHAR (depending on the size  relative to the driver’s limits on VARCHAR)``
setTime``TIME``
setTimestamp``TIMESTAMP``

【例】利用PreparedStatement实现对用户表的增删改查操作。

在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。然后,每当执行同一个 PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,建议在除动态SQL命令之外的所有情况下使用PreparedStatement对象。相对于Statement,PreparedStatement的优点如下:
●可动态设置参数。
●增加了预编译功能。
●提高执行速度。