在使用Java语言进行和数据库有关的的应用开发中,通常都使用JDBC来进行和数据库的交互,其中有一个关键的概念就是Connection(链接),它在Java中是一个类,表明了一个通道。经过它,使用数据的应用就能够从数据库访问数据了。 html
对于一个简单的数据库应用,因为对于数据库的访问不是很频繁。这时能够简单地在须要访问数据库时,就新建立一个链接,用完后就关闭它,这样作也不会带来什 么明显的性能上的开销。可是对于一个复杂的数据库应用,状况就彻底不一样了。频繁的创建、关闭链接,会极大的减低系统的性能,由于对于链接的使用成了系统性 能的瓶颈。 java
本文给出的方法能够有效的解决这个问题。在本方法中提出了一个合理、有效的链接管理策略,避免了对于链接的随意、无规则的使用。该策略的核心思想是:链接 复用。经过创建一个数据库链接池以及一套链接使用管理策略,使得一个数据库链接能够获得高效、安全的复用,避免了数据库链接频繁创建、关闭的开销。另外, 因为对JDBC中的原始链接进行了封装,从而方便了数据库应用对于链接的使用(特别是对于事务处理),提升了开发效率,也正是由于这个封装层的存在,隔离 了应用的自己的处理逻辑和具体数据库访问逻辑,使应用自己的复用成为可能。 数据库
回页首 设计模式
我参与的项目是开发一个网管系统,不可避免的要和数据库打交道。刚开始时,因为对于数据库的访问不是很频繁,对于数据库链接的使用就是简单的须要时就创建,用完就关闭的策略,这很符合XP(eXtreme Programming)的口号:"Do the Simplest Thing that Could Possibly Work"。确实,开始时工做的很好。随着项目的进展,对于数据库的访问开始变的频繁,问题就暴露出来了,原先的经过简单地获取和关闭数据库链接的方法将很大的影响系统的性能,这种影响是因为数据库资源管理器进程频繁的建立和摧毁那些链接对象而引发的。 安全
此时,就有必要对数据库访问方法进行重构(refactoring),由于咱们确实须要进行改进,来提升系统的性能。 多线程
回页首 并发
能够看出,问题的根源就是因为对于链接资源的低效管理形成的。对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造 成的问题的。把该模式应用到数据库链接管理领域,就是创建一个数据库链接池,提供一套高效的链接分配、使用策略,最终目标是实现链接的高效、安全的复用。 框架
第一步,就是要创建一个静态的链接池,所谓静态是指,池中的链接是在系统初始化时就分配好的,而且不可以随意关闭的。Java中给咱们提供不少容器类能够 方便的用来构建链接池,如:Vector、Stack等。在系统初始化时,根据配置建立链接并放置在链接池中,之后所使用的链接都是从该链接池中获取的, 这样就能够避免链接随意创建、关闭形成的开销(固然,咱们没有办法避免Java的Garbage Collection带来的开销)。 性能
有了这个链接池,下面咱们就能够提供一套自定义的分配、释放策略。 spa
当客户请求数据库链接时,首先看链接池中是否有空闲链接,这里的空闲是指,目前没有分配出去的链接。若是存在空闲链接则把链接分配给客户,并做相应处理, 具体处理策略,在关键议题中会详述,主要的处理策略就是标记该链接为已分配。若链接池中没有空闲链接,就在已经分配出去的链接中,寻找一个合适的链接给客 户(选择策略会在关键议题中详述),此时该链接在多个客户间复用。
当客户释放数据库链接时,能够根据该链接是否被复用,进行不一样的处理。若是链接没有使用者,就放入到链接池中,而不是被关闭。
能够看出正是这套策略保证了数据库链接的有效复用。
数据库链接池中到底要放置多少个链接,链接耗尽后该如何处理呢?这时一个配置策略。通常的配置策略是,开始时,根据具体的应用需求,给出一个初始的链接池中链接的数目以及一个链接池能够扩张到的最大链接数目。本方案就是按照这种策略实现的。
本节将对上述解决方案中的关键细节进行详述,正是这些关键的策略保证了数据库链接复用的高效和安全。
3.2节中的分配、释放策略对于有效复用链接很是重要,咱们采用的方法也是采用了一个颇有名的设计模式:Reference Counting(引用记数)。该模式在复用资源方面用的很是普遍,咱们把该方法运用到对于链接的分配释放上。每个数据库链接,保留一个引用记数,用来 记录该链接的使用者的个数。具体的实现上,咱们采用了两极链接池,空闲池和使用池。空闲池中存放目前尚未分配出去被使用的链接,一旦一个链接被分配出 去,那么就会放入到使用池中,而且增长引用记数。
这样作有一个很大的好处,使得咱们能够高效的使用链接,由于一旦空闲池中的链接被所有分配出去,咱们就能够根据相应的策略从使用池中挑选出一个已经正在使 用的链接用来复用,而不是随意拿出一个链接去复用。策略能够根据须要去选择,咱们采用的策略比较简单:复用引用记数最小的链接。Java的面向对象特性, 使得咱们能够灵活的选择不一样的策略(提供一个不一样策略共用的抽象接口,各个具体的策略都实现这个接口,这样对于策略的处理逻辑就和策略的实现逻辑分离)。
前面谈到的都是关于使用数据库链接进行普通的数据库访问。对于事务处理,状况就变得比较复杂。由于事务自己要求原子性的保证,此时就要求对于数据库的操做 符合"All-All-Nothing"原则,即要么所有完成,要么什么都不作。若是简单的采用上述的链接复用的策略,就会发生问题,由于没有办法控制属 于同一个事务的多个数据库操做方法的动做,可能这些数据库操做是在多个链接上进行的,而且这些链接可能被其余非事务方法复用。
Connection自己具备提供了对于事务的支持,能够经过设置Connection的AutoCommit属性为false,显式的调用commit 或者rollback方法来实现。可是要安全、高效的进行Connection进行复用,就必须提供相应的事务支持机制。咱们采用的方法是:采用显式的事 务支撑方法,每个事务独占一个链接。这种方法能够大大下降对于事务处理的复杂性(若是事务不独占一条链接,那么要保证事务的原子性而且又不妨碍复用该连 接的其余和该事务无关的操做,基本上不可能,除非Connection类是你开发的),而且又不会妨碍链接的复用,由于隶属于该事务的全部数据库操做都是 经过这一个链接完成的,而且事务方法又复用了其余一些数据库方法。
在咱们的链接管理服务提供了显式的事务开始、结束(commit或者rollback)声明,以及一个事务注册表,用于登记事务发起者和事务使用的链接的 对应关系,经过该表,使用事务的部分和咱们的链接管理部分就隔离开,由于该表是在运行时根据实际的调用状况,动态生成的。事务使用的链接在该事务运行中不 能被复用。
当使用者须要使用事务方法时,首先调用链接管理服务提供的beginTrans方法,该方法主要处理流程以下(伪码描述):
public void beginTrans( ) { … conn = getIdleConnectionFromPoll( ); userId = getUserId( ); registerTrans(userId, conn); … }
在咱们的实现中,用户标识是经过使用者所在的线程来标识的。后面的全部对于数据库的访问都是经过查找该注册表,使用已经分配的链接来完成的。当事务结束时,从注册表中删除相应表项。
对于嵌套的事务如何处理呢?咱们采用的方法仍为引用记数,不过这里的引用记数是指的"嵌套层次",具体的细节,再也不赘述。
从上面的论述能够看出,普通的数据库方法和事务方法对于链接的使用(分配、释放)是不一样的,为了便于使用,对外提供一致的操做接口,咱们对链接进行了封 装:即普通链接和事务链接。在此,咱们利用了Java中的强大的面向对象特性:多态。普通链接和事务链接均实现了一个DbConnection接口,对于 接口中定义的方法,分别根据本身的特色做了不一样的实现,这样在对于链接的处理上就很是的一致了。
为了是咱们的链接管理服务有更大的通用性,就必需要考虑到多线程环境,即并发问题。在一个多线程的环境下,咱们必需要保证链接管理自身数据的一致性和链接 内部数据是一致性,还好Java提供对这方面的很好的支持(synchronized关键字),这样咱们就很容易使链接管理成为线程安全的。
本文给出了一个基本的链接管理框架,在其中使用了一些普遍使用的设计模式(资源池,引用记数等),使得高效、安全的复用数据库链接成为可能。固然,还有一 些问题没有考虑到,好比:没有实现对不一样种类的数据库的联合管理;没有提供定时检测机制,查询链接的状态等。另外在链接管理的使用包装上比起一些商用的系 统还显粗糙,可是底层的基理是一致的,因此经过本文相信对于这些商用的产品中的相关功能会有更好的理解。