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
java
**/****
** * 回收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来实现:
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 z h a n g s a n zhangsan zhangsan 23 2015 年 09 月 01 日 15 : 15 : 068 2015年09月01日 15:15:06 8 2015年09月01日 15:15:068 lisi 24 24 24 2015年09月01日 15:15:23
9 w a n g w u wangwu wangwu 25 2015 年 09 月 01 日 15 : 15 : 5210 2015年09月01日 15:15:52 10 2015年09月01日 15:15:5210 hello1 500 500 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的优点如下:
●可动态设置参数。
●增加了预编译功能。
●提高执行速度。
7.4 用JDBC连接不同的数据库
了解即可,本节给出的代码因不同的数据库版本可能不一致或者失效,请随时参考官网。
上面我们讲解了使用JDBC连接Oracle数据库的方法,因为JDBC是一套数据库连接的标准,所以连接其它关系型数据库也都大同小异,两点区别在于:
1、数据库驱动路径不同
2、连接数据库的url不同
如下是各种数据库的JDBC连接方式:
(1)MySql数据库
(2) DB2数据库
(3)Sybase数据库
(4)SQLServer数据库
7.5 事务处理
本节内容仅供有余力的且掌握了Oracle数据库的同学参考使用。
7.5.1事务的概念
事务是保持数据一致性的一种数据库机制,通常大多数应用系统中,除了查询操作(SELECT)不需要对事务进行控制之外,其它数据操作(INSERT、UPDATE、DELETE)都会涉及到事务的操作,在JDBC中,事务主要是由数据库连接对象Connection的相关方法来控制,涉及到主要方法包括:
1.setAutoCommit(boolean autoCommit):将此连接的自动提交模式设置为给定状态。
2.commit():使所有上一次提交/回滚后进行的更改成为持久更改,并释放此 Connection 对象当前持有的所有数据库锁。
3.rollback():取消在当前事务中进行的所有更改,并释放此 Connection 对象当前持有的所有数据库锁。如果要取消事务中的操作,则可以使用ROLLBACK命令。执行该命令后,事务中的所有操作都被取消,数据库恢复到事务开始之前的状态,同时事务所占用的系统资源和数据库资源被释放。
如果只想取消事务中的部分操作,而不是取消全部操作,则可以在事务内部设置保存点,将一个大的事务划分为若干个组成部分,这样就可以将事务回滚到指定的保存点。
1connection.rollback();//回滚事务。
2SavePoint sp = connection.setSavepoint(); //设置保存点
3connection.rollerbak(sp); //回滚到保存点
4connection.commit();
可以使用SAVEPOINT语句设置保存点,
1.setSavepoint():在当前事务中创建一个未命名的保存点 (savepoint),并返回表示它的新 Savepoint 对象。
2.setSavepoint(String name) :在当前事务中创建一个具有给定名称的保存点,并返回表示它的新Savepoint 对象。
例如,一个事务中包含3个插入操作、一个更新操作和2个保存点,语句为:
事务回滚的过程示意图如图7-4所示。
上述前三个方法的使用频率特别高,通常应用系统中一个业务操作的实现步骤为:
1在创建好数据库连接对象Connect之后,调用setAutoCommit(false)方法,把自动提交模式设置为假,这样就避免了数据库执行自动提交,而把是否提交的主动权交到程序员手中。
2按照用户的操作顺序执行相应的SQL语句。
3在执行每一个SQL语句过程中,如果出现非预期的结果,则调用rollback()方法,执行回滚操作。
4所有SQL语句执行完成后,通常调用commit()方法执行提交操作,当然这时也可以执行回滚操作。
7.5.2 JDBC实现事务处理
接下来我们通过一个模拟转账实例来看一下JDBC中事务的操作。
程序分析:上例中实现把张三账户的100块钱转给李四,若不出现异常,则数据库中张三的钱数为2900,李四的钱数为2100。如图所示。
假如上例中把抛出异常的语句去掉再次运行,会抛出运行时异常,而数据库中数据如图所示。
在张三转出之后,李四转入之前之间抛出个异常,则会出现张三的钱数减少,而李四的钱数却没有增加,转出的100块钱丢失了。出现了数据不一致问题,若要解决上述问题,需要用到JDBC的事务处理。
【例】使用事务解决上一例题中的数据不一致问题。
7.6 在MVC思想中的DAO模式
本节内容仅供有余力的同学参考使用。
实际项目中经常使用MVC分层思想实现对数据库的操作,MVC模式中的Model(模型)是应用程序中用于处理应用程序业务逻辑的部分。View(视图)是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的。Controller(控制器)是应用程序中协调Model组件和View组件的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
使用MVC思想进行DAO设计与框架搭建代码如下:
对于数据库的每一张表,都要定义一个数据访问类,先定义访问用户表的接口UserDao。之所以定义一个数据访问的接口,是因为面向接口编程,能够起到封装和解耦合的作用,而面向具体类编程。当类中的方法改变时,调用它的类也要相应做出变更,所以面向对象的一条核心编程思想是更多的采用面向接口编程。下面是访问数据库接口的实现类。
DAO的具体实现类。
本节主要讲述了JDBC中的常用类、接口以及方法的使用,为了更好的给读者展示效果,实例代码基本都写到数据访问对象DAO中,例如本章中大量出现的UserDAO类。
在实际项目的编写过程中,通常都是按照Model-View-Control(模型层-视图层-控制层)架构模式来组织代码,通常来说,数据访问对象DAO是属于模型层的一种对象,专门用来编写对数据库中数据的增删改查操作,而对数据库连接对象以及事务的管理,通常不会放到数据访问对象DAO中来处理,否则,如果每个DAO的方法都来创建一个Connection连接对象,就会过多的占用数据连接资源,同时也就无法进行事务控制了。