第二十章 JDBC DAO设计
Connection的含义 Connection表示了一个和数据库的链接,底层需要有操作系统的Socket支持,所以Connection是一种资源,既然是一种资源,就需要按照建立,打开,使用,关闭的顺序合理的使用。
Connection是Java数据库操作的基础,是进行一系列操作的基础,所有的派生的操作,例如Statement,PreparedStatement,ResultSet等都由Connection直接或者间接的衍生。
如何获得Connection呢?
方法一,使用DriverManager类来获取,前提条件是数据库驱动程序需要在classpath下(即使用数据库链接的程序按照Java的方式可以访问到)。
Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@192.168.0.1:1521:ORCL", user, pwd );
方法二,使用数据库连接池来获取
什么是数据库连接池呢,数据库连接池是标准JavaEE容器的一种服务,例如Webspher,Weblogic,Tomcat等,容器预先建立一些数据 库链接,以便应用程序使用的时候从中借取,注意有借有还,当应用程序使用完了之后会将数据库链接还回连接池。(数据源配置请参考其他文档)
使用连接池的好处是,可以预先建立链接,减小在数据库获取上的相对时间。
使用连接池获取数据库链接的方式为:
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
Connection conn = ds.getConnection();
由于在配置数据库连接池的时候已经定义了URL,用户名,密码等信息,所以在程序中使用的时候不需要传入这些信息。
ConnectionManager定义 Connection用来专门管理数据库链接,通常情况下ConnectionManager只有一个方法,调用这个方法将返回一个Connection 的实例。通过ConnectionManager可以封装Connection的获取方式(例如开发的时候使用DriverManager,运用的时候使 用DataSource的方式,但是不需要修改ConnectionManager之外的其他代码)和追加Connection获取之前之后的操作(例如 针对Connection的属性的设置)。
下面的代码是一个ConnectionManager的代码示例:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
public static Connection getConnection() throws DaoException {
Connection conn = null;
try {
conn = DriverManager.getConnection("", "", "");
} catch (SQLException e) {
throw new DaoException("can not get database connection", e);
}
return conn;
}
}
如果需要从开发模式变为运用模式,只需要将上述代码修改为:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
public static Connection getConnection() throws DaoException {
Connection conn = null;
try { Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("jdbc/dsname"); conn = ds.getConnection(); } catch(NamingException e) { throw new DaoException("can not find datasource", e); }catch (SQLException e) {
throw new DaoException("can not get database connection", e);
}
return conn;
}
}
如果需要预先设定Connection的一些属性,也可以在上述代码中设定,例如:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
public static Connection getConnection() throws DaoException {
Connection conn = null;
try {
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/dsname");
conn = ds.getConnection();
conn.setAutoCommit(false); } catch(NamingException e) {
throw new DaoException("can not find datasource", e);
}catch (SQLException e) {
throw new DaoException("can not get database connection", e);
}
return conn;
}
}
CommonDao定义属性和构造方法 通常情况下,CommonDao要有一个Connection的引用。所有一个CommonDao的实例的所有方法的调用都需要依赖于这个 Connection。需要一个Connection的另外一个原因是如果各个方法需要保证在一个事务环境中(上下文中),必须保证所有的操作都在一个 Connection上。
构造方法通常需要将类型为Connection的属性实例化,例如:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
public class CommonDao {
private Connection conn;
public CommonDao() throws DaoException {
this.conn = ConnectionManager.getConnection();
}
}
事务方法 begin()
开始一个事务,调用CommonDao的begin方法之后,所以的后续操作将会在一个事务环境内,要么全部提交,要么全部回滚。
commit()
提交一个事务,必须在begin调用之后调用。且和rollback方法互斥。
rollback()
回滚一个事务,必须在begin方法调用之后调用。且和commit方法互斥。
事务的实现有两种方法,一种是使用基于单一Connection的事务,另外一种方法是使用容器的JTA(Java Transaction API)。需要注意的是第一种方法可以在任何环境下使用,但是只能是针对单一的数据库链接。第二种方法智能在支持JTA的Java EE容器中使用(例如Websphere,Weblogic等,Tomcat默认不支持),但是支持多个Connection实例。
第一种方法代码为:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
import java.sql.SQLException;
public class CommonDao {
private Connection conn;
public CommonDao() throws DaoException {
this.conn = ConnectionManager.getConnection();
}
public void begin() throws DaoException{
if(conn != null) {
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
throw new DaoException("can not begin transaction", e);
}
} else {
throw new DaoException("connection not opened!");
}
}
public void commit() throws DaoException {
try {
if (conn != null && !conn.getAutoCommit()) {
conn.commit();
conn.setAutoCommit(true);
} else {
if (conn == null) {
throw new DaoException("connection not opened!");
} else {
throw new DaoException("first begin then commit please!");
}
}
} catch (SQLException e) {
throw new DaoException("can not commit transaction!", e);
}
}
public void rollback() throws DaoException {
try {
if (conn != null && !conn.getAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
} else {
if (conn == null) {
throw new DaoException("connection not opened!");
} else {
throw new DaoException("first begin then rollback please!");
}
}
} catch (SQLException e) {
throw new DaoException("can not rollback transaction!", e);
}
}
}
第二种我们在使用DAO的实例中介绍如何使用(@TODO)
新建两个DAO,做不同的操作,使用JTA保证事务完整。
查询方法 查询方法也许是CommonDao最常用的方法,查询方法需要将数据库的结果返回给画面。返回值我们一般不使用ResultSet,因为 ResultSet依赖于Connection,如果Connection关闭,ResultSet将不再有效,所以我们通常将ResultSet转变为 一个List之后返回。
在说明查询方法之前,我们先说说如何将数据库中的内容放在List中,我们使用一个List表示一个查询结果集合,使用一个Map表示集合中的一行,Map的key表示数据库表的字段名字,Value表示数据库字段的内容。代码为:
private List convert(ResultSet rs) throws DaoException {
// record list
List retList = new ArrayList();
try {
ResultSetMetaData meta = rs.getMetaData();
// column count
int colCount = meta.getColumnCount();
// each record
while (rs.next()) {
Map recordMap = new HashMap();
// each column
for (int i = 1; i <= colCount; i++) {
// column name
String name = meta.getColumnName(i);
// column value
Object value = rs.getObject(i);
// add column to record
recordMap.put(name, value);
}
// ad record to list
retList.add(recordMap);
}
} catch (SQLException ex) {
throw new DaoException("can not convert result set to list of map", ex);
}
return retList;
}
为了避免Sql注入的安全问题,我们通常使用PreparedStatement,在使用PreparedStatement的时候涉及到如何将传入参数设置到PreparedStatement上面,参看以下的共通方法:
private void apply(PreparedStatement pstmt, List params) throws DaoException {
try {
// if params exist
if (params != null && params.size() > 0) {
// parameters iterator
Iterator it = params.iterator();
// parameter index
int index = 1;
while(it.hasNext()) {
Object obj = it.next();
// if null set ""
if (obj == null) {
pstmt.setObject(index, "");
} else {
// else set object
pstmt.setObject(index, obj);
}
//next index
index++;
}
}
} catch (SQLException ex) {
throw new DaoException("can not apply parameter", ex);
}
}
接着我们继续说我们的查询方法,有了上述两个方法,我们的查询方法就非常简单了:
public List query(String sql, List params) throws DaoException {
List result = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(sql);
this.apply(pstmt, params);
rs = pstmt.executeQuery();
result = this.convert(rs);
} catch (SQLException ex) {
throw new DaoException("can not execute query", ex);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// nothing
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
// nothing
}
}
}
return result;
}
特殊的查询方法(返回单值) 有时候为了方便使用,我们需要返回单值的产寻方法,例如 select max(id) from table_a, select count(id) from table_b等。以下的代码使用了上述通用的查询方法,代码为:
public Object queryOne(String sql, List params) throws DaoException {
List list = this.query(sql, params);
if(list == null || list.size() == 0) {
throw new DaoException("data not exist");
} else {
Map record = (Map)list.get(0);
if(record == null || record.size() == 0 ) {
throw new DaoException("data not exist");
} else {
return record.values().toArray()[0];
}
}
}
更新,删除,插入方法 由于在JDBC中这三个方法都是用了一个execute完成,所以这里我们也使用一个方法来完成这些功能。代码为:
public int execute(String sql, List params) throws DaoException {
int ret = 0;
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
this.apply(pstmt, params);
ret = pstmt.executeUpdate();
}catch(SQLException ex) {
throw new DaoException("", ex);
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
// nothing.
}
}
}
return ret;
}
批处理方法(查询) 有些时候为了便于操作,需要一次查询多条SQL语句,我们称之为批处理,实现参看以下方法,其中为了和query方法做区分,将参数和返回值都改为了数组形式。
public List[] queryBatch(String[] sqlArray, List[] paramArray) throws DaoException {
List rets = new ArrayList();
if(sqlArray.length != paramArray.length) {
throw new DaoException("sql size not equal parameter size");
} else {
for(int i = 0; i < sqlArray.length; i++) {
String sql = sqlArray[i];
List param = paramArray[i];
List ret = this.query(sql, param);
rets.add(ret);
}
return (List[])rets.toArray();
}
}
批处理方法(更新) 有些时候需要一次更新多条Sql语句,为了便于操作,添加了批处理更新操作,参看以下代码,为了和更新方法区分,将参数和返回值都改为了数组形式。
public int[] executeBatch(String[] sqlArray, List[] paramArray) throws DaoException {
List rets = new ArrayList();
if(sqlArray.length != paramArray.length) {
throw new DaoException("sql size not equal parameter size");
} else {
for(int i = 0; i < sqlArray.length; i++) {
int ret = this.execute(sqlArray[i], paramArray[i]);
rets.add(new Integer(ret));
}
int[] retArray = new int[rets.size()];
for(int i = 0; i < retArray.length; i++) {
retArray[i] = ((Integer)rets.get(i)).intValue();
}
return retArray;
}
}
资源释放
由于CommonDao有一个Connection的属性,且Connection属于稀缺资源,所以在CommonDao不需要在使用的时候需要显示的关闭Connection。代码如下:
public void close() throws DaoException{
try {
if (conn != null && conn.getAutoCommit()) {
conn.close();
} else {
if(conn == null) {
throw new DaoException("can not close null connection, first new then close");
} else {
throw new DaoException("transaction is running, rollbakc or commit befor close please.");
}
}
} catch (SQLException ex) {
throw new DaoException("Can not close common dao");
}
}
JDBC工具类(JDBCUtil Class) 在上述的代码中我们看到有很多的无用的处理,例如:
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
// nothing.
}
}
为什么要有这些处理呢?说先这些处理发生的位置都是在正常处理完成之后,这些处理(例如pstmt.close())即使失败也没有影响,这个时候我们需 要做上述的无用处理,这正是JDBC API的一个小小的瑕疵。我们通常使用一个特殊的静态工具来来做补充,例如:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCUtil {
public void safelyClose(Connection conn) {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
//
}
}
}
public void safelyClose(PreparedStatement pstmt) {
if(pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
//
}
}
}
public void safelyClose(ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
//
}
}
}
}
异常处理
也许细心的你已经发现了一个问题,为什么所有抛出异常的地方我们都是将SQLException包装在了DaoException之内抛出呢,为什么不直 接抛出SQLException呢?有两个原因,第一,可以细化,分类Exception抛出合适的异常,添加合适的消息,第二,隔离和Dao和业务逻辑 的耦合,可以方便的修改Dao层而不会影响到业务逻辑层。另外需要注意,DaoExcetion中可以包含SQLException,这个时候可以为客户 提供更详细的错误信息,例如ORA-12524等内容,但是很少见到。
package com.jpleasure.jdbc.dao;
public class DaoException extends Exception {
public DaoException() {
super();
}
public DaoException(String message, Throwable cause) {
super(message, cause);
}
public DaoException(String message) {
super(message);
}
public DaoException(Throwable cause) {
super(cause);
}
}
分享到:
相关推荐
这份"Java语言程序设计(梁勇)第21-30章全部完整习题答案"是学习Java编程时的宝贵资源,它涵盖了高级特性和面向对象编程的关键概念。 在第21章,主题可能涉及到多线程,这是Java的一个强大特性。通过创建线程,...
JAVA2核心技术(第二版中文PDF)本书一共17章。 目录见下: 第一章 java简介 第二章 java编程环境 第三章 java基本编程结构 第四章 对象与类 第五章 进一步介绍OOP:继承 第六章 用AWT进行图形程序设计 第七章 用AWT...
JAVA2核心技术(第二版中文PDF)本书一共17章。 目录见下: 第一章 java简介 第二章 java编程环境 第三章 java基本编程结构 第四章 对象与类 第五章 进一步介绍OOP:继承 第六章 用AWT进行图形程序设计 第七章 用AWT...
JAVA2核心技术(第二版中文PDF)本书一共17章。 目录见下: 第一章 java简介 第二章 java编程环境 第三章 java基本编程结构 第四章 对象与类 第五章 进一步介绍OOP:继承 第六章 用AWT进行图形程序设计 第七章 用AWT...
第二章 表示逻辑 第三章 高级servlet 技术 第四章 使用 WebLogic Server JSP 第五章 WebLogic Server JDBC 和 JTA 第六章 远程方法调用和分布式命名 第七章 使用 Java 消息服务进行企业消息传递 第八章 使用会话企业...
第二章 任务概述 11 2.1 系统目标 11 2.2 系统特点 11 2.3基本流程 11 2.4 费用 12 第三章 详细设计 14 3.1 程序系统的结构 14 3.2 子功能模块图 14 3.2.1 会员功能 14 3.2.2 后台管理 15 3.2.3 前台信息 15 3.2.4 ...
第20章 高级安全性——虚拟专用数据库 第21章 高级安全性:透明数据加密 第22章 使用表空间 第23章 用sql*loader 加载数据 第24章 使用data pump export 和data pump import 第25章 访问远程数据 第...
第二章 开发工具和环境简介2 2.1 Java Server Page 简介2 2.2 Microsoft SQLServer2000 简介2 2.3 JDBC 驱动程序简介-4 2.4 JavaBeans 简介5 2.5 JAVA 简介-5 2.6 电子商务简介8 第三章 书店售书系统功能分析9 3.1...
《JSP程序开发范例宝典》第十二章聚焦于JSP(Java Server Pages)的高级应用和实战技巧。在这一章中,我们通常会深入探讨以下关键知识点: 1. **自定义标签库(Custom Tags)**:JSP 2.0引入了自定义标签库的概念,...
这个名为"javaweb程序设计源代码(共20章)ch4"的资源显然是一个教学或学习资料,专注于第4章的内容。在JavaWeb开发中,第4章可能涉及的是核心概念和技术,如Servlets、JSP(JavaServer Pages)、HTTP协议以及MVC...
第20章 使用表空间 第21章 用SQL*Loader加载数据 第22章 使用Data Pump导入和导出 第23章 访问远程数据 第24章 使用物化视图 第25章 使用Oracle Text进行文本搜索 第26章 使用外部表 第27章 使用回闪查询 第28章 回...
第20章 高级安全性——虚拟专用数据库 第21章 高级安全性:透明数据加密 第22章 使用表空间 第23章 用sql*loader 加载数据 第24章 使用data pump export 和data pump import 第25章 访问远程数据 第...
第二章 任务概述 11 2.1 系统目标 11 2.2 系统特点 11 2.3基本流程 11 2.4 费用 12 第三章 详细设计 14 3.1 程序系统的结构 14 3.2 子功能模块图 14 3.2.1 会员功能 14 3.2.2 后台管理 15 3.2.3 前台信息 15 3.2.4 ...
第20章 高级安全性——虚拟专用数据库 第21章 高级安全性:透明数据加密 第22章 使用表空间 第23章 用sql*loader 加载数据 第24章 使用data pump export 和data pump import 第25章 访问远程数据 第...
本书《Java语言程序设计 进阶版 第十版》提供的复习题及其答案,旨在帮助读者巩固并深化对Java语言的理解。下面将详细讨论复习题中涉及的一些关键知识点。 1. **面向对象编程**:Java是一种面向对象的语言,复习题...
3. **MVC(Model-View-Controller)模式**:这是一种常见的Web应用架构模式,"chap14"的练习可能涉及到创建Model类来处理业务逻辑,编写Controller类来协调模型和视图,以及设计View来显示结果。 4. **JSTL(Java...
《Java程序设计实用教程(第二版)》是耿祥义和张跃平两位专家共同编著的一本经典Java学习教材,旨在帮助初学者和进阶者掌握Java编程的核心概念和技术。该书通过丰富的例题和上机实践模板,使得读者能够深入理解并...
第二章至第十七章涵盖了从基础语法到高级特性的大量内容。以下是这些章节中的关键知识点: 1. **第二章:对象与类** - 这一章讲解了面向对象编程的基础,包括类的定义、对象的创建、封装、继承和多态等概念。了解...
1. **面向对象编程**:Java是一种面向对象的语言,第十二章可能深入讲解了类的设计原则,包括单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则和接口隔离原则。此外,可能还讨论了抽象类、接口、继承和多态...