框架灵魂---JDBC

来源:互联网 发布:如何注册淘宝网店步骤 编辑:程序博客网 时间:2024/05/22 11:32

目录

第一节整理目的 3

第二节jdbc的概念 3

2.1概念 3

2.2 Jdbc与应用程序的关系 3

2.3数据库的连接步骤 4

2.4 Quick Start 4

第三节如何与数据库建立连接 6

3.1注册驱动 6

3.2建立数据库的连接 8

3.3规范Quick Start中的例子 10

第四节Statement 接口的使用详解 12

4.1 Statement的常用方法 12

4.2 CRUD操作 16

4.3 Statement有那些缺点 19

第五节ResultSet接口的使用详解 20

第六节JDBC 中数据类型详解 30

6.1基本数据类型 30

6.2日期类型 34

6.3 CLOB类型 36

6.4 BLOB类型 39

6.5其他数据类型 41

第七节DAO设计模式详解 41

7.1实际项目中如何使用JDBC 41

7.2 DAO设计模式简介 42

7.3 DAO设计模式的实现 42

7.4 DAO设计模式与工厂模式的整合 49

7.5 DAO设计模式测试 52

第八节JDBC对事务的支持 52

8.1模拟转账 53

8.2 jdbc默认事务 54

8.3事务提交与回滚 54

8.4设置保存点 55

8.5 JTA事务的介绍 56

8.6数据库的隔离级别介绍 56

8.6.1未提交读 57

8.6.2提交读 58

8.6.3重复读 59

8.6.4序列化读 60

8.7小结 62

第九节PreparedStatement接口的使用 62

第十节CallableStatement接口的使用 62

9.1无参无返回值存储过程调用 63

9.2有参无返回值存储过程调用 63

9.3有参有返回值存储过程调用 64

9.4 JDBC其他API 65

第十一节元数据信息 66

11.1数据库元数据信息 66

11.2参数元数据信息 67

第十二节批处理的使用 67

12.1普通方式插入一千条数据 68

12.2批处理方式插入一千条数据 69

第十三节JDBC其他API 70

13.1可滚动结果集 70

13.2分页技术 72

13.3可更新结果集 73

第十四节编写一个简单的数据库连接池 74

14.1为什么要使用数据库连接池 74

14.2数据库连接池雏形 74

14.2数据库连接池优化 77

14.2.1对线程池加锁 77

14.2.2连接不够用时抛出异常 77

14.3数据库连接池之代理模式 78

14.3.1静态代理 78

14.3.2动态代理 84

14.4 DBCP数据库连接池的使用 87

第十五节jdbc轻量级封装 88

15.1将结果集封装为Map 88

15.1.1 ResultSetMetaData演示 88

15.1.2解决多行记录的问题 89

15.1.3 Map结果集的封装 90

15.2将结果集封装为对象 91

15.2.1 userPOJO的编写 91

15.2.2 Bean结果集的封装 92

15.3将结果集封装为List 94

15.4策略模式的应用 96

15.4.1 Map结果集策略模式应用 96

15.4.2 Bean结果集策略模式应用 97

15.4.3 List结果集策略模式应用 98

15.4.4单元测试 99

15.5模板模式的应用 100

第十六节近期推出 101

 

第一节 整理目的

当今orm等全自动针对对象持久化的框架越来越多并且也越来越成熟(ibatishibernateejbjpa),但是无奈新东家需要使用jdbc(原始手工作坊)的模式和数据库打交道,用了几年的ibatis,再次使用jdbc发现有些细节和底层的东西自己并不是十分清楚,所以就啰理啰嗦的整理出一份学习笔记,第一作为自己对jdbc重新的复习,第二如果有可能希望给初学jdbc的朋友带来一定的便利,这样也不枉我点点滴滴的记录。

随着对jdbc整理和学习的逐渐深入,发现原先使用orm框架时忽略了那么多的细节,这样在出现问题或者学习orm更加深入知识时则会显得力不从心,在本文档将jdbc如何入门阐述清楚之后,增加了如下的内容:

Ø 数据库连接池,以及常用连接池的使用(dbcpc3p0等)

Ø 编写一套基于jdbc轻量级的api,方便使用;

Ø 如何将查询结果封装为对象;

Ø 如何将查询结果封装为Map

Ø 如何将查询结果封装为List

Ø 如何在JDBC的使用中加入策略,模板等模式;

Ø 在后面的JDBC高级部分将会讲解到Dbutils源码,SpringJDBC的强大封装

第二节 jdbc的概念

2.1概念

我最不喜欢替别人整理某个名词的概念了,只要是概念性的东西基本上在任何地方都可以查得到,所以我就通俗的写一些自己对jdbc的理解,所谓jdbc就是java与数据库之间进行通讯的api,也就是一个标准,所以如果一个java应用程序想要和数据库打交道基本上都离不开jdbc,众所周知,一些优秀的orm框架的底层也是采用jdbc进行封装的。

2.2 Jdbc与应用程序的关系

JdbcAPI所处的位置和它与应用程序之间的关系,下面的一张图再也明显不过了,其中绿色的部分代表jdbcAPI,它提供了很多接口,并且本身也实现了很多方法,可以看到蓝色的部分就是各个数据库厂商自己对jdbcAPI的一些实现,这就是我们常见的数据库连接驱动,这是使用jdbc程序进行开发必不可少的东西。

 

2.3 数据库的连接步骤

1. 注册驱动 Driver

2. 建立连接(创建Connection

3. 创建执行sql语句(通常是创建Statement或者其子类)

4. 执行语句

5. 处理执行结果(在非查询语句中,该步骤是可以省略的)

6. 释放相关资源

在后文中,将会对上述几个步骤一一进行讲解,希望读者能够仔细阅读;

2.4 Quick Start

好了,了解了一下jdbc的基本概念,相比对jdbc已经有了一个感性的认识,现在我们为了直观期间,直接来上一段代码了解一下jdbc最简单的程序如何进行开发的。

在该小节中,我们以一个简单的增删改查为例进行说明,然后会将该章节中涉及的各个常用以及关键的API进行详细的讲解;

首先我们创建一个数据表,在test数据库下,见表语句为

 create table user(id integer primary key,

  name varchar(30) ,

  birthday date,

  money float);

插入两条语句

l insert into user values(2,'zhangsan','2010-01-01',15000);

l insert into user values(1,'wangwenjun','1984-06-09',8500.00);

 

好了,数据准备好了,我们通过一个完整的例子讲上述中数据库的连接步骤进行一个演示,在本例子中,初学者可能有些地方会觉得陌生,看不明白,不用着急,在后文中会对涉及的知识点逐个进行讲解

@Test

public void wholeExample(){

try {

//1.注册驱动

Class.forName("com.mysql.jdbc.Driver");

//2.获取数据库连接

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

//3.创建执行句柄

Statement stmt = conn.createStatement();

//4.执行sql语句

ResultSet rs = stmt.executeQuery("select * from user");

//5.处理执行结果

while(rs.next()){

System.out.println("id:"+rs.getInt(1)+"\tname:"+rs.getString(2)+"\tbirthday:"+rs.getDate(3)+"\tmoney:"+rs.getFloat(4));

}

//6.释放资源

rs.close();

stmt.close();

conn.close();

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

执行结果如下

id:1 name:wangwenjun birthday:1984-06-09 money:8500.0

id:2 name:zhangsan birthday:2010-01-01 money:15000.0

 

第三节  如何与数据库建立连接

3.1 注册驱动

l 第一种注册方式

通常来说,注册驱动的方式有三种,下面我们将一一进行介绍,首先来看看直接调用DriverManagerregisterDriver方法进行加载驱动,在本文中所有的程序均是在mysql数据库上进行演示的。

示例代码如下

@Test

public void registDriver1(){

try {

DriverManager.registerDriver(new com.mysql.jdbc.Driver());

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

}

}

执行结果为

可以看到,当前我们的程序与数据库的连接是正常的。

l 第二种注册方式

@Test

public void registDriver2(){

try {

System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

}

}

执行结果为

可以看到,当前我们的程序与数据库的连接是正常的。

 

l 第三种注册方式

@Test

public void registDriver3(){

try {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

执行结果为

可以看到,当前我们的程序与数据库的连接是正常的。

 

一般来说注册驱动的方式大致上有上述三个,但是最常用的是最后一个,通过我们的代码演示可以看出,数据库都是完全可以被访问成功的。

3.2 建立数据库的连接

其实在上文中的代码演示中,我们都会看到如何获取一个数据库连接,就是通过DriverManager.getConnection()方法获取数据库的链接,该方法大致有三个重载的方法,都是可以进行数据库连接的获取的,下面我们将会一一进行演示

Static Connection

getConnection(String url)
试图建立到给定数据库 URL 的连接。

static Connection

getConnection(String url,Properties info)
试图建立到给定数据库 URL 的连接。

static Connection

getConnection(String url,String user,String password) 试图建立到给定数据库 URL 的连接。

getConnection(String url)

该实例中,登录数据库的所有信息都编写在url中,实例代码如下

@Test

public void getConn1(){

try {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?user=root&password=r66t");

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

 

getConnection(String url,Properties info)

该方法则是将用户名和密码的信息存放在一个Properties键值对中,示例代码如下

@Test

public void getConn2(){

try {

Class.forName("com.mysql.jdbc.Driver");

Properties props = new Properties();

props.put("user", "root");

props.put("password", "r66t");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test",props);

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

 

getConnection(String url,String user,String password)

该方法则是我们在上文中演示了很多次的方式,也是最常用的一种方式,在这里再次进行一下赘述

@Test

public void getConn3(){

try {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

Assert.assertEquals(false, conn.isClosed());

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

 

3.3 规范Quick Start中的例子

其中,我们在Quick Start中写了一个较为完整的代码示例,但是在该代码中存在很多的问题,我们通过本节的介绍,一一进行规范和优化,并且说明一下优化的好处是什么

使用数据库时,涉及数据库的资源都是非常奇缺的,我们在使用的过程中务必保证我们将使用过的资源释放,供别人再次使用或者自己下次再次使用,还有,创建数据库连接时可能存在各种各样的问题导致数据库连接获取失败,这个时候你的应用应该有义务告知上一层使用者到底出现了什么问题,这样就需要一个异常传递的过程(异常是一个比较复杂的机制,笔者在另一篇文章中有详细的讲解,希望读者能够关注)

@Test

public void regularWhole()throws Exception{//抛出异常

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

//1.注册驱动

Class.forName("com.mysql.jdbc.Driver");

//2.获取数据库连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","r66t");

//3.创建执行句柄

stmt = conn.createStatement();

//4.执行sql语句

rs = stmt.executeQuery("select * from user");

//5.处理执行结果

while(rs.next()){

System.out.println("id:"+rs.getInt(1)+"\tname:"+rs.getString(2)+"\tbirthday:"+rs.getDate(3)+"\tmoney:"+rs.getFloat(4));

}

} finally{

try {

//6.释放资源

if(null!=rs){

rs.close();

}

if(null!=stmt){

stmt.close();

}

if(null!=conn){

conn.close();

}

} finally{

if(null!=rs){

rs.close();

}

if(null!=stmt){

stmt.close();

}

if(null!=conn){

conn.close();

}

}

}

}

在该实例中,可以看出确保了资源的完全释放,也将异常抛出告知上一层使用者,那块出现了问题,但是可以看到代码明显写的很罗嗦,而且有很多地方还是值得考究的

其中,注册驱动,数据库的驱动注册,只需要一次即可,重复注册是没有任何意义的,并且资源的释放,在每次使用的时候都进行资源的释放(写资源释放的代码)显得非常罗嗦,所以我们进行再一次的一个优化,代码如下

package com.wangwenjun.jdbc;

 

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

 

public final class ConnCreate {

 

static {

try {

Class.forName("com.mysql.jdbc.Driver");

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

 

public static Connection getConnection(String url, String user, String pwd) {

Connection conn = null;

try {

conn = DriverManager.getConnection(url, user, pwd);

} catch (SQLException e) {

e.printStackTrace();

}

return conn;

}

 

public static void close(Connection conn, Statement stmt, ResultSet rs)

throws SQLException {

if (null != rs) {

rs.close();

}

if (null != stmt) {

stmt.close();

}

if (null != conn) {

conn.close();

}

}

}

可以看出,驱动只会被注册一次,并且对资源释放的代码进行了抽取,在以后的使用过程中则会简单许多,当然上述的代码如果还需要追究问题,肯定还是存在的,在接下来的章节中我们也会进行深入的说明。

 

第四节 Statement 接口的使用详解

Statement 是应用与数据库打交道最关键的一个接口,该接口包括了我们常用的CRUD操作,还可以设置抓取策略,比如设置数据库的游标是多少,可以根据数据量进行调优,也可以进行批量处理等,总之,该接口是非常关键的一个接口,包括后文中的预处理命令接口以及执行存储过程的接口。

4.1 Statement 的常用方法

下面的列表是从jdk API文档上粘贴出来的,当然很多方法我们并不是都能碰到,但是了解一下还是会有好处的

方法摘要

 void

addBatch(String sql)
          将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。

 void

cancel()
          如果 DBMS 和驱动程序都支持中止 SQL 语句,则取消此 Statement 对象。

 void

clearBatch()
          清空此 Statement 对象的当前 SQL 命令列表。

 void

clearWarnings()
          清除在此 Statement 对象上报告的所有警告。

 void

close()
          立即释放此 Statement 对象的数据库和 JDBC 资源,而不是等待该对象自动关闭时发生此操作。

 boolean

execute(String sql)
          执行给定的 SQL 语句,该语句可能返回多个结果。

 boolean

execute(String sql, int autoGeneratedKeys)
          执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序所有自动生成的键都应该可用于检索。

 boolean

execute(String sql, int[] columnIndexes)
          执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序在给定数组中指示的自动生成的键应该可用于检索。

 boolean

execute(String sql,String[] columnNames)
          执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序在给定数组中指示的自动生成的键应该可用于检索。

 int[]

executeBatch()
          将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。

 ResultSet

executeQuery(String sql)
          执行给定的 SQL 语句,该语句返回单个 ResultSet 对象。

 int

executeUpdate(String sql)
          执行给定 SQL 语句,该语句可能为 INSERT、UPDATE 或 DELETE 语句,或者不返回任何内容的 SQL 语句(如 SQL DDL 语句)。

 int

executeUpdate(String sql, int autoGeneratedKeys)
          执行给定的 SQL 语句,并用给定标志通知驱动程序由此 Statement 生成的自动生成键是否可用于检索。

 int

executeUpdate(String sql, int[] columnIndexes)
          执行给定的 SQL 语句,并通知驱动程序在给定数组中指示的自动生成的键应该可用于检索。

 int

executeUpdate(String sql,String[] columnNames)
          执行给定的 SQL 语句,并通知驱动程序在给定数组中指示的自动生成的键应该可用于检索。

 Connection

getConnection()
          检索生成此 Statement 对象的 Connection 对象。

 int

getFetchDirection()
          检索从数据库表获取行的方向,该方向是根据此 Statement 对象生成的结果集合的默认值。

 int

getFetchSize()
          检索结果集合的行数,该数是根据此 Statement 对象生成的 ResultSet 对象的默认获取大小。

 ResultSet

getGeneratedKeys()
          检索由于执行此 Statement 对象而创建的所有自动生成的键。

 int

getMaxFieldSize()
          检索可以为此 Statement 对象所生成 ResultSet 对象中的字符和二进制列值返回的最大字节数。

 int

getMaxRows()
          检索由此 Statement 对象生成的 ResultSet 对象可以包含的最大行数。

 boolean

getMoreResults()
          移动到此 Statement 对象的下一个结果,如果其为 ResultSet 对象,则返回 true,并隐式关闭利用方法 getResultSet 获取的所有当前 ResultSet 对象。

 boolean

getMoreResults(int current)
          将此 Statement 对象移动到下一个结果,根据给定标志指定的指令处理所有当前 ResultSet 对象;如果下一个结果为 ResultSet 对象,则返回 true。

 int

getQueryTimeout()
          检索驱动程序等待 Statement 对象执行的秒数。

 ResultSet

getResultSet()
          以 ResultSet 对象的形式检索当前结果。

 int

getResultSetConcurrency()
          检索此 Statement 对象生成的 ResultSet 对象的结果集合并发性。

 int

getResultSetHoldability()
          检索此 Statement 对象生成的 ResultSet 对象的结果集合可保存性。

 int

getResultSetType()
          检索此 Statement 对象生成的 ResultSet 对象的结果集合类型。

 int

getUpdateCount()
          以更新计数的形式检索当前结果;如果结果为 ResultSet 对象或没有更多结果,则返回 -1。

 SQLWarning

getWarnings()
          检索此 Statement 对象上的调用报告的第一个警告。

 void

setCursorName(String name)
          将 SQL 指针名称设置为给定的 String,后续 Statement 对象的 execute 方法将使用此字符串。

 void

setEscapeProcessing(boolean enable)
          将转义处理设置为开或关。

 void

setFetchDirection(int direction)
          向驱动程序提供关于方向的提示,在使用此 Statement 对象创建的 ResultSet 对象中将按该方向处理行。

 void

setFetchSize(int rows)
          为 JDBC 驱动程序提供关于需要更多行时应该从数据库获取的行数的提示。

 void

setMaxFieldSize(int max)
          设置将字符或二进制值存储到给定字节数中 ResultSet 列中的最大字节数限制。

 void

setMaxRows(int max)
          将任何 ResultSet 对象都可以包含的最大行数限制设置为给定数。

 void

setQueryTimeout(int seconds)
          将驱动程序等待 Statement 对象执行的秒数设置为给定秒数。

其中API 上特别注明了一句话,是非常关键的,我们在使用的时候一定要注意,否则会出现很严重的问题

在默认情况下,同一时间每个 Statement 对象在只能打开一个 ResultSet 对象。因此,如果读取一个 ResultSet 对象与读取另一个交叉,则这两个对象必须是由不同的 Statement 对象生成的。如果存在某个语句的打开的当前 ResultSet 对象,则 Statement 接口中的所有执行方法都会隐式关闭它。

其实从Statement的原理来说,底层他还是从过游标的方式操作数据,尤其是进行查询的时候,并且还是显式游标,如果对其不能进行及时的资源释放,当运行到一定时间,数据库则会抛出异常给应用(打开的游标超过了最大值)

4.2 CRUD操作

我们还是通过上述创建的user表进行一下增删改查的操作,来看看通过Statement怎样进行数据的操作。

l 新增数据

现在想到数据库中新增一条数据,编号为3,名称为lisi,生日为2010-05-05money13000.00,代码示例如下

@Test

public void insert()throws SQLException{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 1.注册驱动

//Class.forName("com.mysql.jdbc.Driver");

// 2.获取数据库连接

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test","root", "r66t");

// 3.创建执行句柄

stmt = conn.createStatement();

// 4.执行sql语句

String sql="insert into user(id,name,birthday,money) values(3,'lisi','2010-05-05',13000.00)";

stmt.executeUpdate(sql);

} finally {

ConnCreate.close(conn,stmt,rs);

}

}

执行结果对比为(下图为,sql执行前后的查询展示)

 

l 修改数据

物价上涨,待遇一直不涨,所以我们将lisimoney给再加上5000元,当然这只是一个演示,意思就是说更新一下数据表中的数据,如果真的可以这么做,大家可以随时Q我,给增加工资,呵呵,好了,直接上演示代码

@Test

public void update()throws SQLException{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 1.注册驱动

//Class.forName("com.mysql.jdbc.Driver");

// 2.获取数据库连接

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test","root", "r66t");

// 3.创建执行句柄

stmt = conn.createStatement();

// 4.执行sql语句

String sql="update user set money=money+5000 where id=3";

stmt.executeUpdate(sql);

} finally {

ConnCreate.close(conn,stmt,rs);

}

}

执行结果为

 

可以看出,lisimoney字段被更新了

l 删除数据

刚才将lisimoney给上调了,结果被领导发现了,现在要求将lisi给开除了,所以我们只好进行删除操作了(所以说敏感数据不能随便修改哒),示例代码如下

@Test

public void delete()throws SQLException{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 1.注册驱动

//Class.forName("com.mysql.jdbc.Driver");

// 2.获取数据库连接

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test","root", "r66t");

// 3.创建执行句柄

stmt = conn.createStatement();

// 4.执行sql语句

String sql="delete from user where id=3";

stmt.executeUpdate(sql);

} finally {

ConnCreate.close(conn,stmt,rs);

}

}

执行结果如下图所示

 

从上图可以看出,lisi已经被删除掉了。

l 查询数据

前面的章节中,关于查询的示例实在太多了,此处省略。

4.3 Statement有那些缺点

应用执行sql其实大体上可以分为两步,第一步是将sql交给数据库应用检查编译,然后再由数据库将执行结果返回给应用。好了我们看一个实例,来感受一下Statement会有哪些缺点

@Test

public void conditionQuery()throws SQLException{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 1.注册驱动

//Class.forName("com.mysql.jdbc.Driver");

// 2.获取数据库连接

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test","root", "r66t");

// 3.创建执行句柄

stmt = conn.createStatement();

// 4.执行sql语句

String sql="select * from user where name='wangwenjun' or 1 or ''";

System.out.println(sql);

rs = stmt.executeQuery(sql);

// 5.处理执行结果

while (rs.next()) {

System.out.println("id:" + rs.getInt(1) +"\tname:"

+ rs.getString(2) + "\tbirthday:" + rs.getDate(3)

+ "\tmoney:" + rs.getFloat(4));

}

} finally {

ConnCreate.close(conn,stmt,rs);

}

}

请看一下我们的代码,其中name的值很诡异,假设,这个值是由前台用户传递进来的,我们就不能保证他到底是什么,当然我们也可以通过编写过滤器来进行规避,但是我们怎么样通过jdbc来规避上述的问题呢,首先看看运行结果吧

select * from user where name='wangwenjun' or 1 or ''

id:1 name:wangwenjun birthday:1984-06-09 money:8500.0

id:2 name:zhangsan birthday:2010-01-01 money:15000.0

id:3 name:lisi birthday:2010-05-05 money:18000.0

可以看到将所有的语句都查询出来了。

简单总结一下Statement的缺点哈(纯属个人观点,可能有些不太全面)

执行时发送sql,影响效率.

同样的sql,每次都要发送,不能进行有效的缓存,是一种资源的浪费.

示例代码中演示可以看出,为了防止恶意数据我们还需要编写附加的程序(过滤器)带来不必要的开支.

拼接sql字符串很容易出现错误.

为了解决上述的问题,我们引入一个新的接口PreparedStatement,该接口是Statement的子接口,他们的主要区别是,在执行sql之前首先准备好sql语句,将其中的条件通过?进行占位,还有一个好处就是,同样的sql会被PreparedStatement有效的缓存,也就是说,数据库会减少校验的过程,减少消耗,这就是我们常说的预处理命令方式,在后文中也会涉及到。

第五节 ResultSet接口的使用详解

从字面意思上来了解,该接口是一个数据集合,是从数据库中获取的数据会存放在该集合当中,该集合提供了很多种数据获取的方法,详细信息,请看下表展示

方法摘要

 boolean

absolute(int row)
          将指针移动到此 ResultSet 对象的给定行编号。

 void

afterLast()
          将指针移动到此 ResultSet 对象的末尾,正好位于最后一行之后。

 void

beforeFirst()
          将指针移动到此 ResultSet 对象的开头,正好位于第一行之前。

 void

cancelRowUpdates()
          取消对 ResultSet 对象中的当前行所作的更新。

 void

clearWarnings()
          清除在此 ResultSet 对象上报告的所有警告。

 void

close()
          立即释放此 ResultSet 对象的数据库和 JDBC 资源,而不是等待该对象自动关闭时发生此操作。

 void

deleteRow()
          从此 ResultSet 对象和底层数据库中删除当前行。

 int

findColumn(String columnName)
          将给定的 ResultSet 列名称映射到其 ResultSet 列索引。

 boolean

first()
          将指针移动到此 ResultSet 对象的第一行。

 Array

getArray(int i)
          以 Java 编程语言中 Array 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Array

getArray(String colName)
          以 Java 编程语言中 Array 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 InputStream

getAsciiStream(int columnIndex)
          以 ASCII 字符流的形式检索此 ResultSet 对象的当前行中指定列的值。

 InputStream

getAsciiStream(String columnName)
          以 ASCII 字符流的形式检索此 ResultSet 对象的当前行中指定列的值。

 BigDecimal

getBigDecimal(int columnIndex)
          以具有全精度的 java.math.BigDecimal 的形式检索此 ResultSet 对象的当前行中指定列的值。

 BigDecimal

getBigDecimal(int columnIndex, int scale)
          已过时。  

 BigDecimal

getBigDecimal(String columnName)
          以具有全精度的 java.math.BigDecimal 的形式检索此 ResultSet 对象的当前行中指定列的值。

 BigDecimal

getBigDecimal(String columnName, int scale)
          已过时。  

 InputStream

getBinaryStream(int columnIndex)
          以未解释字节的二进制流的形式检索此 ResultSet 对象的当前行中指定列的值。

 InputStream

getBinaryStream(String columnName)
          以未解释的 byte 流的形式检索此 ResultSet 对象的当前行中指定列的值。

 Blob

getBlob(int i)
          以 Java 编程语言中 Blob 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Blob

getBlob(String colName)
          以 Java 编程语言中 Blob 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 boolean

getBoolean(int columnIndex)
          以 Java 编程语言中 boolean 的形式检索此 ResultSet 对象的当前行中指定列的值。

 boolean

getBoolean(String columnName)
          以 Java 编程语言中 boolean 的形式检索此 ResultSet 对象的当前行中指定列的值。

 byte

getByte(int columnIndex)
          以 Java 编程语言中 byte 的形式检索此 ResultSet 对象的当前行中指定列的值。

 byte

getByte(String columnName)
          以 Java 编程语言中 byte 的形式检索此 ResultSet 对象的当前行中指定列的值。

 byte[]

getBytes(int columnIndex)
          以 Java 编程语言中 byte 数组的形式检索此 ResultSet 对象的当前行中指定列的值。

 byte[]

getBytes(String columnName)
          以 Java 编程语言中 byte 数组的形式检索此 ResultSet 对象的当前行中指定列的值。

 Reader

getCharacterStream(int columnIndex)
          以 java.io.Reader 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Reader

getCharacterStream(String columnName)
          以 java.io.Reader 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Clob

getClob(int i)
          以 Java 编程语言中 Clob 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Clob

getClob(String colName)
          以 Java 编程语言中 Clob 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getConcurrency()
          检索此 ResultSet 对象的并发模式。

 String

getCursorName()
          检索此 ResultSet 对象使用的 SQL 指针的名称。

 Date

getDate(int columnIndex)
          以 Java 编程语言中 java.sql.Date 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Date

getDate(int columnIndex,Calendar cal)
          以 Java 编程语言中 java.sql.Date 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Date

getDate(String columnName)
          以 Java 编程语言中的 java.sql.Date 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Date

getDate(String columnName,Calendar cal)
          以 Java 编程语言中 java.sql.Date 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 double

getDouble(int columnIndex)
          以 Java 编程语言中 double 的形式检索此 ResultSet 对象的当前行中指定列的值。

 double

getDouble(String columnName)
          以 Java 编程语言中 double 的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getFetchDirection()
          检索此 ResultSet 对象的获取方向。

 int

getFetchSize()
          检索此 ResultSet 对象的获取大小。

 float

getFloat(int columnIndex)
          以 Java 编程语言中 float 的形式检索此 ResultSet 对象的当前行中指定列的值。

 float

getFloat(String columnName)
          以 Java 编程语言中 float 的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getInt(int columnIndex)
          以 Java 编程语言中 int 的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getInt(String columnName)
          以 Java 编程语言中 int 的形式检索此 ResultSet 对象的当前行中指定列的值。

 long

getLong(int columnIndex)
          以 Java 编程语言中 long 的形式检索此 ResultSet 对象的当前行中指定列的值。

 long

getLong(String columnName)
          以 Java 编程语言中 long 的形式检索此 ResultSet 对象的当前行中指定列的值。

 ResultSetMetaData

getMetaData()
          检索此 ResultSet 对象的列的编号、类型和属性。

 Object

getObject(int columnIndex)
          以 Java 编程语言中 Object 的形式获取此 ResultSet 对象的当前行中指定列的值。

 Object

getObject(int i, Map<String,Class<?>> map)
          以 Java 编程语言中 Object 的形式检索此 ResultSet 对象的当前行中指定列的值。

 Object

getObject(String columnName)
          以 Java 编程语言中 Object 的形式获取此 ResultSet 对象的当前行中指定列的值。

 Object

getObject(String colName,Map<String,Class<?>> map)
          以 Java 编程语言中 Object 的形式检索此 ResultSet 对象的当前行中指定列的值。

 Ref

getRef(int i)
          以 Java 编程语言中 Ref 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Ref

getRef(String colName)
          以 Java 编程语言中 Ref 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getRow()
          检索当前行编号。

 short

getShort(int columnIndex)
          以 Java 编程语言中 short 的形式检索此 ResultSet 对象的当前行中指定列的值。

 short

getShort(String columnName)
          以 Java 编程语言中 short 的形式检索此 ResultSet 对象的当前行中指定列的值。

 Statement

getStatement()
          检索生成此 ResultSet 对象的 Statement 对象。

 String

getString(int columnIndex)
          以 Java 编程语言中 String 的形式检索此 ResultSet 对象的当前行中指定列的值。

 String

getString(String columnName)
          以 Java 编程语言中 String 的形式检索此 ResultSet 对象的当前行中指定列的值。

 Time

getTime(int columnIndex)
          以 Java 编程语言中 java.sql.Time 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Time

getTime(int columnIndex,Calendar cal)
          以 Java 编程语言中 java.sql.Time 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Time

getTime(String columnName)
          以 Java 编程语言中 java.sql.Time 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Time

getTime(String columnName,Calendar cal)
          以 Java 编程语言中 java.sql.Time 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Timestamp

getTimestamp(int columnIndex)
          以 Java 编程语言中 java.sql.Timestamp 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Timestamp

getTimestamp(int columnIndex,Calendar cal)
          以 Java 编程语言中 java.sql.Timestamp 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Timestamp

getTimestamp(String columnName)
          以 java.sql.Timestamp 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 Timestamp

getTimestamp(String columnName,Calendar cal)
          以 Java 编程语言中 java.sql.Timestamp 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 int

getType()
          检索此 ResultSet 对象的类型。

 InputStream

getUnicodeStream(int columnIndex)
          已过时。 使用 getCharacterStream 取代 getUnicodeStream

 InputStream

getUnicodeStream(String columnName)
          已过时。 使用 getCharacterStream 代替

 URL

getURL(int columnIndex)
          以 Java 编程语言中 java.net.URL 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 URL

getURL(String columnName)
          以 Java 编程语言中 java.net.URL 对象的形式检索此 ResultSet 对象的当前行中指定列的值。

 SQLWarning

getWarnings()
          检索此 ResultSet 对象上的调用报告的第一个警告。

 void

insertRow()
          将插入行的内容插入到此 ResultSet 对象和数据库中。

 boolean

isAfterLast()
          检索指针是否位于此 ResultSet 对象的最后一行之后。

 boolean

isBeforeFirst()
          检索指针是否位于此 ResultSet 对象的第一行之前。

 boolean

isFirst()
          检索指针是否位于此 ResultSet 对象的第一行。

 boolean

isLast()
          检索指针是否位于此 ResultSet 对象的最后一行。

 boolean

last()
          将指针移动到此 ResultSet 对象的最后一行。

 void

moveToCurrentRow()
          将指针移动到记住的指针位置,通常为当前行。

 void

moveToInsertRow()
          将指针移动到插入行。

 boolean

next()
          将指针从当前位置下移一行。

 boolean

previous()
          将指针移动到此 ResultSet 对象的上一行。

 void

refreshRow()
          用数据库中的最近值刷新当前行。

 boolean

relative(int rows)
          按相对行数(或正或负)移动指针。

 boolean

rowDeleted()
          检索是否已删除某行。

 boolean

rowInserted()
          检索当前行是否已有插入。

 boolean

rowUpdated()
          检索是否已更新当前行。

 void

setFetchDirection(int direction)
          设置此 ResultSet 对象中行的处理方向。

 void

setFetchSize(int rows)
          为 JDBC 驱动程序设置此 ResultSet 对象需要更多行时应该从数据库获取的行数。

 void

updateArray(int columnIndex,Array x)
          用 java.sql.Array 值更新指定列。

 void

updateArray(String columnName,Array x)
          用 java.sql.Array 值更新指定列。

 void

updateAsciiStream(int columnIndex,InputStream x, int length)
          用 ascii 流值更新指定列。

 void

updateAsciiStream(String columnName,InputStream x, int length)
          用 ascii 流值更新指定列。

 void

updateBigDecimal(int columnIndex,BigDecimal x)
          用 java.math.BigDecimal 值更新指定列。

 void

updateBigDecimal(String columnName,BigDecimal x)
          用 java.sql.BigDecimal 值更新指定列。

 void

updateBinaryStream(int columnIndex,InputStream x, int length)
          用二进制流值更新指定列。

 void

updateBinaryStream(String columnName,InputStream x, int length)
          用二进制流值更新指定列。

 void

updateBlob(int columnIndex,Blob x)
          用 java.sql.Blob 值更新指定列。

 void

updateBlob(String columnName,Blob x)
          用 java.sql.Blob 值更新指定列。

 void

updateBoolean(int columnIndex, boolean x)
          用 boolean 值更新指定列。

 void

updateBoolean(String columnName, boolean x)
          用 boolean 值更新指定列。

 void

updateByte(int columnIndex, byte x)
          用 byte 值更新指定列。

 void

updateByte(String columnName, byte x)
          用 byte 值更新指定列。

 void

updateBytes(int columnIndex, byte[] x)
          用 byte 数组值更新指定列。

 void

updateBytes(String columnName, byte[] x)
          用字节数组值更新指定列。

 void

updateCharacterStream(int columnIndex,Reader x, int length)
          用字符流值更新指定列。

 void

updateCharacterStream(String columnName,Reader reader, int length)
          用字符流值更新指定列。

 void

updateClob(int columnIndex,Clob x)
          用 java.sql.Clob 值更新指定列。

 void

updateClob(String columnName,Clob x)
          用 java.sql.Clob 值更新指定列。

 void

updateDate(int columnIndex,Date x)
          用 java.sql.Date 值更新指定列。

 void

updateDate(String columnName,Date x)
          用 java.sql.Date 值更新指定列。

 void

updateDouble(int columnIndex, double x)
          用 double 值更新指定列。

 void

updateDouble(String columnName, double x)
          用 double 值更新指定列。

 void

updateFloat(int columnIndex, float x)
          用 float 值更新指定列。

 void

updateFloat(String columnName, float x)
          用 float 值更新指定列。

 void

updateInt(int columnIndex, int x)
          用 int 值更新指定列。

 void

updateInt(String columnName, int x)
          用 int 值更新指定列。

 void

updateLong(int columnIndex, long x)
          用 long 值更新指定列。

 void

updateLong(String columnName, long x)
          用 long 值更新指定列。

 void

updateNull(int columnIndex)
          为可以为 null 的列提供 null 值。

 void

updateNull(String columnName)
          用 null 值更新指定列。

 void

updateObject(int columnIndex,Object x)
          用 Object 值更新指定列。

 void

updateObject(int columnIndex,Object x, int scale)
          用 Object 值更新指定列。

 void

updateObject(String columnName,Object x)
          用 Object 值更新指定列。

 void

updateObject(String columnName,Object x, int scale)
          用 Object 值更新指定列。

 void

updateRef(int columnIndex,Ref x)
          用 java.sql.Ref 值更新指定列。

 void

updateRef(String columnName,Ref x)
          用 java.sql.Ref 值更新指定列。

 void

updateRow()
          用此 ResultSet 对象的当前行的新内容更新底层数据库。

 void

updateShort(int columnIndex, short x)
          用 short 值更新指定列。

 void

updateShort(String columnName, short x)
          用 short 值更新指定列。

 void

updateString(int columnIndex,String x)
          用 String 值更新指定列。

 void

updateString(String columnName,String x)
          用 String 值更新指定列。

 void

updateTime(int columnIndex,Time x)
          用 java.sql.Time 值更新指定列。

 void

updateTime(String columnName,Time x)
          用 java.sql.Time 值更新指定列。

 void

updateTimestamp(int columnIndex,Timestamp x)
          用 java.sql.Timestamp 值更新指定列。

 void

updateTimestamp(String columnName,Timestamp x)
          用 java.sql.Timestamp 值更新指定列。

 boolean

wasNull()
          报告最后一个读取的列是否具有值 SQL NULL。

有兴趣的读者可以逐个方法进厅junit测试,接口封装的很强大,所以使用起来很方便,几乎没有什么看不懂的地方,就不做其他罗嗦的讲述了。

第六节 JDBC 中数据类型详解

Jdbc中提供了我们能见到的所有数据类型,其中想Stringint等,看一下PreparedStatement源码就可以查看得到,这里就不在讲述

6.1 基本数据类型

 void

addBatch()
          将一组参数添加到此 PreparedStatement 对象的批处理命令中。

 void

clearParameters()
          立即清除当前参数值。

 boolean

execute()
          在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句。

 ResultSet

executeQuery()
          在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。

 int

executeUpdate()
          在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。

 ResultSetMetaData

getMetaData()
          获取包含有关 ResultSet 对象列信息的 ResultSetMetaData 对象,ResultSet 对象将在执行此 PreparedStatement 对象时返回。

 ParameterMetaData

getParameterMetaData()
          获取此 PreparedStatement 对象的参数的编号、类型和属性。

 void

setArray(int parameterIndex,Array x)
          将指定参数设置为给定 java.sql.Array 对象。

 void

setAsciiStream(int parameterIndex,InputStream x)
          将指定参数设置为给定输入流。

 void

setAsciiStream(int parameterIndex,InputStream x, int length)
          将指定参数设置为给定输入流,该输入流将具有给定字节数。

 void

setAsciiStream(int parameterIndex,InputStream x, long length)
          将指定参数设置为给定输入流,该输入流将具有指定字节数。

 void

setBigDecimal(int parameterIndex,BigDecimal x)
          将指定参数设置为给定 java.math.BigDecimal 值。

 void

setBinaryStream(int parameterIndex,InputStream x)
          将指定参数设置为给定输入流。

 void

setBinaryStream(int parameterIndex,InputStream x, int length)
          将指定参数设置为给定输入流,该输入流将具有给定字节数。

 void

setBinaryStream(int parameterIndex,InputStream x, long length)
          将指定参数设置为给定输入流,该输入流将具有指定字节数。

 void

setBlob(int parameterIndex,Blob x)
          将指定参数设置为给定 java.sql.Blob 对象。

 void

setBlob(int parameterIndex,InputStream inputStream)
          将指定参数设置为 InputStream 对象。

 void

setBlob(int parameterIndex,InputStream inputStream, long length)
          将指定参数设置为 InputStream 对象。

 void

setBoolean(int parameterIndex, boolean x)
          将指定参数设置为给定 Java boolean 值。

 void

setByte(int parameterIndex, byte x)
          将指定参数设置为给定 Java byte 值。

 void

setBytes(int parameterIndex, byte[] x)
          将指定参数设置为给定 Java byte 数组。

 void

setCharacterStream(int parameterIndex,Reader reader)
          将指定参数设置为给定 Reader 对象。

 void

setCharacterStream(int parameterIndex,Reader reader, int length)
          将给定参数设置为给定 Reader 对象,该对象具有给定字符数长度。

 void

setCharacterStream(int parameterIndex,Reader reader, long length)
          将指定参数设置为给定 Reader 对象,该对象具有给定字符数长度。

 void

setClob(int parameterIndex,Clob x)
          将指定参数设置为给定 java.sql.Clob 对象。

 void

setClob(int parameterIndex,Reader reader)
          将指定参数设置为 Reader 对象。

 void

setClob(int parameterIndex,Reader reader, long length)
          将指定参数设置为 Reader 对象。

 void

setDate(int parameterIndex,Date x)
          使用运行应用程序的虚拟机的默认时区将指定参数设置为给定 java.sql.Date 值。

 void

setDate(int parameterIndex,Date x, Calendar cal)
          使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Date 值。

 void

setDouble(int parameterIndex, double x)
          将指定参数设置为给定 Java double 值。

 void

setFloat(int parameterIndex, float x)
          将指定参数设置为给定 Java REAL 值。

 void

setInt(int parameterIndex, int x)
          将指定参数设置为给定 Java int 值。

 void

setLong(int parameterIndex, long x)
          将指定参数设置为给定 Java long 值。

 void

setNCharacterStream(int parameterIndex,Reader value)
          将指定参数设置为 Reader 对象。

 void

setNCharacterStream(int parameterIndex,Reader value, long length)
          将指定参数设置为 Reader 对象。

 void

setNClob(int parameterIndex,NClob value)
          将指定参数设置为 java.sql.NClob 对象。

 void

setNClob(int parameterIndex,Reader reader)
          将指定参数设置为 Reader 对象。

 void

setNClob(int parameterIndex,Reader reader, long length)
          将指定参数设置为 Reader 对象。

 void

setNString(int parameterIndex,String value)
          将指定参数设置为给定 String 对象。

 void

setNull(int parameterIndex, int sqlType)
          将指定参数设置为 SQL NULL。

 void

setNull(int parameterIndex, int sqlType,String typeName)
          将指定参数设置为 SQL NULL。

 void

setObject(int parameterIndex,Object x)
          使用给定对象设置指定参数的值。

 void

setObject(int parameterIndex,Object x, int targetSqlType)
          使用给定对象设置指定参数的值。

 void

setObject(int parameterIndex,Object x, int targetSqlType, int scaleOrLength)
          使用给定对象设置指定参数的值。

 void

setRef(int parameterIndex,Ref x)
          将指定参数设置为给定 REF(<structured-type>) 值。

 void

setRowId(int parameterIndex,RowId x)
          将指定参数设置为给定 java.sql.RowId 对象。

 void

setShort(int parameterIndex, short x)
          将指定参数设置为给定 Java short 值。

 void

setSQLXML(int parameterIndex,SQLXML xmlObject)
          将指定参数设置为给定 java.sql.SQLXML 对象。

 void

setString(int parameterIndex,String x)
          将指定参数设置为给定 Java String 值。

 void

setTime(int parameterIndex,Time x)
          将指定参数设置为给定 java.sql.Time 值。

 void

setTime(int parameterIndex,Time x, Calendar cal)
          使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Time 值。

 void

setTimestamp(int parameterIndex,Timestamp x)
          将指定参数设置为给定 java.sql.Timestamp 值。

 void

setTimestamp(int parameterIndex,Timestamp x, Calendar cal)
          使用给定的 Calendar 对象将指定参数设置为给定 java.sql.Timestamp 值。

 void

setUnicodeStream(int parameterIndex,InputStream x, int length)
          已过时。  

 void

setURL(int parameterIndex,URL x)
          将指定参数设置为给定 java.net.URL 值。

上述文件中存在很多个数据类型的setget方法,有时间了可以仔细阅读研究一下。在本文中不再进行描述。

其中如果不知道数据表中数据类型或者不能确定是什么类型的情况下,可以直接使用setObject方法和getObject方法进行获取和设置。

6.2 日期类型

其中日期类型是一个比较特殊的数据类型,我们看一下getDate()方法

l getDate

Date getDate(int columnIndex)

             throws SQLExceptionJava编程语言中 java.sql.Date对象的形式获取此 ResultSet对象的当前行中指定列的值。

 

参数:

columnIndex - 第一个列是 1,第二个列是2,……

返回:

列值;如果值为 SQL NULL,则返回值为null

抛出:

SQLException - 如果 columnIndex无效;如果发生数据库访问错误或在已关闭的结果集上调用此方法

其中返回值Date类型是java.sql.Date类型,希望使用的时候不要弄混淆。

l Java.sql.Datejava.util.Date的子类

 

现在我们做一个简单的测试,来看看两者的区别

@Test

public void compareDate(){

java.util.Date date1 = new java.util.Date();

System.out.println(date1);

System.out.println("=====================");

java.sql.Date date2 = new java.sql.Date(3434534);

System.out.println(date2);

}

执行结果为:

Mon May 30 13:39:20 CST 2011

=====================

1970-01-01

其中可以看出,sql中的date是没有时间的,而util中的date是存在时间的,其次两者的差距是toString()方法中,当然这并不是多么重要的差距,再做一个测试来看一下,我们保存一个数据到数据库中,看是否能够将时间也保存进去呢

String sql = "insert into user(id,name,birthday,money) values(4,'lisi',?,13000.00)";

stmt = conn.prepareStatement(sql);

stmt.setDate(1, new Date(System.currentTimeMillis()));

stmt.execute();

执行完毕之后,我们查询数据库看到如下的结果

 

可以看到,时间还是没有被保存进去,如果我们要解决该问题应该如何处理呢,再来看一个函数

l setTimestamp

void setTimestamp(int parameterIndex,

                  Timestamp x)

                  throws SQLException

将指定参数设置为给定 java.sql.Timestamp 值。在将此值发送到数据库时,驱动程序将它转换成一个 SQL TIMESTAMP 值。

参数: 

parameterIndex - 第一个参数是 1,第二个参数是2……

x - 参数值

抛出: 

SQLException -如果 parameterIndex不对应于 SQL语句中的参数标记;如果发生数据库访问错误,或者在关闭的 PreparedStatement 上调用此方法

 

使用该函数就可以将日期时间一并保存在数据库当中,我们再次修改一下刚才的示例来演示一下

@Test

public void insertTimeStamp()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into user(id,name,birthday,money) values(5,'zhaowu',?,13000.00)";

stmt = conn.prepareStatement(sql);

stmt.setTimestamp(1, new Timestamp(System.currentTimeMillis()));

stmt.execute();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

执行成功之后,可以看到日期保存时,时间也跟着保存了进去,读者可以自己进行试验,这里就不再演示。

l 日期类型的读取

如果想要读取完整的日期,包括时间,不要使用getDate()方法,否则后面的时间会被舍弃掉的,及时转换成java.util.Date后面的时间仍然会是0,这就要看具体需要完成什么,再来决定使用上述的哪几种方法。

6.3 CLOB类型

当我们存放大量的文本信息时,数据库中的varchar或者varchar2肯定是不能满足的,varchar2好像最多只能有4000个长度,存放一篇很长的文章或者一个文本信息,肯定是无法满足我们的需求,现在的大多数数据库都支持了CLOB的类型用于满足我们的需求,一般来说它所提供能容量肯定能够满足我们存放文本信息,当然jdbc也对其提供了相应的API文档支持。

l 创建数据表

create table clob_test(id integer primary key ,info text);

接下来我们来演示一下,将一个文本文件存放在该字段中,并且再将存放的信息查询出来。

保存CLOB数据

package com.wangwenjun.jdbc;

 

import java.io.BufferedReader;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

 

import org.junit.Test;

 

public class ClobTest {

 

@Test

public void insertText()throws SQLException, FileNotFoundException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into clob_test(id,info) values(?,?)";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, 3);

File file = new  File("E:\\execise\\jdbc\\jdbc1\\src\\com\\wangwenjun\\jdbc\\ClobTest.java");

FileReader reader = new FileReader(file);

BufferedReader buffer = new BufferedReader(reader);

stmt.setCharacterStream(2,buffer,3);

stmt.execute();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

}

 

我们将运行的类作为文本文件给进行了保存,查看数据库,确实也进行了保存,为了能更加直观期间,我们通过CLOB的读取方式进行查看

查询CLOB数据

再上一个小节中,我们将一个文本文件保存在了数据库中,能存就能取,为了能说明问题,我们做一个小例子,将刚才的保存信息再给获取出来,在控制台上打印一份,并且保存在一个txt文件中。

package com.wangwenjun.jdbc;

 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

import java.io.Reader;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

 

import org.junit.Test;

 

public class ClobTest {

 

@Test

public void insertText()throws SQLException, FileNotFoundException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into clob_test(id,info) values(?,?)";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, 3);

File file = new  File("E:\\execise\\jdbc\\jdbc1\\src\\com\\wangwenjun\\jdbc\\ClobTest.java");

FileReader reader = new FileReader(file);

BufferedReader buffer = new BufferedReader(reader);

stmt.setCharacterStream(2,buffer,3);

stmt.execute();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

@Test

public void queryText()throws SQLException, IOException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "select info from  clob_test where id=3";

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

if(rs.next()){

Reader r=rs.getCharacterStream(1);

BufferedReader buffer = new BufferedReader(r);

String temp = "";

File f = new File("d:\\reader.txt");

FileWriter fw = new FileWriter(f);

BufferedWriter bw = new BufferedWriter(fw);

while((temp=buffer.readLine())!=null){

System.out.println(temp);

bw.write(temp+"\n");

bw.flush();

}

bw.close();

fw.close();

buffer.close();

}

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

}

 

查看控制台可以看到和上面代码一模一样的输出,并且在D盘的根目录下有一个reader.txt的文件,其中内容也和上面的代码一模一样,读者可以亲自测试一下。

6.4 BLOB类型

BLOB类型是专门针对二进制文件进行的存取,比如图片,音频,等信息,当然了现在主流的数据库均会对其进行支持的,jdbc API同样也对其进行了支持,下面我们通过一个示例来进行演示

l 创建数据表

Create table blob_test(id integer primary key ,info blob);

 

l 保存二进制文件至数据库

@Test

public void insertBlob()throws SQLException, IOException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into blob_test(id,info) values(?,?)";

stmt = conn.prepareStatement(sql);

stmt.setInt(1,3);

File file = new  File("C:\\Users\\Administrator\\Pictures\\Google Talk\\Alien 1.bmp");

FileInputStream fis = new FileInputStream(file);

stmt.setBinaryStream(2, fis, (int)file.length());

stmt.execute();

fis.close();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

程序运行正常,并且查看数据库,数据库中存放了图片的信息。由于通过sql语句无法看到图片的信息,所以我们通过读取的方式展示一下。

l 读取二进制文件从数据库

@Test

public void queryBlob()throws SQLException, IOException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "select info from  blob_test where id=3";

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

if(rs.next()){

InputStream is=rs.getBinaryStream(1);

File file = new File("d:\\a.bmp");

OutputStream os = new FileOutputStream(file);

int len = 0;

byte[] buffers = new byte[1024];

while((len=is.read(buffers))>0){

os.write(buffers, 0, len);

}

os.flush();

os.close();

is.close();

}

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

程序运行结束之后,我们可以在D盘的根目录下面看到一个名字叫a.bmp的文件,阅读者可以自己运行示例中的代码。

6.5 其他数据类型

其他数据类型可以查看数据库和jdbc相关的文档,或者在java.sql.Types文件中可以查看到所涉及的所有类型信息;

第七节 DAO设计模式详解

7.1 实际项目中如何使用JDBC

实际项目中,我们经常会通过查询获取数据,然后将数据用于其他的用途,并非简单的打印或者展示,其次,在实际的应用中,和数据库打交道的jdbc代码会很少出现在业务逻辑之中,因为这样对代码的维护以及再扩展会带来极大的开销,所以会尽量的将数据层的东西抽取出来,让代码显得干净一些,这也就是DAO模式最需要解决的问题之一,当然DAO模式最大的好处是通过数据访问层的接口完全隐藏了数据层的实现细节,让业务层不需要关心具体的细节。

7.2 DAO设计模式简介

DAO的全称是data access object,其中非常重要的一个概念是Domain对象,也就是说一个最常用的POJO对应数据库中的一个表与之对应,如下图所示,展示了DAO所处的位置和所起的作用,图画的有些难看,但是能够说明问题

 

 

7.3 DAO设计模式的实现

首先我们需要设计一个domain对象,还记得我们之前的那张数据表么?就是User表,我们针对这个数据表来设计一个domain对象,代码如下

package com.wangwenjun.jdbc;

 

import java.sql.Date;

 

public class User {

private int id;

private Date birthday;

private String name;

private float money;

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public Date getBirthday() {

return birthday;

}

public void setBirthday(Date birthday) {

this.birthday = birthday;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public float getMoney() {

return money;

}

public void setMoney(float money) {

this.money = money;

}

}

好了我们的domain已经编写好了,可以看出,其中包括了数据表中的所有字段,并且提供了setget方法,接下来我们需要定义一个DAO接口来供应用程序使用

package com.wangwenjun.jdbc.dao;

 

import java.util.List;

 

import com.wangwenjun.jdbc.User;

 

public interface UserDao {

/**

 * 增加一个用户

 * @param user

 * @return

 */

public int addUser(User user);

/**

 * 删除一个用户信息

 * @param user

 * @return

 */

public int deleteUser(User user);

/**

 * 查询用户信息通过id编号

 * @param id

 * @return

 */

public User queryUserById(int id);

 

/**

 * 查询用户列表通过用户名

 * @param name

 * @return

 */

public List<User> queryUserForList(String name);

/**

 * 更新用户信息,根据id编号来更新

 * @param newInfo

 * @param id

 * @return

 */

public int updateUser(User newInfo,int id);

}

 

一个简单的DAO接口的实现,代码如下

package com.wangwenjun.jdbc.dao;

 

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

 

import com.wangwenjun.jdbc.ConnCreate;

import com.wangwenjun.jdbc.User;

 

public class UserDaoImpl implements UserDao {

 

private Connection conn = null;

public UserDaoImpl(Connection conn){

this.conn = conn;

}

public int addUser(User user) {

PreparedStatement stmt = null;

int result = 0;

try {

String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, user.getId());

stmt.setString(2, user.getName());

stmt.setDate(3, user.getBirthday());

stmt.setFloat(4, user.getMoney());

result=stmt.executeUpdate();

} catch (SQLException e) {

e.printStackTrace();

} finally {

try {

ConnCreate.close(null, stmt, null);

} catch (SQLException e) {

e.printStackTrace();

}

}

return result;

}

 

public int deleteUser(User user) {

PreparedStatement stmt = null;

int result = 0;

try {

String sql = "delete from   user where id=?";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, user.getId());

result=stmt.executeUpdate();

} catch (SQLException e) {

e.printStackTrace();

} finally {

try {

ConnCreate.close(null, stmt, null);

} catch (SQLException e) {

e.printStackTrace();

}

}

return result;

}

 

public User queryUserById(int id) {

PreparedStatement stmt = null;

ResultSet rs = null;

User user =null;

try {

String sql = "select * from user where id=?";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, id);

rs = stmt.executeQuery();

if(rs.next()){

user = new User();

user.setId(rs.getInt(1));

user.setName(rs.getString(2));

user.setBirthday(rs.getDate(3));

user.setMoney(rs.getFloat(4));

}

} catch (SQLException e) {

e.printStackTrace();

} finally {

try {

ConnCreate.close(null, stmt, rs);

} catch (SQLException e) {

e.printStackTrace();

}

}

return user;

}

 

public List<User> queryUserForList(String name) {

PreparedStatement stmt = null;

ResultSet rs = null;

User user =null;

List<User> lists = new ArrayList<User>();

try {

String sql = "select * from user where name=?";

stmt = conn.prepareStatement(sql);

stmt.setString(1, name);

rs = stmt.executeQuery();

while(rs.next()){

user = new User();

user.setId(rs.getInt(1));

user.setName(rs.getString(2));

user.setBirthday(rs.getDate(3));

user.setMoney(rs.getFloat(4));

lists.add(user);

}

} catch (SQLException e) {

e.printStackTrace();

} finally {

try {

ConnCreate.close(null, stmt, rs);

} catch (SQLException e) {

e.printStackTrace();

}

}

return lists;

}

 

public int updateUser(User newInfo, int id) {

PreparedStatement stmt = null;

int result = 0;

try {

String sql = "update user set money=? where id=?";

stmt = conn.prepareStatement(sql);

stmt.setString(1, newInfo.getName());

stmt.setInt(2, id);

result=stmt.executeUpdate();

} catch (SQLException e) {

e.printStackTrace();

} finally {

try {

ConnCreate.close(null, stmt, null);

} catch (SQLException e) {

e.printStackTrace();

}

}

return result;

}

}

最后我们来做一个简单的测试,对DAO中所涉及的接口逐个进行测试,来看看最后的执行效果

package com.wangwenjun.jdbc;

 

import java.sql.Connection;

import java.sql.Date;

 

import org.junit.Test;

 

import com.wangwenjun.jdbc.dao.UserDao;

import com.wangwenjun.jdbc.dao.UserDaoImpl;

 

public class UserDaoTest {

@Test

public void addUser(){

Connection conn =  ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

UserDao dao = new UserDaoImpl(conn);

User user = new User();

user.setId(7);

user.setName("liudehua");

user.setBirthday(new Date(System.currentTimeMillis()));

user.setMoney(456456);

int result=dao.addUser(user);

System.out.println("信息录入"+(result>0?"成功!":"失败!"));

}

}

 

程序执行成功,信息也正确插入

信息录入成功!

查看数据库中的信息如下所示

 

可以看到liudehua的信息已经正确的录取了并且在数据库中存放了。

剩余的接口我就不再进行测试了,读者可以自己进行测试,总之通过这样的更改之后我们不难发现,在业务层的确精炼了许多,数据库操作的信息完全被隐藏了起来,如果我们要更改其中的细节,业务逻辑相关的代码则无需进行更改。如果想要增加新的接口,可以继续进行继承或者在原有接口基础之上进行更改,应用照样是不需要进行替换。

至此,我们实现了一个简单的Dao模式,可以看出,我们的代码显的越来越精简,并且可读性也越来越好,在下节中,将会结合工厂模式对DAO模式进行一下整合,进一步来完善程序的设计。

7.4 DAO设计模式与工厂模式的整合

在上一小节中设计的DAO模式还是存在一些问题,比如说当执行时出现异常,我们只是简单的打印了一下堆栈的信息,并没有通知上一层使用者或者展示层,来表示现在出现的错误,并且也没有想办法进行相应的容错,当然,如果sql语句出现错误,或者逐渐冲突,容错是非常难做的,所以一般情况下数据库执行sql语句的错误只需要通过异常的传递机制告知上一层使用者即可。

当然我们的异常可以直接使用SQLException这样的异常,但是会发现我们的应用还是和JDBC产生了关系,也就是产生了依赖,这样对分层是极大不利的。

那么我们自定义一个异常,该异常用于通知使用者或者业务逻辑来知道数据库操作时出现了错误,首先自定义一个异常,代码如下

package com.wangwenjun.jdbc.exception;

 

public class JDBCException extends Exception {

 

private static final long serialVersionUID = -914212712828534650L;

 

private String message="";

private Throwable throwable;

public JDBCException(){

super();

}

public JDBCException(String message){

super(message);

this.message = message;

}

public JDBCException(String message,Throwable throwable){

this(message);

this.throwable = throwable;

}

 

public String getMessage() {

return message;

}

 

public void setMessage(String message) {

this.message = message;

}

 

public Throwable getThrowable() {

return throwable;

}

 

public void setThrowable(Throwable throwable) {

this.throwable = throwable;

}

}

 

修改一下DAO接口

/**

 * 增加一个用户

 * @param user

 * @return

 */

public int addUser(User user)throws JDBCException;

….其余的省略

 

修改一下DAO的实现类

public int addUser(Useruser) throws JDBCException {

PreparedStatement stmt = null;

int result = 0;

try {

String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, user.getId());

stmt.setString(2, user.getName());

stmt.setDate(3, user.getBirthday());

stmt.setFloat(4, user.getMoney());

result=stmt.executeUpdate();

} catch (SQLException e) {

throw new JDBCException("新增用户信息时出现那异常!", e);

} finally {

try {

ConnCreate.close(null, stmt,null);

} catch (SQLException e) {

throw new JDBCException("释放资源时出现异常!", e);

}

}

return result;

}

….其余的省略

再次执行上述的测试代码出现了逐渐冲突,打印信息如下所示

录入信息时出现异常Duplicate entry '7' for key 1

信息录入失败!

很明显的在告知应用,7这个id已经存在了,不允许重复!如果我们做页面的话可以通过很友好的方式告知用户,然后记录错误信息,方便更改查询,而并不是出现一堆堆栈信息,自己不好查找原因,用户也不知道是什么东西。

虽然通过DAO模式将数据库的操作细节隐藏起来了,业务逻辑的处理虽然也是依赖接口但是同时也依赖了接口的实现。

并且随着多个DAO的产生,我们在使用起来就显得比较凌乱,所以试想一下,有没有一个创建DAO的工厂呢?他只产生相关DAO的接口,可以是UserDAO,也可是其他DAO的生产者。

DAOFactory.java

package com.wangwenjun.jdbc.dao;

 

public interface DAOFactory {

 

public UserDao createUserDao();

}

 

SimpleDAOFactory.java

package com.wangwenjun.jdbc.dao;

 

import java.sql.Connection;

 

import com.wangwenjun.jdbc.ConnCreate;

 

public class SimpleDAOFactotyimplements DAOFactory{

 

public UserDao createUserDao() {

Connection conn =  ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");//当然数据库连接的获取通过这样的方式是不太合理的,在后文中将会讲解通过怎样的方式规避!

UserDao dao = new UserDaoImpl(conn);

return dao;

}

}

 

修改一下之前的业务逻辑层

@Test

public void addUser(){

DAOFactory daoFactory = new SimpleDAOFactoty();

UserDao dao = daoFactory.createUserDao();

User user = new User();

user.setId(7);

user.setName("liudehua");

user.setBirthday(new Date(System.currentTimeMillis()));

user.setMoney(456456);

int result = 0;

try {

result = dao.addUser(user);

} catch (JDBCException e) {

System.out.println("录入信息时出现异常"+e.getThrowable().getMessage());

}

System.out.println("信息录入"+(result>0?"成功!":"失败!"));

}

这样一来,业务逻辑的所有行为只会和数据层相关的接口打交道,实现了真正的面向接口编程,更换DAO的实现只需要修改工厂或者新增工厂中的接口即可。

7.5 DAO设计模式测试

相关的测试代码已经在7.37.4中有所演示,所以在本节中将不再进行赘述。

第八节 JDBC对事务的支持

数据库的事务是保证数据完整性的一种机制,简而言之,就是怎样确保数据的执行过程要么都成功,要么都失败,举例子为假设你要给银行还款,需要从你的银行卡中扣除相关的金额也需要在你的信用卡上加钱,这个流程务必是一个完整的流程,不能拆分,如果从你的银行卡中扣除了钱,但是加钱的流程是失败的,这个时候用户是吃亏的,反之银行则会亏本,所以这就涉及到了事务的机制。

原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。

一致性(consistency):在事务处理执行前后,数据库是一致的(两个账户要么都变,或者都不变)

隔离性(isolcation):一个事务处理对另一个事务处理没有影响。

持续性(durability):事务处理的效果能够被永久保存下来 。

概括起来可以用ACID来表述

8.1 模拟转账

还记得之前的那张数据表user么,记得有一个字段叫做money,假设有这样一个需求,首先呢将wangwenjun用户的钱转给liudehua 1000元,然后判断liudehua的账户余额是否大于10000,如果大于10000则抛出一个异常(假设此时系统出现了故障),我们来看看,wangwenjun的钱是不是会减少,在做这样的操作之前我们先查看一下他们两个此时的信息

执行sql语句select * from user where name in('wangwenjun','liudehua');

 

现在来编写程序测试一下,刚才我们的需求,代码示例如下

@Test

public void transcation1()throws Exception{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

stmt = conn.createStatement();

String sql = "update user set money=money-1000 where id=1";//wangwenjun的钱减少1000

stmt.execute(sql);

sql="select money from user where id=7";

rs = stmt.executeQuery(sql);

float money = 0;

if(rs.next()){

money = rs.getFloat(1);

if(money>10000){

throw new Exception("钱已到上线,无法在增加");

}

}

sql = "update user set money=money+1000 where id=7";

stmt.execute(sql);

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

程序像我们当初设想的那样抛出了异常,这个时候我们再来查看一下wangwenjun用户的钱是否被减少了呢,继续执行sql语句

 

可以看到wangwenjun的钱已经变为7500元,这是系统的一个很严重的bug,也是wangwenjun不愿意看到的,上面的问题为什么会发生呢?下面的几个章节将会进行讲解

8.2 jdbc默认事务

Jdbc的事务默认是打开的,也就是说执行每一步操作的话,事务都会隐式的进行提交,在抛出异常之前,我们的更改操作已经同步到了数据库中去,此时只能看到wangwenjun减少了钱,却不能看到liudehua加了钱。

我们应该如何将jdbc的事务打开呢,执行下面的语句即可

connection.setAutoCommit(false);

8.3 事务提交与回滚

为了保证我们的程序能够正确的按照我们的意图来进行,现在修改一下代码,将代码中涉及的三个执行语句放在一个事务当中,统一进行提交或者回滚,修改后的代码如下

@Test

public void transcation2()throws Exception{

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

conn.setAutoCommit(false);

stmt = conn.createStatement();

String sql = "update user set money=money-1000 where id=1";//wangwenjun的钱减少1000

stmt.execute(sql);

sql="select money from user where id=7";

rs = stmt.executeQuery(sql);

float money = 0;

if(rs.next()){

money = rs.getFloat(1);

if(money>10000){

throw new Exception("钱已到上线,无法在增加");

}

}

sql = "update user set money=money+1000 where id=7";

stmt.execute(sql);

} finally {

ConnCreate.close(conn, stmt, rs);

}

conn.commit();

}

在执行代码之前先来查看一下结果

 

执行代码,让异常发生,再来看看效果,同样我们的异常还是像预先设定的那样如约而至,为了明显期间,我们将运行了程序之后的查询粘贴一下,方便做对比。

 

可以看到,wangwenjunmoney没有发生任何的变化,原因是因为我们将所有的sql操作放在了同一个事务之中,通过上端代码的演示,相信读者可以明显的看到,jdbc对事物的控制有两个函数来控制

l connection.commit();//提交事务。

l connection.rollback();//回滚事务。

8.4 设置保存点

如果我们的数据操作中涉及多个数据库操作,其中当出现问题的时候,并非需要将所有的操作都进行回滚,可能其中的某些细节是没有必要回滚的,此时,单纯的用提交或者回滚未免会显得有些武断,我们还可以通过设置保存点的方式来设置我们想要将事务回滚到某个点。

我们可以通过SavePoint API来解决上面的问题

可以在sql的执行过程中来设置SavePoint,然后再需要混滚的地方调用rollback的重载方法

Conn.rollback(SavePoint point);来进行回滚。

个人觉得一个业务流程不应该有太多的数据操作,如果关系非常密切的数据库操作,尽量让其事务保持一致,这样不至于破坏数据的一致性和原子性,所以我觉得使用设置保存点的程序是一种不良设计,只需要知道有这样的一个功能,也就不再对其进行演示。

8.5 JTA事务的介绍

随着分布式系统的普及,我们的数据库也可能是集群形式的,有时候一个应用程序需要操作两个或者两个以上的数据库,这个时候数据的事务控制将会是一个更加复杂和严峻的问题,这就是通常所说的跨库事务,要求几个数据库的事务在一个应用中保持一致,JTA就是为了解决这个问题而诞生的,在该文档中将不再进行介绍,如果有兴趣的读者可以自行研究或者留意作者后期推出的JTA学习总结我们一起学习,一起进步,说好了!不见不散哦。

8.6 数据库的隔离级别介绍

隔离级别 

脏读 

不可重复读 

幻读 

读未提交(Read uncommitted)

V

V

V

读已提交(Read committed)

x

V

V

可重复读(Repeatable read)

x

x

V

可串行化(Serializable )

x

x

x

l V:可能出现,X:不会出现

隔离级别的作用是能够保证多个线程同时操作一条数据时的正确性,它是一个非常重要的概念,也是一个不容易理解透的概念,希望阅读者能够掌握。

l 读未提交(Read uncommitted)

他是隔离性最差的一个级别,简单理解就是,当你修改了或者增加了一个数据,还没有进行提交,别人就可以看到,这个时候就会产生脏读,可以重复读,幻读了。

l 读已提交(Read committed)

他的隔离级别就相比之前的来说稍微高一些,意思就是说当一个线程的操作事务没有提交,别人是无法读取的得到的,但是他还是不能解决不可重复读和幻读。

l 可重复读(Repeatable read)

隔离级别更高了,它是mysql的默认隔离级别,它能保证不会读到其他线程未提交的数据,也能保证每次读到的数据是一样的,但是不能避免幻读的问题。

l 可串行化(Serializable )

这是级别最高的隔离级别,它可以避免脏读,不可重复读,幻读的问题。

但是隔离级别越高,对数据正确性的保证会越好,但同时牺牲很多的数据库性能,并发性会越差,具体的调整需要根据自己的需要进行,建议不要设置为可串行化与读未提交这样的级别,当然各个数据库提供对隔离级别的支持是不一样的,有些数据库压根就没有实现和支持相应的隔离级别。

l 隔离级别实验演示

为了能更好的理解上述的描述,我们在mysql数据库中进行一下隔离级别的测试,现在打开两个mysql客户端程序

1. 查询mysql隔离级别的方法,查询全局变量

select @@tx_isolation;

 

2. 设置隔离级别为未提交读

set transaction isolation level read uncommitted;

3. 启动事务

start transaction;

前期的准备已经就绪了,现在就开始测试一下我们的结果

8.6.1 未提交读

Ø 窗口一的演示

 

Ø 窗口二的演示

 

可以看到第二个客户端读到了未提交的数据,产生了脏读,

当第一个窗口将事务回滚,第二个独到的数据又变回去了,所以产生了不可重复读这样;

同样在第一个窗口中插入一条数据,第二个窗口也是可以看到的,所以就产生了幻读;

8.6.2 提交读

Ø 窗口一的演示

 

Ø 窗口二的演示

 

通过上图的对比,发现窗口一没有提交,读到的数据则是不一样的,避免了脏读的问题,但是当第一个窗口将事务提交,第二个窗口读到的数据又会是新的,所以没有避免不可重复读,幻读同样存在。

8.6.3 重复读

Ø 窗口一演示

 

Ø 窗口二演示

 

可以看到非但没有读到未提交的数据,就连其他程序提交事务之后的数据也没有读到,这样不仅避免了脏读,也支持了不可重复读,但是幻读同样未解决。

8.6.4 序列化读

Ø 窗口一演示

 

Ø 窗口二演示

 

序列化级别的数据不仅能解决幻读,还能保证重复读取数据,并且能将幻影数据过滤掉,我们在第一个窗口中插入了数据,并且进行了提交,在第二个窗口中都没有的到相应的数据。

8.7 小结

个人认为,这一个章节是我写的最好的一部分,因为在编写该章节中让我加深了对事务的理解,并且学习到了不少的东西,总结的也很认真,其中对隔离级别的实验让我加深了对数据库事务的更进一步认识,还有个人觉得设置保存点应该尽量避免使用。

第九节  PreparedStatement接口的使用

其实通过前面几个章节的介绍,大部分代码都涉及到了PreparedStatement接口的用法,详细的信息就不用过多介绍,只说几点总结

Ø PreparedStatementStatement的子类;

Ø PreparedStatement是一个预处理命令的Statement实现;

Ø 在数据库操作中PreparedStatement会带来很大的方便,减少拼写sql字符串带来的麻烦;

Ø 具体的函数接口查看API文档的介绍;

第十节 CallableStatement接口的使用

写到这里,就会想JDBC如此之强大,它能否调用数据库中的存储过程么?当然了,如此之强大的JDBC,肯定能满足我们这样的需求,在本节中将会介绍JDBC如何调用数据库存储过程。

在本节中,将会通过三个方面介绍CallableStatement的使用

Ø 调用存储过程(无参数,无返回值)

Ø 调用存储过程(有参数,无返回值)

Ø 调用存储过程(有参数,有返回值)

9.1 无参无返回值存储过程调用

首先我们创建一个存储过程,创建脚本如下

create or replace procedure test1

as

begin

insert into user values(11,'hi',now(),1000);

commit;

end test1;

是一个非常简单的存储过程,只需要在user表中新增一条数据即可,下面我们来使用JDBC来执行存储过程

@Test

public void callProcedureWithOutParamWithOutResult()throws SQLException{

Connection conn = null;

CallableStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "{call test1()}";

stmt = conn.prepareCall(sql);

stmt.executeUpdate();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

执行完毕之后会在数据库中查看新增了一条记录;

9.2 有参无返回值存储过程调用

修改存储过程,将存储过程变为如下

create or replace procedure test1(in id integer,in name varchar(20),in money float)

as

begin

insert into user values(id,name,now(),money);

commit;

end test1;

可以看到在该存储过程中新增加了几个入参,这样我们可以通过使用callablestatement所提供的set方法对其进行赋值,代码演示如下

@Test

public void callProcedureWithParamWithOutResult()throws SQLException{

Connection conn = null;

CallableStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "{call test1(?,?,?)}";

stmt = conn.prepareCall(sql);

stmt.setInt(1, 17);

stmt.setString(2, "test");

stmt.setFloat(3, 6000);

stmt.executeUpdate();

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

执行之后,我们的数据库中又增加了一条记录

9.3 有参有返回值存储过程调用

再次修改存储过程,让其支持值返回

create or replace procedure test1(in id integer,in name varchar(20),in money float,out counter integer)

as

begin

insert into user values(id,name,now(),money);

select count(1) into counter from user;

commit;

end test1;

编写测试代码,在需要将值返回时,需要对其进行注册,代码演示如下

@Test

public void callProcedureWithParamWithResult()throws SQLException{

Connection conn = null;

CallableStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "{call test1(?,?,?,?)}";

stmt = conn.prepareCall(sql);

stmt.setInt(1, 17);

stmt.setString(2, "test");

stmt.setFloat(3, 6000);

stmt.registerOutParameter(1, Types.INTEGER);

stmt.executeUpdate();

int counter = stmt.getInt(4);

System.out.println(counter);

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

至此,关于存储过程的调用已经ok了,接口也比较简单,熟悉Statement或者PreparedStatement熟悉起来就非常简单,复杂的地方就是存储过程的编写,在笔者的后期札记中会详细的介绍,到时候希望关注。

9.4 JDBC其他API

假设,我们的表中有一个自动生成的主键,我们插入了一条数据,但是此时还想要将该主键的数值获取出来,这个时候怎么做呢?在查询一次么?不用了,JDBC API已经提供了这样的方法可以方便我们的使用,代码演示如下

@Test

public void testQueryPK()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into user values(?,?,?,?)";

stmt = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

stmt.setInt(1, 999);

stmt.setString(2, "test2");

stmt.setDate(3, new Date(System.currentTimeMillis()));

stmt.setFloat(4, 5000);

stmt.executeUpdate();

rs = stmt.getGeneratedKeys();

if(rs.next())

System.out.println(rs.getInt(1));

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

 

第十一节 元数据信息

元数据信息就是获取相关的信息参数,大致可以分为两个

Ø 数据库元数据信息;

Ø 参数元数据信息;

在本节中将会围绕着这两点进行讲解

11.1 数据库元数据信息

就是获取数据库的相关信息资源,这个是比较简单的,我们通过一个简单的小例子讲解一下即可

 

@Test

public  void connectionMeta()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

DatabaseMetaData dataBaseMeta=conn.getMetaData();

System.out.println("主版本:"+dataBaseMeta.getDatabaseMajorVersion());

System.out.println("次版本:"+dataBaseMeta.getDatabaseMinorVersion());

System.out.println("驱动名称:"+dataBaseMeta.getDriverName());

System.out.println("默认隔离级别:"+dataBaseMeta.getDefaultTransactionIsolation());

} finally {

ConnCreate.close(conn, stmt, null);

}

}

执行结果为:

主版本:5

次版本:0

驱动名称:MySQL-AB JDBC Driver

隔离级别:2

还有很多参数,读者可以自己进行尝试。

11.2 参数元数据信息

这是一个非常有用的API ,我们在讲解PreparedStatement接口的时候,会经常参入一些参数,参数元数据信息的作用就是获取参数的一些信息,我们通过一个示例来看一下怎样获取和使用

假设有这样一个需求,我们需要执行一个占位符这样的sql,我们在执行之前要看看,参数在数据库中的名字,类型,长度等信息

@Test

public  void paramMeta()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into user values(?,?,?,?)";

stmt = conn.prepareStatement(sql);

ParameterMetaData meta=stmt.getParameterMetaData();

int count = meta.getParameterCount();

for(int i = 1;i<=count;i++){

System.out.print("ParameterClassName"+meta.getParameterClassName(i)+"\nParameterType"+meta.getParameterType(i)+"\nParameterTypeName"+meta.getParameterTypeName(i)+"\n");

}

} finally {

ConnCreate.close(conn, stmt, null);

}

}

第十二节 批处理的使用

在本节中,我们来看看,JDBC对批处理的操作,首先简单说一下JDBC操作sql语句的简单机制。

JDBC执行数据库操作语句,首先需要将sql语句打包成为网络字节流,传递给数据库,数据库经过解包,然后编译sql语句,最后执行,然后将结果通过字节流的形式返回给JDBC API,简单的来说大致分为以下几点:

Ø JDBC打包sql语句;

Ø 发送字节流至数据库;

Ø 数据库解包;

Ø 检查sql语法,编译sql

Ø 执行sql语句;

Ø sql语句返回给JDBC接口;

如果我们需要插入成千上万甚至更多的数据库,如果采用传统的方式,势必每次都需要经历上述几个步骤,其中执行多少次sql语句就需要进行多少次通讯,网络数据通讯的开销也是一个很耗时的操作步骤,怎样才能减少网络的操作次数呢?我们可否将所有的需要执行的sql语句一次性传递给数据库,然后再将结果返回回来呢,这样不就减少了网络层的开销么?基于这样的原因JDBC API 提供了一个批处理的机制,方便我们的操作,稍等我会通过示例对其进行演示;

12.1 普通方式插入一千条数据

@Test

public void statementInsert()throws SQLException{

long startTime = System.currentTimeMillis();

normalInsert(0, 1000);

System.out.println("total time:"+(System.currentTimeMillis()-startTime));

}

private  static void normalInsert(int start,int end)throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";

for(int i = start;i<end;i++){

stmt = conn.prepareStatement(sql);

stmt.setInt(1, i);

stmt.setString(2, "test");

stmt.setDate(3, new Date(System.currentTimeMillis()));

stmt.setFloat(4, 6000);

stmt.executeUpdate();

}

} finally {

ConnCreate.close(conn, stmt, null);

}

}

执行结果为:

total time:27530

12.2 批处理方式插入一千条数据

@Test

public void batchInsert()throws SQLException{

long startTime = System.currentTimeMillis();

batchInsert(1000, 2000);

System.out.println("total time:"+(System.currentTimeMillis()-startTime));

}

private static void batchInsert(int start,int end)throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";

stmt = conn.prepareStatement(sql);

for(int i = start;i<end;i++){

stmt.setInt(1, i);

stmt.setString(2, "test");

stmt.setDate(3, new Date(System.currentTimeMillis()));

stmt.setFloat(4, 6000);

stmt.addBatch(sql);

}

int[] result=stmt.executeBatch();

System.out.println("result:"+result.length);

} finally {

ConnCreate.close(conn, stmt, null);

}

}

执行结果为:

total time:22305

可以看到性能优化了一些,但是可能和大家的期望值有些差距,当然这和数据库的驱动有直接的关系,有时候甚至会降低性能,如果需要采取这样的方式做的时候需要提前进行一下测试,然后进行选择;

第十三节 JDBC其他API

截止目前,JDBCAPI已经讲解的基本上差不多了,我们在本节中将剩下的一些技术做一个简单的介绍,方便读者使用,至于剩余的技术,读者可以自行上网研究查询资料。

Ø 可滚动结果集;

Ø 分页技术;

Ø 可更新结果集;

13.1 可滚动结果集

大家还有没有注意到,我们使用ResultSet的时候,获取结果的时候经常使用next方法,该指针的意思就是将指针指向下一个数据行,然后获取出来,那么我们可否访问某个指针上一行记录呢?这就是可滚动结果集的初衷

代码示例

package com.wangwenjun.jdbc.batch;

 

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

 

import org.junit.Test;

 

import com.wangwenjun.jdbc.ConnCreate;

 

public class ScrollResultTest {

 

@Test

public void scollResult()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "select * from user where id<10";

stmt = conn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);

rs = stmt.executeQuery();

while(rs.next()) //向下滚动

{

System.out.println("name:"+rs.getString(2)+"id:"+rs.getInt(1));

}

System.out.println("---------------------------");

if(rs.previous()){//向上滚动

System.out.println("name:"+rs.getString(2)+"id:"+rs.getInt(1));

}

rs.absolute(5);

System.out.println("---------------------------");

if(rs.next()){//定位到指定的行

System.out.println("name:"+rs.getString(2)+"id:"+rs.getInt(1));

}

} finally {

ConnCreate.close(conn, stmt, null);

}

}

}

 

执行结果

name:testid:0

name:testid:1

name:testid:2

name:testid:3

name:testid:4

name:testid:5

name:testid:6

name:testid:7

name:testid:8

name:testid:9

---------------------------

name:testid:9

---------------------------

name:testid:5

可以看到分割线前后的打印信息一样,可以看出来,结果集向上滚动成功,并且能准确定位到指定的行,当然还可以直接到第一行,最后一行,判断是否是第一行或者最后一行,读者可以自己做testCase

13.2 分页技术

分页技术是一项很常见的技术,而且是一个非常重要的技术,如果数据总共有几十万几百万条,都获取出来给浏览者带来一定的麻烦不说,还会将服务器的内存消耗的很严重,所以我们就采用分页技术,分页技术的概念其实很简单,意思就是说我想从第几行开始再获取多少行记录,我们来看一个例子

@Test

public void page()throws SQLException{

page(100,20);

}

static void page(int start,int total)throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "select * from user limit ?,?";

stmt = conn.prepareStatement(sql);

stmt.setInt(1, start);

stmt.setInt(2, total);

rs = stmt.executeQuery();

while(rs.next()) //向下滚动

{

System.out.println("name:"+rs.getString(2)+"id:"+rs.getInt(1));

}

} finally {

ConnCreate.close(conn, stmt, null);

}

}

执行结果当然是将数据库中的数据从第100行开始到120的记录展示出来,其中使用了limit这个关键字,这是mysql特有的,其他数据库是没有这个关键字的,oracle需要使用伪列rowid来进行分页。

13.3 可更新结果集

这个API是一个不常使用的API,也是实际项目中极力反对的API,但是为了拓展读者的阅读视野,在本小节中做一个简单的演示即可。

@Test

public void modifyReult()throws SQLException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnCreate.getConnection("jdbc:mysql://localhost:3306/test",

"root", "r66t");

String sql = "select * from user where id=100";

stmt = conn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);

rs = stmt.executeQuery();

while(rs.next()) //向下滚动

{

System.out.println("name:"+rs.getString(2)+"id:"+rs.getInt(1));

rs.updateString("name", "update3");

rs.updateRow();

}

} finally {

ConnCreate.close(conn, stmt, rs);

}

}

演示结果为

 

可以看到结果已经正常被更新成功!

第十四节 编写一个简单的数据库连接池

通过前面十三个章节的讲解,我们的jdbc常用API基本上介绍完了,在本节中我们将通过由浅入深的方式介绍一下数据库连接池的原理,并尝试写一个简单的数据库连接池,加深读者对数据库连接池的理解;

14.1 为什么要使用数据库连接池

使用jdbc最大的开销之一就是创建数据库,当我们频繁的创建数据库时,势必影响应用的效率,或者在数据库关闭出现问题时,我们不能马上释放,时间长一些,整个数据库的 资源将会被我们的应用耗尽,这是一个很危险的讯息,怎样将数据库连接创建带来的开销降到最低呢,就是我们实现将一系列创建完毕的连接存放起来,等到使用的时候再来使用,这就是数据库连接池解决的最大问题和存在的最大必要。

14.2 数据库连接池雏形

编写SimpleDataSource.java

package com.wangwenjun.jdbc.dataSource;

 

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.LinkedList;

 

public class SimpleDataSource {

private static LinkedList<Connection>connPool = null;

 

static {

try {

Class.forName("com.mysql.jdbc.Driver");

connPool = new LinkedList<Connection>();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

public SimpleDataSource(){

for(int i = 0;i<10;i++){

try {

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root", "r66t");

connPool.addFirst(conn);

System.out.println(conn);

} catch (SQLException e) {

e.printStackTrace();

}

}

}

public Connection getConnection(){

return connPool.removeLast();

}

public void closeConn(Connection conn){

connPool.addFirst(conn);

}

}

 

修改ConnCreate.java

package com.wangwenjun.jdbc;

 

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

 

import com.wangwenjun.jdbc.dataSource.SimpleDataSource;

 

public class ConnCreate {

 

private static SimpleDataSource dataSource = new SimpleDataSource();

public static Connection getConnection() {

Connection conn = dataSource.getConnection();

return conn;

}

 

public static void close(Connection conn, Statement stmt, ResultSet rs)

throws SQLException {

if (null != rs) {

rs.close();

}

if (null != stmt) {

stmt.close();

}

if (null != conn) {

dataSource.closeConn(conn);

}

}

}

开始进行单元测试

@Test

public void createDataSource()throws SQLException{

for(int i = 0;i<10;i++){

Connection conn=ConnCreate.getConnection();

ConnCreate.close(conn, null,null);

}

}

执行结果为:

com.mysql.jdbc.Connection@93dee9

com.mysql.jdbc.Connection@1ba34f2

com.mysql.jdbc.Connection@1cde100

com.mysql.jdbc.Connection@360be0

com.mysql.jdbc.Connection@6b97fd

com.mysql.jdbc.Connection@15ff48b

com.mysql.jdbc.Connection@1b90b39

com.mysql.jdbc.Connection@13e8d89

com.mysql.jdbc.Connection@1a8c4e7

com.mysql.jdbc.Connection@1729854

可以看出,获取了10个数据库连接,因为他们的hashcode不一样,我们再来测试一下我们的连接是否能够等到重用呢?修改一下测试代码

@Test

public void createDataSource()throws SQLException{

for(int i = 0;i<20;i++){

Connection conn=ConnCreate.getConnection();

ConnCreate.close(conn, null,null);

System.out.println(conn);

}

}

执行结果如下所示

com.mysql.jdbc.Connection@affc70

com.mysql.jdbc.Connection@1e63e3d

com.mysql.jdbc.Connection@1004901

com.mysql.jdbc.Connection@1b90b39

com.mysql.jdbc.Connection@18fe7c3

com.mysql.jdbc.Connection@b8df17

com.mysql.jdbc.Connection@13e8d89

com.mysql.jdbc.Connection@1be2d65

com.mysql.jdbc.Connection@9664a1

com.mysql.jdbc.Connection@1a8c4e7

com.mysql.jdbc.Connection@affc70

com.mysql.jdbc.Connection@1e63e3d

com.mysql.jdbc.Connection@1004901

com.mysql.jdbc.Connection@1b90b39

com.mysql.jdbc.Connection@18fe7c3

com.mysql.jdbc.Connection@b8df17

com.mysql.jdbc.Connection@13e8d89

com.mysql.jdbc.Connection@1be2d65

com.mysql.jdbc.Connection@9664a1

com.mysql.jdbc.Connection@1a8c4e7

可以看出数据库连接被重复使用了,因为通过打印的语句可以看出,有相同hashcodeConnection

14.2 数据库连接池优化

在上一节中,我们简单实现了一个数据库连接池,做到了简单的数据库连接的重复应用,当然上述的代码还不能真的使用到项目之中去,因为考虑的因素是在太少了,并发,数据库连接的关闭等等,所以在本节中将会逐步进行一系列的优化

14.2.1 对线程池加锁

为了保证,每一个线程获取的数据库连接实例是不同的,我们需要对线程池进行加锁,保证多线程并发时获取的连接各不相干,修改其中的代码片段如下

public Connection getConnection(){

synchronized (connPool) {

return connPool.removeLast();

}

}

14.2.2 连接不够用时抛出异常

在我们的代码中,没有体现出来,当连接不够用时怎么处理,这里我们做一个简单的处理,直接抛出一个异常,让调用者得知当前连接不够使用。

public Connection getConnection()throws Exception{

synchronized (connPool) {

if(connPool.isEmpty())

throw new Exception("have no connection now!");

return connPool.removeLast();

}

}

 

14.3 数据库连接池之代理模式

在使用JDBC连接数据库的时候,最后需要用户释放资源,如果使用者按照传统的方式关闭连接,那么我们的连接池就没有存在的意义了,因为每一次使用者都会给关闭掉,导致连接池的连接会是无效的或者越来越少,为了防止这样的事情发生,我们需要保留使用者的使用习惯,也就是说允许使用者通过close方法释放连接,这个时候我们应该如何做既能起到用户的使用习惯,又能在进行关闭的时候不是真的关掉数据库连接,而是直接存放至数据库连接池中

14.3.1 静态代理

SimpleDataSource.java

package com.wangwenjun.jdbc.dataSource;

 

import java.sql.CallableStatement;

import java.sql.Connection;

import java.sql.DatabaseMetaData;

import java.sql.PreparedStatement;

import java.sql.SQLException;

import java.sql.SQLWarning;

import java.sql.Savepoint;

import java.sql.Statement;

import java.util.Map;

 

public class SimpleConnection implements Connection{

 

private SimpleDataSource dataSource = null;

private Connection connection = null;

public SimpleConnection(SimpleDataSource dataSource,Connection connection) {

this.dataSource = dataSource;

this.connection = connection;

}

public void clearWarnings() throws SQLException {

connection.clearWarnings();

}

 

public void close() throws SQLException {

dataSource.closeConn(this);

}

 

public void commit() throws SQLException {

connection.commit();

}

 

public Statement createStatement() throws SQLException {

return connection.createStatement();

}

 

public Statement createStatement(int arg0, int arg1) throws SQLException {

return connection.createStatement(arg0, arg1);

}

 

public Statement createStatement(int arg0, int arg1, int arg2)

throws SQLException {

return connection.createStatement(arg0, arg1, arg2);

}

 

public boolean getAutoCommit() throws SQLException {

return connection.getAutoCommit();

}

 

public String getCatalog() throws SQLException {

return connection.getCatalog();

}

 

public int getHoldability() throws SQLException {

return connection.getHoldability();

}

 

public DatabaseMetaData getMetaData() throws SQLException {

return connection.getMetaData();

}

 

public int getTransactionIsolation() throws SQLException {

return connection.getTransactionIsolation();

}

 

public Map<String, Class<?>> getTypeMap() throws SQLException {

return connection.getTypeMap();

}

 

public SQLWarning getWarnings() throws SQLException {

return connection.getWarnings();

}

 

public boolean isClosed() throws SQLException {

return connection.isClosed();

}

 

public boolean isReadOnly() throws SQLException {

return connection.isReadOnly();

}

 

public String nativeSQL(String arg0) throws SQLException {

return connection.nativeSQL(arg0);

}

 

public CallableStatement prepareCall(String arg0) throws SQLException {

return connection.prepareCall(arg0);

}

 

public CallableStatement prepareCall(String arg0, int arg1, int arg2)

throws SQLException {

return connection.prepareCall(arg0, arg1, arg2);

}

 

public CallableStatement prepareCall(String arg0, int arg1, int arg2,

int arg3) throws SQLException {

return connection.prepareCall(arg0, arg1, arg2, arg3);

}

 

public PreparedStatement prepareStatement(String arg0) throws SQLException {

return connection.prepareStatement(arg0);

}

 

public PreparedStatement prepareStatement(String arg0, int arg1)

throws SQLException {

return null;

}

 

public PreparedStatement prepareStatement(String arg0, int[] arg1)

throws SQLException {

return connection.prepareStatement(arg0, arg1);

}

 

public PreparedStatement prepareStatement(String arg0, String[] arg1)

throws SQLException {

return connection.prepareStatement(arg0, arg1);

}

 

public PreparedStatement prepareStatement(String arg0, int arg1, int arg2)

throws SQLException {

return connection.prepareStatement(arg0, arg1, arg2);

}

 

public PreparedStatement prepareStatement(String arg0, int arg1, int arg2,

int arg3) throws SQLException {

return connection.prepareStatement(arg0, arg1, arg2, arg3);

}

 

public void releaseSavepoint(Savepoint arg0) throws SQLException {

connection.releaseSavepoint(arg0);

}

 

public void rollback() throws SQLException {

connection.rollback();

}

 

public void rollback(Savepoint arg0) throws SQLException {

connection.rollback(arg0);

}

 

public void setAutoCommit(boolean arg0) throws SQLException {

connection.setAutoCommit(arg0);

}

 

public void setCatalog(String arg0) throws SQLException {

connection.setCatalog(arg0);

}

 

public void setHoldability(int arg0) throws SQLException {

connection.setHoldability(arg0);

}

 

public void setReadOnly(boolean arg0) throws SQLException {

connection.setReadOnly(arg0);

}

 

public Savepoint setSavepoint() throws SQLException {

return connection.setSavepoint();

}

 

public Savepoint setSavepoint(String arg0) throws SQLException {

return connection.setSavepoint(arg0);

}

 

public void setTransactionIsolation(int arg0) throws SQLException {

connection.setTransactionIsolation(arg0);

}

 

public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException {

connection.setTypeMap(arg0);

}

 

}

数据库连接池代码SimpleDataSource.java

package com.wangwenjun.jdbc.dataSource;

 

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.LinkedList;

 

public class SimpleDataSource {

private static LinkedList<Connection> connPool = null;

private static int minCount = 5;

static {

try {

Class.forName("com.mysql.jdbc.Driver");

connPool = new LinkedList<Connection>();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

public SimpleDataSource(){

for(int i = 0;i<minCount;i++){

try {

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "r66t");

Connection proxyConnection = new SimpleConnection(this, conn);

connPool.addFirst(proxyConnection);

} catch (SQLException e) {

e.printStackTrace();

}

}

}

/**

 * 获取数据库连接

 * @return

 * @throws Exception

 */

public Connection getConnection() throws Exception{

synchronized (connPool) {

if(connPool.isEmpty())

throw new Exception("have no connection now!");

return connPool.removeLast();

}

}

/**

 * 释放数据库连接

 * @param conn

 * @throws SQLException

 */

void closeConn(Connection conn) throws SQLException{

synchronized (connPool) {

connPool.addFirst(conn);

}

}

/**

 * 获取当前数据库连接数

 * @return

 */

public int getCurrentCount(){

synchronized (connPool) {

return connPool.size();

}

}

}

测试代码

@Test

public void createDataSource1()throws Exception{

for(int i = 0;i<20;i++){

Connection conn=ConnCreate.getConnection();

conn.close();

System.out.println(conn);

}

}

在上述代码中我们可以看出我们自定义了一个Connection的实现类,并且在连接池中存放的是我们自定义的Connection类,这样在进行关闭的时候我们可以将数据库连接存放至连接池中。

14.3.2 动态代理

可以看出,静态代理的机制,在实现上很死板,并且我们需要重复写的东西实在太多了,从JDK1.3开始有了一个动态代理机制,我们可以利用该机制来实现我们刚才想要的功能。

DynSimpleDataSource.java

package com.wangwenjun.jdbc.dataSource;

 

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.LinkedList;

 

public class DynSimpleDataSource {

private static LinkedList<Connection> connPool = null;

private static int minCount = 5;

static {

try {

Class.forName("com.mysql.jdbc.Driver");

connPool = new LinkedList<Connection>();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

public DynSimpleDataSource(){

for(int i = 0;i<minCount;i++){

try {

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "r66t");

DynSimpleConnection dynSimpleConnection = new DynSimpleConnection(this);

Connection connWarp=dynSimpleConnection.bind(conn);

connPool.addFirst(connWarp);

} catch (SQLException e) {

e.printStackTrace();

}

}

}

/**

 * 获取数据库连接

 * @return

 * @throws Exception

 */

public Connection getConnection() throws Exception{

synchronized (connPool) {

if(connPool.isEmpty())

throw new Exception("have no connection now!");

return connPool.removeLast();

}

}

/**

 * 释放数据库连接

 * @param conn

 * @throws SQLException

 */

void closeConn(Connection conn) throws SQLException{

synchronized (connPool) {

connPool.addFirst(conn);

}

}

/**

 * 获取当前数据库连接数

 * @return

 */

public int getCurrentCount(){

synchronized (connPool) {

return connPool.size();

}

}

}

DynSimpleConnection.java

package com.wangwenjun.jdbc.dataSource;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.sql.Connection;

 

public class DynSimpleConnection implements InvocationHandler {

 

private Connection realConnection = null;// Connection real

 

private Connection warpConnection = null;

private DynSimpleDataSource dataSource = null;

 

public DynSimpleConnection(DynSimpleDataSource dataSource) {

this.dataSource = dataSource;

}

 

public Connection bind(Connection obj) {

this.realConnection = obj;

this.warpConnection= (Connection) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj

.getClass().getInterfaces(), this);

return warpConnection;

}

 

public Object invoke(Object arg0, Method arg1, Object[] arg2)

throws Throwable {

String methodName = arg1.getName();

Object obj = null;

if(methodName.equals("close")){

dataSource.closeConn(warpConnection);

}else{

obj = arg1.invoke(realConnection, arg2);

}

return obj;

}

}

可以发现代码简化了好多,并且实现了我们所需要的功能,测试代码如下所示

@Test

public void createDataSource2()throws Exception{

for(int i = 0;i<20;i++){

Connection conn=ConnCreate.getConnection();

conn.close();

System.out.println(conn);

}

}

14.4 DBCP数据库连接池的使用

写到这里,我们的连接池已经有些接近真实的数据库连接池了,但是其功能还是很单一,健壮性远远不够,不能直接在实际项目中应用,但是通过前面几个小节的讲解和逐步改进,我们最起码了解了什么是数据库连接池,以及连接池或与应该具备怎样的功能,在开源界,连接池框架实在太多了,并且健壮性足够而且还有专门的优秀团队维护,比如DBCPC3P0DbPool等等,在本节中我们做一个抛砖引玉的工作,简单的介绍一下DBCP的示例代码,希望读者有兴趣可以自己再研究一下。

@Test

public void simpleTest()throws Exception{

BasicDataSource dataSource = new BasicDataSource();

dataSource.setUsername("root");

dataSource.setPassword("r66t");

dataSource.setUrl("jdbc:mysql://localhost:3306/test");

dataSource.setDriverClassName("com.mysql.jdbc.Driver");

System.out.println(dataSource.getConnection());

}

 

第十五节 jdbc轻量级封装

传统的JDBC使用方式在使用起来非常的麻烦,需要创建一系列的资源,然后又要释放一系列的资源,还要为了重复的异常信息进行处理,在本章中我们将对JDBC的使用做一个轻量级的封装,在本章中将会展示极少数部分的代码,如果大家需要剩余的代码可以给我发QQ跟我进行索要。

15.1 将结果集封装为Map

15.1.1 ResultSetMetaData演示

在进行Map封装的开始,我们首先来介绍一个API,并且进行一下简单的演示,先简单说一下该API的作用,如果我们不知道我们的一个sql语句查询了几列结果集,并且每列的列名,类型等信息,这个时候我们应该怎么去做呢?

ResultSetMetaData接口刚好给我们提供了这样的一个功能,我们可以通过他的若干个方法来获取我们关心的信息,在开始将结果集分装为Map对象的时候我们来进行一下演示。

@Test

public void resultMeta()throws SQLException{

String sql="select * from user";

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

int count = rsmd.getColumnCount();

for(int i=1;i<=count;++i){

System.out.println("Type:"+rsmd.getColumnType(i));

System.out.println("ColumnName:"+rsmd.getColumnName(i));

System.out.println("ColumnLable:"+rsmd.getColumnLabel(i));

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

}

执行结果为:

Type:4

ColumnName:id

ColumnLable:id

Type:12

ColumnName:name

ColumnLable:name

Type:91

ColumnName:birthday

ColumnLable:birthday

Type:7

ColumnName:money

ColumnLable:money

15.1.2解决多行记录的问题

我们的需求是这样的,我们需要将列名作为Key值,查询出来的记录作为Value值,如果当我们查询出来的记录超过一行,这个时候怎么做呢?我们不能武断的随便将一行记录封装成Map结果集给使用者,这是一种不合理的设计,我们需要告知使用者,当前的查询语句会查询出来一行以上的记录,不能使用Map这样的结果集,通过代码演示如何得到记录数超过一行并且告知使用者

@Test

public void resultExceed()throws SQLException{

String sql="select * from user";

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

if(rs.next()){

if(!rs.isLast())

throw new SQLException("查询记录超过一行,不能使用Map结果集");

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

}

执行结果为

 

15.1.3 Map结果集的封装

好了,知识点具备了,并且知道了怎么去解决多行记录的问题,我们开始将结果集封装成Map的形式

@Test

public void warp2Map()throws SQLException{

Map<String,Object> map = warp2Map("select * from user where id=1");

System.out.println(map);

}

static Map<String,Object> warp2Map(String sql)throws SQLException{

Map<String,Object> map = null;

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

int count = rsmd.getColumnCount();

if(rs.next()){

if(!rs.isLast())

throw new SQLException("查询记录超过一行,不能使用Map结果集");

map = new HashMap<String, Object>();

for(int i=1;i<=count;++i){

map.put(rsmd.getColumnLabel(i), rs.getObject(i));

}

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

return map;

}

执行结果为:

{money=6000.0, name=test, id=1, birthday=2011-06-02}

可以看出我们已经顺利完成了任务

15.2 将结果集封装为对象

这个需求相对来说就显得有些复杂了,我们需要用到反射的技术,如果大家对反射不是很了解,可以阅读我的另外一本札记《反射(reflection)学习整理》 

15.2.1 userPOJO的编写

package com.wangwenjun.jdbc.template;

 

import java.sql.Date;

 

public class User {

//-----------------------------------------字段的定义

private Integer id;

private String name;

private Date birthday;

private Float money;

//-----------------------------------------提供默认构造函数

public User(){

}

 

//-----------------------------------------生成setter getter方法

public Integer getId() {

return id;

}

 

public void setId(Integer id) {

this.id = id;

}

 

public String getName() {

return name;

}

 

public void setName(String name) {

this.name = name;

}

 

public Date getBirthday() {

return birthday;

}

 

public void setBirthday(Date birthday) {

this.birthday = birthday;

}

 

public Float getMoney() {

return money;

}

 

public void setMoney(Float money) {

this.money = money;

}

}

 

15.2.2 Bean结果集的封装

package com.wangwenjun.jdbc.template;

 

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

 

import org.junit.Test;

 

public class ResultBean {

@Test

public void warp2Object()throws SecurityException, IllegalArgumentException, SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{

User user =(User) warp2Class("select * from user where id=0",User.class);

System.out.println(user.getName());

System.out.println(user.getBirthday());

System.out.println(user.getId());

System.out.println(user.getMoney());

}

 

static Object warp2Class(String sql,Class<?> clazz)throws SQLException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

Object destObject = clazz.newInstance();

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

if(rs.next()){

if(!rs.isLast())

throw new SQLException("查询记录超过一行,不能使用Map结果集");

Method method = null;

for(int i=1;i<=rsmd.getColumnCount();i++){

method = clazz.getMethod(parseMethodName(rsmd.getColumnLabel(i)),new Class[]{rs.getObject(i).getClass()});

method.invoke(destObject, new Object[]{rs.getObject(i)});

}

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

return destObject;

}

static String parseMethodName(String columnName){

String temp = "set";

temp+=columnName.substring(0, 1).toUpperCase();

temp+=columnName.substring(1);

return temp;

}

}

 

执行结果为:

test

2011-06-02

0

6000.0

15.3 将结果集封装为List

封装为List相比之下较为复杂,需要用到15.2节中的一些知识,如果认真看过15.2节的讲解我们会比较容易的读懂这一小节中的代码

package com.wangwenjun.jdbc.template;

 

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

import org.junit.Test;

 

public class ResultList {

@Test

public void warp2Object() throws SecurityException, IllegalArgumentException, SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{

List<Object> userList =warp2Class("select * from user",User.class);

for(Iterator<Object> it = userList.iterator();it.hasNext();){

User user  = (User) it.next();

System.out.println(user.getName()+"|"+user.getBirthday()+"|"+user.getMoney()+"|"+user.getId());

}

}

 

static List<Object> warp2Class(String sql,Class<?> clazz) throws SQLException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException{

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

List<Object> lists = new ArrayList<Object>();

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

while(rs.next()){

Object destObject = clazz.newInstance();

Method method = null;

for(int i=1;i<=rsmd.getColumnCount();i++){

method = clazz.getMethod(parseMethodName(rsmd.getColumnLabel(i)),new Class[]{rs.getObject(i).getClass()});

method.invoke(destObject, new Object[]{rs.getObject(i)});

}

lists.add(destObject);

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

return lists;

}

static String parseMethodName(String columnName){

String temp = "set";

temp+=columnName.substring(0, 1).toUpperCase();

temp+=columnName.substring(1);

return temp;

}

}

执行结果为:

test|2011-06-02|6000.0|0

test|2011-06-02|6000.0|1

15.4 策略模式的应用

前面的三个小节中,我们通过将结果集封装成三种不同的结构体演示了如何通过反射,结果集的元数据将数据库对象转换成Java对象,基本功能均已实现,但是存在一个问题,就是我们大多时候使用了强类型转换,并且没有使用统一的接口去管理三个不同的封装形式,如果要我们自己编写API供别人使用,这将是一个很不太好的方式。

在本节中我们通过一个统一的接口来作为抽象,三种不同的实现作为继承,这样使用者只需要通过接口进行编程即可。

接口ResultHandler.java

package com.wangwenjun.jdbc.template;

 

public interface ResultHandler<T> {

 

public  T  handler(String sql,Class<?> clazz) throws SQLException;

}

 

分别修改上述三种封装代码

15.4.1 Map结果集策略模式应用

package com.wangwenjun.jdbc.template;

 

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

import java.util.HashMap;

import java.util.Map;

 

public class ResultMap implements ResultHandler<Map<String, Object>> {

 

public Map<String, Object> handler(String sql, Class<?> clazz)

throws SQLException {

Map<String, Object> map = null;

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

int count = rsmd.getColumnCount();

if (rs.next()) {

if (!rs.isLast())

throw new SQLException("查询记录超过一行,不能使用Map结果集");

map = new HashMap<String, Object>();

for (int i = 1; i <= count; ++i) {

map.put(rsmd.getColumnLabel(i), rs.getObject(i));

}

}

} finally {

ConnFactory.close(conn, stmt, rs);

}

 

return map;

}

 

}

15.4.2 Bean结果集策略模式应用

package com.wangwenjun.jdbc.template;

 

import java.lang.reflect.Method;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

 

public class ResultBean<T> implements ResultHandler<T>{

 

private String parseMethodName(String columnName){

String temp = "set";

temp+=columnName.substring(0, 1).toUpperCase();

temp+=columnName.substring(1);

return temp;

}

 

@SuppressWarnings("unchecked")

public T handler(String sql, Class<?> clazz) throws Exception {

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

T t = (T) clazz.newInstance();

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

if(rs.next()){

if(!rs.isLast())

throw new SQLException("查询记录超过一行,不能使用Map结果集");

Method method = null;

for(int i=1;i<=rsmd.getColumnCount();i++){

method = clazz.getMethod(parseMethodName(rsmd.getColumnLabel(i)),new Class[]{rs.getObject(i).getClass()});

method.invoke(t, new Object[]{rs.getObject(i)});

}

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

return t;

}

}

15.4.3 List结果集策略模式应用

package com.wangwenjun.jdbc.template;

 

import java.lang.reflect.Method;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.util.ArrayList;

import java.util.List;

 

public class ResultList<T> implements ResultHandler<List<T>>{

private String parseMethodName(String columnName){

String temp = "set";

temp+=columnName.substring(0, 1).toUpperCase();

temp+=columnName.substring(1);

return temp;

}

 

@SuppressWarnings("unchecked")

public List<T> handler(String sql, Class<?> clazz) throws Exception {

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

List<T> lists = new ArrayList<T>();

try {

conn = ConnFactory.getConnection();

stmt = conn.prepareStatement(sql);

rs = stmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

while(rs.next()){

T destObject = (T) clazz.newInstance();

Method method = null;

for(int i=1;i<=rsmd.getColumnCount();i++){

method = clazz.getMethod(parseMethodName(rsmd.getColumnLabel(i)),new Class[]{rs.getObject(i).getClass()});

method.invoke(destObject, new Object[]{rs.getObject(i)});

}

lists.add(destObject);

}

} finally{

ConnFactory.close(conn, stmt, rs);

}

return lists;

}

}

15.4.4 单元测试

package com.wangwenjun.jdbc.template;

 

import java.util.List;

import java.util.Map;

 

import org.junit.Assert;

import org.junit.Test;

 

public class ContextTest {

 

@Test

public void mapWarp()throws Exception{

ResultHandler<Map<String, Object>> handler = new ResultMap();

Map<String,Object> maps=handler.handler("select * from user where id=1",null);

System.out.println(maps);

}

@Test

public void beanWarp()throws Exception{

ResultHandler<User> handler = new ResultBean<User>();

User user = handler.handler("select * from user where id=1", User.class);

Assert.assertEquals("test", user.getName());

}

 

@Test

public void ListWarp()throws Exception{

ResultList<User> handler = new ResultList<User>();

List<User> lists = handler.handler("select * from user", User.class);

Assert.assertEquals(2, lists.size());

}

}

 

测试结果为:

 

15.5 模板模式的应用

模板模式其实我们的代码中已经有过多次的展示,在获取数据库连接,释放资源等等地方,这里就不再演示。

第十六节 近期推出

本来要总结一下JVM原理ClassLoader的学习知识,但是一位友人说他急需一个关于PLSQL的资料,所以我准备在接下来的时间将PLSQL的基本概念和用法做一个整理,接下来将进行dbutils源码分析和springJdbc高级支持,也算是对JDBC技术的一个进阶吧!希望大家到时候关注

 

1 0