1. JDBC概述
JDBC提供了在Java程序中直接访问数据库的功能,那么,什么是JDBC?JDBC是如何工作的?本节将介绍JDBC的这些基本知识
1.1 JDBC框架介绍
JDBC(Java DataBase Connectivity) 称为Java数据库连接,它是一种用于数据库访问的应用程序API,由一组Java语言编写的类和接口组成,有了JDBC就可以用统一的语法对多种关系数据库进行访问,而不用担心其数据库操作语言的差异。
JDBC框架隔离了Java代码和具体的数据库,用户能够以相同的方式连接多种不同的数据库系统,即可以实现即使切换数据库服务器也不必变改写Java代码。如图所示
JDBC连接数据库之前必须先装载特定厂商提供的数据库驱动程序(Driver),通过JDBC的API访问数据库。有了JDBC技术,就不必为访问Mysql数据库专门写一个程序,为访问Oracle又专门写一个程序等等。
1.2 JDBC体系结构
JDBC的结构可划分为两层:一个是面向底层的JDBC Driver Interface(驱动程序管理器接口),另一个是面向程序员的JDBC核心API,如图7-2所示。
使用JDBC编程,可让开发人员从复杂的驱动器命令和函数中解脱出来,侧重于应用程序中的关键业务代码上。JDBC支持不同的关系数据库,这使得程序的可移植性大大加强。JDBC的API是面向对象的,可以让用户把常用的方法封装为—个类。但是它也有缺点,一是使用JDBC,访问数据记录的速度会受到一定程度的影响。二是JDBC结构中包含不同厂家的产品,这就给更改数据源带来了很大的麻烦,更换后台数据库可能需要修改Java代码。
1.3 JDBC驱动分类
JDBC驱动是由数据库厂商提供的,可以到各自数据库的官网指定位置上下载。根据具体的使用方法不同,目前比较常见的JDBC驱动程序可分为四类。四种类型驱动程序如图7-3所示,通常开发中多采用第四种方式,这种驱动不需要先把JDBC的调用传给ODBC或本地数据库接口或者是中间层服务器,所以它的执行效率高。各数据库厂商均提供对JDBC的支持,即提供数据库连接使用的驱动程序文件。我们只需要做的就是正确加载驱动程序文件以获得数据库连接,实施操作。
(1)JDBC-ODBC桥加上ODBC驱动程序
JDBC的API调用转换成ODBC的API调用,然后ODBC的API调用针对供应商的ODBC驱动程序来访问数据库,即利用JDBC-ODBC桥通过ODBC来操作数据源。微软提出的开放式数据库互连(Open-DataBase-Connectivity,即ODBC)成为一个强有力解决方案,并逐步成为Windows和Macintosh平台上的标准接口,并推动了这方面的开放性和标准化。本方案性能过低。
(2)本地API
这种类型的驱动程序把客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其它DBMS的调用。这种驱动方式将数据库厂商的特殊协议转换成Java代码及二进制类码,使Java数据库客户方与数据库服务器方通信。
(3)JDBC网络纯Java驱动程序
这种驱动程序将JDBC转换为与DBMS无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS协议。服务器中间件能够将纯Java客户机连接到多种不同的数据库上。数据库客户以标准网络协议(如HTTP、SHTTP)同数据库访问服务器通信,数据库访问服务器然后翻译标准网络协议成为数据库厂商的专有特殊数据库访问协议与数据库通信。
(4)本地协议纯Java驱动程序
这种类型的驱动程序将JDBC调用直接转换为DBMS所使用的网络协议。这将允许从客户机机器上直接调用DBMS服务器。这种方式也是纯Java-Driver。数据库厂商提供了特殊的JDBC协议使Java数据库客户与数据库服务器通信。这种驱动直接把JDBC调用转换为符合相关数据库系统规范的请求。
由于第四种类型驱动写的应用可以直接和数据库服务器通讯,这种类型的驱动完全由Java实现,因此实现了平台独立性。
1.4 JDBC核心接口与类
JDBC核心API都包含在java.sql包中,主要核心类有:
- DriverManager:负责管理JDBC驱动程序。使用JDBC驱动程序之前,必须先将驱动程序加载并注册后才可以使用,同时提供方法来建立与数据库的连接。
- SQLException:有关数据库操作的异常。
JDBC核心接口主要有:
- Connection:特定数据库的连接,在连接上下文中执行SQL语句并返回结果。
- Statement:用于执行静态SQL语句并返回它所生成结果的对象。
- PreparedStatement:Statement类的子类,表示预编译的SQL语句的对象,继承并扩展了Statement接口。
- CallableStatement:(了解即可)Statement类的子类,用于执行SQL存储过程的接口,继承并扩展了PreparedStatement接口。
- ResultSet:装载数据库结果集的接口,用于数据查询。
2. 创建JDBC应用
2.1 创建JDBC应用程序的步骤
(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()方法完成。
2.2 JDBC应用程序的实现
(1)载入JDBC驱动程序
在JavaAPI中的Class类提供了加载驱动程序的方法,方法格式如下:
public static Class forName(String className) throws ClassNotFoundException
参数className表示驱动类的描述符字符串,例如加载Oracle驱动的语句描述符为:
Class.forName("com.mysql.jdbc.Driver");
Mysql数据库的JDBC驱动程序文件可到Mysql官方网站下载。注意加载驱动程序的时候会抛出ClassNotFoundException,SQLException异常。
(2)定义连接URL,建立数据库连接对象
public static Connection getConnection(String url,String userName,String password) throws SQLException
MySQL数据库的url参数格式为:
jdbc:mysql://:主机名或IP:3306/<数据库名>
下面语句是建立MySQL数据库连接对象的语句:
String url = "jdbc:mysql://127.0.0.1:3306/mydatabase";
Connection conn = DriverManager.getConnection(url,"root","root");
(3)创建Statement对象
Connection接口中提供了获得Statement对象的方法,方法格式如下:
public Statement createStatement() throws SQLException
(4)执行查询或更新
获取Statement对象后,就可以通过Statement对象的executeQuery()或exeucteUpdate()方法执行查询或者更新操作,有关方法的详细解释,可以查阅JavaAPI文档。
(5)操作结果集
ResultSet接口提供对结果集进行操作的方法,主要包括:
- boolean next() throws SQLException:移动结果集操作指针。
- getXxx(String columnName) throws SQLException:根据传入列的名字获取指定列的值。
- getXxx(1) throws SQLException:根据传入列的编号获取指定列的值。
SQL类型与Java数据类型不同,下表列出了SQL类型与Java数据类型的对应关系。
SQLType | JavaType |
---|---|
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 |
(6)释放资源
数据库操作完成后,需要调用ResultSet、Statement、Connection接口中的关闭方法,释放相关资源,关闭顺序如下:
- 首先关闭结果集ResultSet对象
- 然后关闭Statement对象
- 最后关闭Connection对象。
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())。
(2)DriverManager类
DriverManager类在用户程序和数据库系统之间维护着与数据库驱动程序之间的连接。它实现驱动程序的装载、创建与数据库系统连接的Connection类对象。DriverManager类的方法主要包括:
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类的对象,查询结果封装在该对象中。
close():关闭Statement对象。
例:stmt.executeQuery("select * from users where username=‘张三’ and password=‘123’ ");
executeUpdate()方法:它用来执行更新操作,参数是一个String对象,即一个更新数据表记录的SQL语句。使用它可以对表中的记录进行修改、插入和删除等操作。例:
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=‘李四’ ”);
使用它还可以创建和删除数据表及修改数据表结构。例:
stmt.executeUpdate(“create table users(id int ,username varchar(20))”);
stmt.executeUpdate(“drop table users”);
stmt.executeUpdate(“alter table users add column usertype char(1)”);
stmt.executeUpdate(“alter table users drop column usertype”);
(5)ResultSet接口
ResultSet结果集类提供对查询结果集进行处理的方法。例:
ResultSet rs=stmt.executeQuery(" select * from users ");
ResultSet对象维持着一个指向表格的行的指针,开始时指向表格的起始位置(第一行之前)。 ResultSet类常用的方法包括:
next()方法:光标移到下一条记录,返回一个boolean值。
previous()方法:光标移到前一条记录。
getXXX()方法:获取指定类型的字段的值。调用方式 getXXX(“字段名”) 或 getXXX(int i)。i值从1开始表示结果集中第一列的字段。
close():关闭ResultSet对象。
例:
while(rs.next()){
id=rs.getInt(1);
username=rs.getString(“username”);
}
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的所有数据。
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcMysqlTest {
public static void main(String[] args) throws ClassNotFoundException,SQLException {
//1.加载jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
//2.定义连接url
String url = "jdbc:mysql://127.0.0.1:3306/neuedu";
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection(url,"root","root");
//4.获得statement对象(用来执行sql语句,并返回结果)
Statement st = conn.createStatement();
//5.执行查询或更新
String sql = "select id,name,age,birth from t_user";
ResultSet rs = st.executeQuery(sql);
//6.处理结果(遍历获取查询出来的所有数据)
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String age = rs.getString("age");
Date birth = rs.getDate("birth"); //获取日期类型数据
System.out.println(id+":"+name+":"+age+":"+birth);
}
//7.关闭连接(释放资源)
rs.close();
st.close();
conn.close();
}
}
程序运行结果如下:
7:zhangsan:age:2015-09-01
8:lisi:24:2015-09-01
9:wangwu:25:2015-09-01
10:wang:23:2015-09-01
3. PreparedStatement对象
PreparedStatement对象表示预编译的SQL语句的对象,为解决Statement静态拼接所产生的SQL注入问题,引入了PreparedStatement接口。PreparedStatement接口是Statement接口的子接口,允许使用不同的参数多次执行同样的SQL语句。Connection接口提供创建PreparedStatement对象的方法,可指定SQL语句:
PreparedStatement prepareStatement(String sql) throws SQLException
PreparedStatement对象继承了Statement,但PreparedStatement语句中包含了警告预编译的SQL语句,因此可以获得更高的执行效率。虽然使用Statement可以对数据库进行操作,但它只适用于简单的SQL语句。如果需要执行带参数的SQL语句时,我们必须利用PreparedStatement类对象。PreparedStatement对象用于执行带或不带输入参数的预编译的SQL语句,语句中可以包含多个用问号?代表的字段,在程序中可以利用setXxx()方法设置该字段的内容,从而增强了程序设计的动态性。
例如,在案例中要查询编号为1的人员信息,可用以下代码段:
ps=con.PreparedStatement("select id,name from person where id= ? ");
ps.setInt(1,1);
接着当我们需查询编号为2的人员信息时,我们仅需以下代码:
ps.setInt(1,2);
PreparedStatement同Statement对象一样提供了很多基本的数据库操作方法,下面列出了执行SQL命令的3种方法。
【例】使用PreparedStatement解决例上面例子登录功能的SQL注入问题。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SqlInject {
public static void login(String name, String PWD) throws SQLException{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = DBUtil.getConnection();
String sql = "SELECT * FROM t_user WHERE NAME=? AND PWD=?";
ps = conn.prepareStatement(sql);
//设置参数
ps.setString(1, id);
ps.setString(2, name);
rs = ps.executeQuery();
if(rs.next()){
System.out.println("登录成功..");
}else{
System.out.println("登录失败..");
}
}finally{
DBUtil.close(rs, ps, conn);
}
}
public static void main(String[] args) throws SQLException {
login("123123", "sadfsdf' or 1=1 or ''='");//解决注入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实现对用户表的增删改查操作。 |
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
public class UserDaoPreparedStatement {
//插入操作
public static void insert(String name, int age, Date birth)
throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into t_user values(seq_user.nextval,?,?,?)";
ps = conn.prepareStatement(sql);
// 设置参数,有几个?就需要设置几个参数值
ps.setString(1, name);
ps.setInt(2, age);
ps.setTimestamp(3, new Timestamp(birth.getTime()));
int result = ps.executeUpdate();
if (result > 0) {
System.out.println("insert 成功...");
} else {
System.out.println("insert 失败...");
}
} finally {
DBUtil.close(ps, conn);
}
}
public static void main(String[] args) throws SQLException {
insert("hello", 234, new Date());
}
}
在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。然后,每当执行同一个 PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,建议在除动态SQL命令之外的所有情况下使用PreparedStatement对象。相对于Statement,PreparedStatement的优点如下:
- 可动态设置参数。
- 增加了预编译功能
- 提高执行速度
4.用JDBC连接不同得数据库
(1) MySQL
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/sample";// sample为数据库名
String userName = "root";//使用能访问MySQL数据库的用户名root
String password = "mysql"; //使用口令
Connectioncon = DriverManager.getConnection(url,userName,password);
(2) SQLServer
Class.forName("Com.microsoft.jdbc.sqlserver.SQLServerDriver");
String url = "jdbc:Microsoft:sqlserver://localhost:1433//sample";//sample为数据库名
String user = "admin";
String password = "admin";
Connection con = DriverManager.getConnection(url,user,password);
(3) DB2
Class.forName("com.ibm.jdbc.app.DB2Driver");
String url="jdbc:db2://localhost:5000:sample";// sample为数据库名
String user="admin";
String password="";
Connection con = DriverManager.getConnection(url, user, password);
(4) Sybase
Class.forName("com.sybase.jdbc.SybDriver");
String url="jdbc:Sybase:Tds:localhost:5007//sample";// sample为数据库名
Properties sysProps=System.getProperties();
sysProps.put("user","userid");
sysProps.put("password","user_password");
Connection con=DriverManager.getConnection(url, sysProps);
5.事务处理
5.1 事务的概念
事务是保持数据一致性的一种数据库机制,通常大多数应用系统中,除了查询操作(SELECT)不需要对事务进行控制之外,其它数据操作(INSERT、UPDATE、DELETE)都会涉及到事务的操作,在JDBC中,事务主要是由数据库连接对象Connection的相关方法来控制,涉及到主要方法包括:
1. setAutoCommit(boolean autoCommit):将此连接的自动提交模式设置为给定状态。
2. commit():使所有上一次提交/回滚后进行的更改成为持久更改,并释放此Connection对象当前持有的所有数据库锁。
3. rollback():取消在当前事务中进行的所有更改,并释放此Connection对象当前持有的所有数据库锁。如果要取消事务中的操作,则可以使用ROLLBACK命令。执行该命令后,事务中的所有操作都被取消,数据库恢复到事务开始之前的状态,同时事务所占用的系统资源和数据库资源被释放。
如果只想取消事务中的部分操作,而不是取消全部操作,则可以在事务内部设置保存点,将一个大的事务划分为若干个组成部分,这样就可以将事务回滚到指定的保存点。
1. connection.rollback();//回滚事务。
2. SavePoint sp = connection.setSavepoint(); //设置保存点
3. connection.rollerbak(sp); //回滚到保存点
4. connection.commit();
可以使用SAVEPOINT语句设置保存点
1. setSavepoint():在当前事务中创建一个未命名的保存点 (savepoint),并返回表示它的新 Savepoint 对象。
2. setSavepoint(String name) :在当前事务中创建一个具有给定名称的保存点,并返回表示它的新Savepoint 对象。
例如,一个事务中包含3个插入操作、一个更新操作和2个保存点,语句为:
INSERT INTO departments VALUES(400,’ACCOUNTING’,100,1500);
INSERT INTO departments VALUES(410,’SALES’,120,1600);
SAVEPOINT A;
UPDATE departments SET location_id=1600 where department_id=400;
SAVEPOINT B;
INSERT INTO departments VALUES(420,’RESEARCH’,130,1700);
--在该事务提交之前,可以使用ROLLBACK命令全部或部分回滚事务中的操作。语句为:
ROLLBACK TO B; --(回滚最后一个INSERT操作)
ROLLBACK TO A; --(回滚后面的INSERT操作和UPDATE操作)
ROLLBACK; --(回滚全部操作)
上述前三个方法的使用频率特别高,通常应用系统中一个业务操作的实现步骤为:
1. 在创建好数据库连接对象Connect之后,调用setAutoCommit(false)方法,把自动提交模式设置为假,这样就避免了数据库执行自动提交,而把是否提交的主动权交到程序员手中。
2. 按照用户的操作顺序执行相应的SQL语句。
3. 在执行每一个SQL语句过程中,如果出现非预期的结果,则调用rollback()方法,执行回滚操作。
4. 所有SQL语句执行完成后,通常调用commit()方法执行提交操作,当然这时也可以执行回滚操作。
5.2 JDBC实现事务处理
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
public class UserDaoBak {
//转账的方法
public void transfer() throws Exception{
//将zhangshan的money -100 lishi的money+100
Connection conn = null;
PreparedStatement ps = null;
try{
conn = DBUtil.getConnection();
//将zhangshan的money -100
String sql1 = "update t_User set money=money-100 where id=1";
ps = conn.prepareStatement(sql1);
ps.executeUpdate();
Savepoint sp = conn.setSavepoint();
//将lishi的money +100
String sql2 = "update t_User set money=money+100 where id=2";
ps = conn.prepareStatement(sql2);
//模拟一个异常
if(1 == 1)
throw new RuntimeException("一个模拟的异常....");
ps.executeUpdate();
//提交事务
conn.commit();
System.out.println("转账成功");
}catch(Exception e){
e.print();
}finally{
DBUtil.close(ps, conn);
}
}
}
//如下为测试方法
public class Test {
public static void main(String[] args) throws Exception {
UserDaoBak userDaoBak=new UserDaoBak();
userDaoBak.transfer();
}
}
程序分析:上例中实现把张三账户的100块钱转给李四,若不出现异常,则数据库中张三的钱数为2900,李四的钱数为2100。如图所示。
假如上例中把抛出异常的语句去掉再次运行,会抛出运行时异常,而数据库中数据如图所示。
在张三转出之后,李四转入之前之间抛出个异常,则会出现张三的钱数减少,而李四的钱数却没有增加,转出的100块钱丢失了。出现了数据不一致问题,若要解决上述问题,需要用到JDBC的事务处理。
【例】使用事务解决上一例题中的数据不一致问题。
public class UserDaoBak {
//转账的方法
public void transfer() throws Exception{
//将zhangshan的money -100 lishi的money+100
Connection conn = null;
PreparedStatement ps = null;
try{
conn = DBUtils.getConnection();
//关闭conn的自动提交
conn.setAutoCommit(false);
//将zhangshan的money -100
String sql1 = "update t_User set money=money-100 where id=1";
ps = conn.prepareStatement(sql1);
ps.executeUpdate();
//Savepointsp = conn.setSavepoint();
//将lishi的money +100
String sql2 = "update t_User set money=money+100 where id=2";
ps = conn.prepareStatement(sql2);
//模拟一个异常
if(1 == 1)
throw new RuntimeException("一个模拟的异常....");
ps.executeUpdate();
//提交事务
conn.commit();
//conn.setAutoCommit(true);
System.out.println("转账成功");
}catch(Exception e){
if(conn != null){
//如果执行过程中出异常,则回滚事务
conn.rollback();
conn.setAutoCommit(true);
}
throw e;
}finally{
DBUtil.close(ps, conn);
}
}
}
程序分析:上例中,在转账之前,把事务的自动提交功能关闭,默认情况下JDBC是自动提交事务的。若抛出异常,则事务回滚,李四账号没收到钱,则张三账户的钱也不会减少。这样就不会出现数据不一致的情况。
6. MVC框架中的Dao模式
//首先定义User类,把用户表封装成实体。
import java.util.Date;
public class User {
private String id;
private String name;
private int age;
private Date birth;
public User(){
}
public User(String id, String name, int age, Date birth) {
super();
this.id = id;
this.name = name;
this.age = age;
this.birth = birth;
}
public int getAge() {
returnage;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
returnid;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
returnname;
}
public void setName(String name) {
this.name = name;
}
}
import java.lang.Exception;
import java.util.List;
public interface UserDao {
public boolean insert(User user) throws Exception;// 添加用户
public boolean update(User user) throws Exception;//更新用户信息
public boolean delete(String id) throws Exception;// 根据用户ID删除用户
public List<User> query() throws Exception;// 查询当前所有用户
public User queryById(String id)throws Exception;// 根据用户ID查询当前用户信息
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class UserDaoJdbcImpl implements UserDao{
public List<User> query() throws Exception {
List<User> users = new ArrayList<User>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = DBUtil.getConnection();
String sql = "select id,name,age,birth from t_user";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
//遍历结果集,并将数据封装到list中
User u = null;
while(rs.next()){
String id = rs.getString(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birth = rs.getDate(4);
u = new User(id,name,age,birth);
users.add(u);
}
}finally{
DBUtil.close(rs,ps, conn);
}
return users;
}
public User queryById(String id) throws Exception {
User user = null;;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = DBUtils.getConnection();
String sql = "select id,name,age,birth from t_user where id=?";
ps = conn.prepareStatement(sql);
ps.setString(1, id);
rs = ps.executeQuery();
//遍历结果集,并将数据封装到list中
if(rs.next()){
String name = rs.getString(2);
int age = rs.getInt(3);
Date birth = rs.getDate(4);
user = new User(id,name,age,birth);
}
}finally{
DBUtil.close(rs,ps, conn);
}
return user;
}
}
在测试类中,调用接口中的方法实现数据库的查询操作。
import java.util.Date;
import java.util.List;
import User;
import UserDao;
import UserDaoJdbcImpl;
public class Test {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("oop");
user.setAge(23);
user.setBirth(new Date());
UserDao dao = new UserDaoJdbcImpl();
List<User> users = dao.query();
for (User u : users) {
System.out.println(u.getId() + ":" + u.getName() + ":" + u.getAge()+ ":" + u.getBirth());
}
}
}