【SQL开源代码栏目提醒】:网学会员为广大网友收集整理了,Apusic应用服务器的数据源管理 - 技术总结,希望对大家有所帮助!
第 1 页 共 13 页 Apusic数据源管理 Apusic 1 在基于J2EE平台的应用开发中,大多数的应用都需要跟数据库打交道;而自从接触JDBC起,我们便不止一次的被告之:数据库资源是十分宝贵的系统资源,一定要谨慎使用。
但令人遗憾的是,在笔者见过的大部分跟数据库相关的应用开发中,针对数据库资源的使用总是充斥着这样或者那样的问题。
在本文中,笔者针对常见的一些错误或者不当的使用数据库资源的案例进行介绍与分析,并阐述金蝶Apusic应用服务器提供的一些增值特性,通过这些特性能够有效的避免某些错误的发生。
2 / 2.1 申请了数据库连接,却没有及时的关闭它,这几乎是最常见的数据库连接使用错误。
犯这种错误的原因有很多,以下是常见的一种低级错误: public void foo Connection conn getConnection Statement stmt null try conn getConnection stmt conn.createStatement catch Exception e finally closestmt conn 在上述案例中的第行
代码中,作者已经申请了一个Connection,但在第行
代码中,又申请了一个新的Connection,并且丢失了第一次申请的connection 第 2 页 共 13 页 Apusic数据源管理 的引用,至此,当程序每调一次foo方法,将导致申请一个新的Connection而没有释放它,如此一来,当数据库达到能够承受的最大连接数时,将导致整个应用的运行失败。
避免这种错误的方法有很多,譬如,可采用类似于FindBugs注1的
代码分析工具对应用的源码进行分析,找出可能产生错误的
代码。
此外,在应用中,我们需要非常频繁的对申请的数据库连接进行关闭与释放,此时,建议封装成某些工具类使用,并且要尽可能安全的关闭数据库连接。
下面,我们以关闭Statement及Connection的通用close方法的不同实现方案来比较: private void closeStatement stmt Connection conn try stmt.close conn.close catch Exception e 在上述
代码中,倘若第行
代码中的stmt为空,或者stmt.close方法出错并抛出异常,都将使第行
代码不能够正常调用,从而导致数据库连接无法释放,那么,更安全的写法应该是: private void closeStatement stmt Connection conn try ifstmtnull stmt.close catch Exception e try ifconnnull conn.close catch Exception e 第 3 页 共 13 页 Apusic数据源管理 在修订后的
代码中,我们可以看到,无论第行
代码中关闭stmt是否成功,程序都能够保证向下执行,从而正确的关闭conn。
这些常用的数据库资源操作公用类,可以使用Apache的Commons DbUtils注2组件。
2.2 不考虑事务上下文,任意的申请数据库连接资源,也是常见的一种不当用法。
但这种问题往往是难以克服的,根源在于Java是一种面向对象的语言,而数据库的事务却是一种批量化的操作过程。
我们以常见的“序列号”的实现方案为例:在某些应用场景中,我们需要一种自增长的整数型字段,但由于不同的数据库有不同的实现,所以,为达到各个数据库兼容的目的,我们常用的解决方案是,新建一张T_SEQUENCE表,它可能包含的字段有:NAME varchar100 CURRENT_VAL number10;其中,NAME存放序列的名称,而CURRENT_VAL存放序列的当前值。
假设某一业务对象Customer需要新增一笔记录时,为获得不重复且自增长的Customer ID,需要将T_SEQUENCE表中的与该业务表对应的序列号加1并更新,然后将更新后的值作为Customer的ID,如下述表格所示: T_SEQUENCE NAME CURRENT_VAL CUSTOMER 10 T_CUSTOMER ID CUSTOMER_NAME 9 Kevin 10 Mary 第 4 页 共 13 页 Apusic数据源管理 于是,在Java语言中,我们以面向对象的方法来实现,可能会是这样(常见写法,未必是最优实现): public class Customer public void sequencePlus Connection conn null Statement stmt null try conn getConnection stmt conn.createStatement String
sql update T_SEQUENCE set CURRENT_VAL CURRENT_VAL 1 where NAME CUSTOMER stmt.executesql catch Exception e e.printStackTrace finally DbUtils.closeQuietlystmt DbUtils.closeQuietlyconn public int getSequenceCurrentVal Connection conn null Statement stmt null ResultSet rset null int id 0 try conn getConnection stmt conn.createStatement String
sql select CURRENT_VAL from T_SEQUENCE where NAME CUSTOMER rset stmt.executeQuerysql if rset.next id rset.getInt1 catch Exception e e.printStackTrace finally DbUtils.closeQuietlyconn stmt rset return id 第 5 页 共 13 页 Apusic数据源管理 public void addCustomerString name Connection conn null PreparedStatement stmt null ResultSet rset null try sequencePlus int id getSequenceCurrentVal conn getConnection stmt conn.prepareStatement insert into T_CUSTOMERID CUSTOMER_NAME values stmt.setInt1 id stmt.setString2 name null : name stmt.execute catch Exception e e.printStackTrace finally DbUtils.closeQuietlystmt DbUtils.closeQuietlyconn 针对这种应用场景,我们首先需要认识到:上述的三个方法应该属于同一个数据库事务,否则,在并发情况下,将出现由于主键重复而导致数据插入失败的情况。
但同时,我们也需要看到:即便上述三个方法的执行位于同一个事务中,但三个方法使用的是不同的数据库连接,虽然在sequencePlus方法中将T_SEQUENCE表中的数据加1 ,但在事务并未提交的情况下,由于Connection隔离级别的原因,在getSequenceCurrentVal方法中,是看不到sequencePlus方法中更新以后的数据的,这样,也将导致数据插入失败,因为主键势必跟旧有ID值重复。
因此,传统的编程方法中,为克服上述问题,只有在上述的方法中使用同一个Connection,才能够保证业务数据的正确。
但这样一来,将影响我们以OO方法分析问题时的“纯洁”性,很容易让人厌倦。
第 6 页 共 13 页 Apusic数据源管理 2.3 Connection 另外一种常见的不当编程模式是将Connection作为类的成员变量。
一般来说,针对Connection,我们采取的策略是:用时再申请,用完立即释放。
而将Connection作为成员变量,将是对该规则的严重挑战,容易引起若干编程错误。
举例而言:成员变量级的Connection,何时创建?何时释放?倘若在每一个方法体内进行Connection的创建与释放,那么将Connection作为成员变量又失去了意义;倘若在类的构造期内进行Connection的创建,那么又在何时释放它呢?因为在Java语言内,你是无法控制对象的生命周期的。
将Connection作为成员变量还会产生另外一个问题:资源的闲臵浪费。
因为在申请连接以后,该资源将在这个对象的生命之期之内一直有效,即使该对象处于非使用状况,这无疑是一种资源的浪费。
更有甚者,倘若这种对象过多,将造成数据库达到最大连接数,造成应用运行失败。
3 Apusic 金蝶Apusic应用服务器支持业界主流的各种数据库,在Apusic应用服务器之内进行数据源的配臵与使用都非常简单,同时,它提供了许多增值特性,能够为应用的正常运行提供额外的保障。
3.1 我们注意到:java.
sql.Connection是一个Interface,那么,真正实现这个接口的类是什么呢? 我们可以做一个简单的测试案例,在普通的Java Application中,调用如下方法: 第 7 页 共 13 页 Apusic数据源管理 public void showConnection Connection conn null try Class.forNameoracle.jdbc.driver.OracleDriver conn DriverManager.getConnection jdbc:oracle:thin:localhost:1521:KEVINORA system manager System.out.printlnConnection Class is: conn.getClass.getName catch Exception e e.printStackTrace finally DbUtils.closeQuietlyconn 得到的输出结果是:Connection Class is: oracle.jdbc.driver.T4CConnection 而在Apusic应用服务器中运行如下方法:public void showConnection Connection conn null try Context ctx new InitialContext ds DataSource ctx.lookupjdbc/oracle conn ds.getConnection System.out.printlnConnection Class is: conn.getClass.getName catch Exception e e.printStackTrace finally DbUtils.closeQuietlyconn 得到的输出结果是:Connection Class is: com.apusic.jdbc.adapter.ConnectionHandle 明明用相同的JDBC Driver连接同一个数据库,为什么取得的Connection却是不同的类呢?事实上,通过Apusic应用服务器获得的数据库连接其实只是一个逻辑连接,真正的物理连接隐藏在该逻辑连接之内,这是一个典型的Delegate模式,而恰恰是这个模式,通过 Apusic应用服务器对数据源进行管理,将给我们的应用开发带来很多好处: 第 8 页 共 13 页 Apusic数据源管理 3.2 我们以一个最简单的Stateless Session Bean为例: public class SimpleBean implements SessionBean public void foo Connection conn null try Context ctx new InitialContext DataSource ds DataSource ctx.lookupjdbc/oracle conn ds.getConnection System.out.printlnnot release connection catch Exception e e.printStackTrace finally // Not close the connection // DbUtils.closeQuietlyconn SimpleBean中的foo方法的事务属性设臵为Required,在该方法中,我们申请了一个数据库连接,但并没有释放它,在运行之前,我们通过SQLPlus观察Oracle数据库的Session,得到的结果是: Oracle Session 而在执行完SimpleBean的foo方法之后,我们再次观察Oracle数据库的Session,得到的结果是: 第 9 页 共 13 页 Apusic数据源管理 由此,我们可以得知:即便由于程序的书写错误,没能够释放申请的数据库连接,但Apusic应用服务器在事务完成之后,能够把该事务上下文中申请的物理连接主动释放,这对提升应用的容错性带来一定的好处。
3.3 jsp/servletjsp/servlet 同事务中申请的数据库连接会主动释放一样,在jsp/servlet中申请的数据库物理连接,当jsp/servlet运行完毕以后,如果用户没有释放这些连接,Apusic应用服务器也将予以主动释放。
读者可以尝试自己做一个案例:在jsp中申请一个连接,故意不释放,在jsp执行完毕以后,可以通过SQLPlus或者Apusic性能监控工具,查看连接是否已经被应用服务器主动释放。
由上述两节内容我们可以看到,Apusic应用服务器能够有效避免2.1节中所描述的问题。
3.4 Connection Sharing 通过共享连接可以更有效地使用资源及提高性能,并且可以防止连接之间的资源锁定问题。
第 10 页 共 13 页 Apusic数据源管理 例如两个EJB组件A和B,它们的事务属性都设臵为Required。
在调用EJB A的方法时打开了一个数据库连接,并对数据库中的某个表进行了更新操作,而在关闭连接之前EJB A调用了EJB B的某个方法,同样EJB B打开同一个数据库的连接,也对数据库中同一个表进行了更新操作。
倘若没有连接共享机制,这两个连接指向的是两个不同的物理连接,在其上执行的数据库操作将会互相锁定,而这种死锁状态是无法恢复的。
现在有了连接共享机制可以有效地解决这个问题。
在EJB A和B中所获得的连接对象实际上都指向同一个物理连接。
这一个过程可以简单描述如下: con1 getConnection Transaction.begin perform database operation on con1 con2 getConnection perform database operation on con2 con2.close Transaction.commit con1.close 无论两个连接是在事务边界之内或之外打开和关闭都没有问题。
只有在一个事务边界之内连接才会被共享,如果一个连接是在事务边界之外打开的,那么在事务开始时会将此连接参与到事务中,并找到一个具有正确事务场景的物理连接和连接对象相关联。
在离开事务场景之后如果连接对象仍未关闭,则将其关联到一个不具有事务场景的物理连接。
可以在部署描述中指定一个资源引用的res-sharing-scope属性来允许或禁止连接共享,属性值shareable为允许共享,unshareable为禁止共享,缺省情况下为允许共享。
回到2.2节中Customer那个测试案例,我们已经说过,Customer的 第 11 页 共 13 页 Apusic数据源管理 sequencePlus方法、getSequenceCurrentVal方法、以及addCustomer方法,需要放在一个事务中处理。
但在这三个方法中,使用的是不同的Connection,而由于Connection的隔离级别,将导致插入T_CUSTOMER表中的ID主键将重复,最终导致事务回滚。
利用Apusic应用服务器连接共享特性,能够很好的解决这个问题。
也就是说:虽然这三个方法申请的逻辑连接是不同的,但逻辑连接内部所使用的物理连接是同一个,这样,将保证不同方法中对数据库的操作结果相见可见,从而保证事务的正常提交。
举例如下:假设在一个jsp文件中,这样调用: 在上述
代码中,通过UserTransaction 启动一个事务,然后在该事务上下文中,增加一笔Customer的记录,我们发觉,在不需要更改Customer类的情况下,上述方法能够正常完成。
由此可以得知:在Apusic应用服务器中进行应用的开发,我们无需因为考虑数据库Connection的隔离级别而影响我们对系统的面向对象的分析方法,Apusic应用服务器将替我们保证在同一事务上下文中,使用相同的物理连接。
通过Apusic应用服务器的这个特性,能够有效的解决2.2节中描述的问题。
第 12 页 共 13 页 Apusic数据源管理 3.5 Lazy Connection Association Optimization: 在3.1节中我们谈到:通过Apusic应用服务器管理的数据库连接分逻辑连接与物理连接,物理连接隐藏在逻辑连接的背后。
那么,逻辑连接何时与一个真正的物理连接相关联的呢?在关联的过程之中,Apusic应用服务器又提供了哪些优化机制呢?举例如下: J2EE组件可能会将连接对象保存在其实例变量中从而可以在多个事务之间重复使用,但是如果这个组件在使用一次之后就很少再被用到,那么系统资源将会被组件白白占用而得不到释放,当连接池被占满时就再也无法获得新的连接。
Lazy Connection Association Optimization是这样一种机制,当J2EE组件方法调用完成时,释放连接对象所指向的物理连接以供其他组件使用,连接对象进入一个Inactive状态,在这个状态下它不和任何物理连接相关联。
当J2EE组件需要使用该连接对象时,容器将其激活,将其和一个实际的物理连接相关联。
这一过程对于应用组件来说是完全透明的。
J2EE程序员经常犯的一个错误是忘记关闭连接,特别是发生异常时没有执行正确的清理,过去我们解决这一问题是在方法调用完成时强制关闭所有的连接,现在有了Lazy Connection Association Optimization机制可以更完美地解决这一问题。
Connection Sharing和Lazy Connection Association Optimization是同时起作用的,例如,当一个连接被激活时,它将被包含在当前事务场景中,并与同一事务场景中的其他逻辑连接共享同一个物理连接。
我们在2.3节中强调:将Connection作为成员变量是一种糟糕的设计模式,但同时,我们也看到:哪怕用户旧有系统中存在这样的用法,Apusic应用服务器也 第 13 页 共 13 页 Apusic数据源管理 能够很好的解决由于这种糟糕的设计所带来的缺陷。
4 本文首先与读者分析了一些错误或者不当的数据库资源使用方法,然后简要介绍了金蝶Apusic应用服务器在数据源管理上的一些特性。
这些特性,对应用的健壮性及容错性带来一定的好处。
但需要再次提醒的是:应用服务器提供的一些增值特性,仅能够当作保障我们应用正常运行的最后一道屏障,我们切不可依赖于这些特性而忽视程序自身的编码质量。
一个J2EE应用能否正常的运行,程序自身的设计与编码永远是主要因素。
5 注1:FindBugs:Sourceforge上的一个
开源工具,能够对源码进行分析从而发现可能出现的编程错误,http://findbugs.sourceforge.net/ 注2:Commons DbUtils: Apache Jakarta项目的Commons组件,http://jakarta.apache.org/commons/index.html 注3: 金蝶Apusic应用服务器:国内首家通过J2EE 1.4认证的应用服务器,请参考http://www.apusic.com
上一篇:
基于Asterisk开源软件的IP呼叫中心系统的缺陷
下一篇:
软件工程毕业设计论文