使用Java做过商业应用开发的人想必一定用到过数据库。
不论具体方案是使用JDBC还是JDO还是直接使用J2EE提供的连接池又或者是使用Hibernate屏蔽了这一切,不变的原则是,数据库连接是有限的资源,为了实现数据持久化的高效和稳定,不应该不加控制地频繁创建数据库连接;更不能在建立连接(或从连接池中取出)并使用之后就简单地结束,而不做资源回收。即使这种资源有超时空闲的自动回收机制,这种做法也是不可接受的。
打住,随便发了点儿评论。言归正传,本文的标题虽然是讨论数据库连接释放的问题,但实质是借着这个话题说明一下设计模式在Java中的应用。
现在,让我们设想一个虚拟场景:当前项目的数据库连接方式采用了JDBC + 连接池。如此设想是因为大概这种实现的效率最高,且可以发挥的地方也最多。
系统启动阶段建立了数据库的连接池,里面预先放了很多的连接,供需要持久化的模块调用,并接收返还的连接。
这里,每块借用了连接的代码都要记得用完了之后一定得返还连接。否则,连接池里的连接用完了,系统就不得不新建连接,迟早会抛出连接无法创建的异常。
那么,第一种方案是,把所有的数据库操作放在一个try块里。然后在finally里执行返还连接的操作。这种方案好处是便于实现,目的单纯。对于只有一、两处数据库操作的应用来说,效果明显,维护起来也不困难。
但是假设应用中不只含有一个数据库,比如说是按照功能划分了安全管理、商品库存、销售记录、员工考勤等库(表空间),而实际上会用到数据操作的不下几十处,那么第一种方案就是有问题的了。尤其对于一次访问若干个库,比如说同时访问商品库存和销售记录,同时访问安全管理和员工考勤,这时候程序员要时刻牢记目前有哪些连接在使用,finally要释放哪些连接,即使项目完成了,也不可能通过某种便捷有效的手段保证所有的连接都能够正常释放。举个最简单的例子。
// ...
Connection connA = DbUtil.getConnA();
Connection connB = DbUtil.getConnB();
try {
// ...
} catch (Exception ex) {
// ...
} finally {
connA.close();
connB.close();
}
粗看上去,似乎两个连接都释放掉了。但实际并非如此,connB不一定会被关掉。假设在try块中,connA的服务器突然宕机了。这时候,在finally块里,connA.close();将会抛出异常,connB.close();的代码将不会被执行。
这时候内行说话了,只要在finally块中给每个close()方法套上try块就行了,像这样:
// ...
} finally {
try {
connA.close();
} catch (Exception ex) {
}
try {
connB.close();
} catch (Exception ex) {
}
}
}
这样改是没有毛病了,不过稍微有些冗长。再修改一下:
// ...
} finally {
DbUtil.close(connA);
DbUtil.close(connB);
}
}
// ...
public final class DbUtil
{
private DbUtil()
{
}
public static void close(Connection conn)
{
try {
conn.close();
} catch (Exception ex) {
}
}
}
那么如何保证几十个用到连接的地方都释放了呢?尤其C+P之后,编译通过也不说明逻辑没有问题。例如:
// ...
} finally {
DbUtil.close(connA);
DbUtil.close(connA); // C + P 后遗症,忘记修改变量名了
}
}
// ...
用了什么就要记住释放什么,这种事,一点儿安全感都没有。
那么,现在要隆重祭出设计模式这杆大旗了。
不过很不好意思地说,具体是什么模式,实际上我心里也没底。我先解释解释吧。
首先,设计目的是,要能够比较自由地使用某种资源,并且在停止使用资源之后必然做到自动的资源释放。
其次,设计思想是,把使用资源的这个操作变为整个操作中可以被替换的部分,整个流程应该是这样的:取得资源,使用资源,释放资源。其中使用资源是不断变化的,相应取得资源也有可能发生变化,但是释放资源不会有变化,而且这样的三步走流程也始终保持不变。
因此,我们可以设想有一个对象,创建的时候把资源和资源操作当作参数注入其中,然后执行三步走。这样,只要所有的数据库操作都使用这个对象来执行,我们就可以很确信的说,所有的资源必然都会被正确的释放。除非我们设计的对象有什么其他缺陷。不过这样的方案肯定能大大简化代码的维护,把散布在各处的数据持久化操作的维护转移到对某个类的维护。
由于Java没有闭包,因此只能使用对象模仿函数指针。代码如下:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* DbAdapter
* 数据库访问接口类
*/
public abstract class DbAdapter
{
/**
* 处理sql,并根据要求执行数据操作或查询
*/
static Object process(Connection conn, String sql, DbParamInjector injector, DbReader reader) throws SQLException
{
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(sql);
if (injector != null) {
if (reader != null) {
injector.setParams(pstmt);
rs = pstmt.executeQuery();
return reader.read(rs);
} else {
do {
injector.setParams(pstmt);
pstmt.execute();
} while (injector.hasNext());
return null;
}
} else {
if (reader != null) {
rs = pstmt.executeQuery();
return reader.read(rs);
} else {
pstmt.execute();
return null;
}
}
} finally {
// 释放数据库资源
DBUtil.close(conn, pstmt, rs);
}
}
}
/**
* DbParamInjector
* PreparedStatement的参数注入器
*/
abstract class DbParamInjector
{
abstract void setParams(PreparedStatement pstmt) throws SQLException;
/**
* 是否还有下一条待执行的数据操作,重载的时候要注意避免死循环
*/
boolean hasNext()
{
return false;
}
}
/**
* DbReader
* 数据结果集的读取器
*/
interface DbReader
{
Object read(ResultSet rs) throws SQLException;
}
使用例子如下:
// ...
String sql = "INSERT INTO TBL_USERLOG (LOGID, USERID, LOGDATE, LOGNOTE)"
+" VALUES (?, ?, ?, ?)";
DbParamInjector injector = new DbParamInjector() {
public void setParams(PreparedStatement pstmt) throws SQLException
{
int i = 1;
pstmt.setInt(i++, logId);
pstmt.setInt(i++, userId);
pstmt.setTimestamp(i++, new Timestamp(new Date().getTime()));
pstmt.setString(i++, new logNote);
}
};
try {
DbAdapter.process(conn, sql, injector, null);
} catch (SQLException ex) {
ex.printStackTrace();
}
// ...
当然,以上的代码只是符合某种数据库访问的需要,并不是普遍适用的例子。对于需要一边读入数据,一边更新表或者其它更为复杂模式的数据库使用,还是需要进一步改动作为引入参数的函数对象的设计和实现方式。这里就不进一步展开了。
OK,最后说一下我对其中有关的一些设计模式的理解。
首先,所谓命令模式,就是:
- 我接编程项目,这是一个抽象概念。
- 所有的编程项目都是一样的需求文档格式,一样的产品输出介质,这是统一的接口。
- 你需要实现一个Java项目,这是一个具体的请求。
- 于是,我雇了一个小伙计,帮你把项目做了。你把钱给我。这是一个用例。
如此,虽然我对外接收编程项目,但实质工作不是我做的,我把工作转嫁给别人去做。
我只不过规范了编程人员的需求输入和产品输出。并通过这个接口派发项目。
至于编程人员怎么开发,我也不知道。
我们的例子中,小伙计使用的需求文档格式就是DbParamInjector,如果有输出的话,处理输出的就是DbReader。
其次,所谓工厂模式,就是:
- 咖啡厂制造罐装咖啡。
- 一罐咖啡包含咖啡粉、植脂末、糖、添加剂、瓶子,包装纸。
- 装瓶的流程是确定的,原料还没到。
- 一旦原料定下来了,咖啡的风味就定了。
- 那么就先安排好工位,大家排排坐。
- 原料来了,一罐罐咖啡也出来了。
而我们的例子里面,就多了最后一步,把用剩下的咖啡粉扫扫起来,倒入原料池。
分享到:
相关推荐
2. 连接的获取与释放:提供接口供应用请求数据库连接,同时确保当连接使用完毕后能正确归还。 3. 连接的监控:监控连接池中的连接状态,如超时、空闲、活跃等,及时处理异常情况。 4. 连接的生命周期管理:根据配置...
为了有效地解决这些问题,可以采用设计模式中的单例模式来管理数据库连接。 #### 单例模式简介 单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要...
数据库连接池(connection pool)的工作原理是为了解决资源的频繁分配、释放所造成的问题。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时...
具体而言,文章重点介绍了在设计一个面向物流企业的电子商务系统——包括在线购物和配送管理系统时,如何实现数据库连接池和DAO模式。 #### 逻辑结构与功能结构 ##### 逻辑结构 从逻辑结构来看,系统被分为四个...
单实例模式数据库连接池是一种软件设计模式,主要用于提高数据库访问效率并降低系统资源消耗。在该模式下,整个应用程序仅创建一个数据库连接池实例,所有数据库操作共享这个实例中的连接资源。这种设计方式能够有效...
连接池的基本工作原理 ...我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。 更为重要的是我们可以通过连接池的治理机制监视数据库的连接的数量?使用情况,为系统开发?测试及性能调整提供依据。
在传统的JDBC编程中,开发者通常需要手动打开和关闭数据库连接,这不仅增加了代码复杂性,而且频繁的连接创建和释放也可能导致性能瓶颈。为了解决这个问题,JDBC引入了连接池的概念,通过代理模式实现了对数据库连接...
单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在IT行业中,尤其是在处理资源密集型任务如数据库连接时,单例模式被广泛应用。数据库连接池就是这种应用的一个典型例子。 ...
数据库连接池是一种对象池设计模式的应用,它维护了一组数据库连接,供多个应用程序共享。通过复用已建立的连接,避免了频繁创建和销毁连接带来的开销,从而提升了系统性能。数据库连接池还提供了诸如连接管理和资源...
在数据库连接池的场景下,代理对象负责实际数据库连接的创建、管理和释放,而应用程序只与代理对象交互,无需关心底层连接的细节。这样可以隔离应用程序和数据库连接管理的逻辑,提高代码的可维护性和扩展性。 代理...
### 数据库连接管理的重要性 在软件开发领域,尤其是在构建中大型系统时,数据库的应用不可或缺。数据库作为存储和处理数据的核心组件之一,其性能直接影响到整个系统的稳定性和响应速度。然而,若缺乏有效的数据库...
数据库连接的单例模式是一种设计模式,它在软件工程中被广泛应用,特别是在处理数据库连接时。这个模式的主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这样可以避免频繁创建和销毁数据库...
在本文中,我们将深入探讨C#中数据库连接的相关知识点,以及如何创建这样一个通用的数据库连接类。 首先,C#中的数据库连接通常基于ADO.NET(ActiveX Data Objects .NET)框架。ADO.NET提供了System.Data.SqlClient...
本资源提供的"通用java数据库连接程序"是一个可复用的组件,能够方便地在各种项目中集成,以实现与数据库的无缝交互。下面将详细阐述这个程序的主要组成部分及其工作原理。 1. **RelationalInfoDao.java**: 这个...
资源池设计模式是一种经典的软件设计模式,用于解决资源频繁分配与释放带来的性能瓶颈问题。对于数据库连接这种宝贵的资源来说,每次创建和销毁连接都会消耗大量的时间和系统资源。因此,通过使用数据库连接池来管理...
**资源池(Resource Pool)模式**:这一设计模式的核心在于通过维护一个可重复使用的资源集合,避免了资源的频繁创建与销毁所带来的性能开销。在数据库连接管理场景下,连接池技术正好符合这一模式的要求。 **连接池...
4. **ForkingDB**: 专为多进程环境设计的数据库连接池,可以确保每个子进程都有自己的数据库连接副本,避免了进程间共享连接带来的问题。 接下来,我们看看`psycopg2-latest.tar.gz`。这通常是一个包含最新版本的...
### Java操作数据库方式与设计模式应用 #### 一、Java操作数据库基础:JDBC **JDBC(Java Database Connectivity)**是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写...
不过,实际开发中,为了遵循MVC(Model-View-Controller)设计模式,通常会将数据库操作分离到JavaBean或Servlet中,使JSP仅负责展示视图,从而提高代码的可维护性和可扩展性。 此外,需要注意的是,为了确保代码能...