分享

用数据库框架控制开发环境

 铃儿响叮当 2006-10-17

用数据库框架控制开发环境




本文从数据库管理员的角度,讨论了保护数据库的重要性。作者建议通过添加Java数据库框架,在开发人员和数据库之间构建一个经过反复试验的稳固的中间层,以降低风险,并提供跟踪及报告问题的工具。

没有什么比这样一只Java开发小组更影响数据库性能的了:他们有一堆需求,又要使用数据库,但却仅仅了解了一些Java数据库连接(JDBC)的皮毛。连接会打开并闲置好几个小时,一旦连接超时,问题就会扔给数据库管理员(DBA)。未关闭的语句和占用系统资源的结果将是数据库管理员头痛的问题。编程人员使用含糊的JDBC方法和动态SQL导致性能低下。

因此,本文讨论了如何使用Java数据库框架帮助数据库远离随意操作的开发人员。它提供了连接池和管理、跟踪及报告JDBC对象、有选择地删除性能低下的结构和方法。目的在于防止开发人员影响性能、避免沾上常见的不良开发习惯,并且在无法防止这类活动的情况下提供跟踪机制。这样一来,数据库管理员就可以找到问题的根源,在系统进入生产环境之前改正问题。使用Java数据库框架的另一个目的在于,让一切开发工作都保持简单,那样开发人员就可以尽快熟悉情况。

与任何框架一样,Java数据库框架的目的在于隐藏复杂性,并为处理复杂任务提供一套标准操作程序。同样重要的一个方面是让执行任务的方式具有一致性。这可以改进封装、大大提高代码的可维护性。只要设想一下:假如每个人都构建各自的类和方法来建立数据库连接,势必会导致混乱、无序的局面。除了数据库外,框架通常适用的一些方面包括:进程间通信、多线程管理和图形用户界面(GUI)标准。

本文描述的框架旨在供所有中间件开发人员使用,它在由数据库开发商提供的实际的JDBC实现上添加了一层(如图1)。

JDBC问题和陷阱

记得下面这一点很重要:JDBC是数据库开发商提供的实现,但不是所有的实现都是相同的。但是,在数据库框架让,开发人员可以在某种程度上让它们相同。在个别情况下,同一家开发商提供的JDBC驱动程序的各个版本之间会存在差异。不同开发商提供的JDBC驱动程序免不了总是会存在差异。差异通常出现在以下几方面:连接管理;存储过程和返回ResultSets;处理ResultSets;元数据支持;因语句和ResultSets未结束而消耗资源;连接未关闭带来的问题;性能异常及实现缓慢;数据库优化器从一个版本到下一个版本所出现的变化;数据库从一个版本到下一个版本添加了新特性。

笔者曾有幸参于来自Sybase和Oracle的JDBC实现,它们采用的方法形成了鲜明对比。笔者常开玩笑说,Oracle好比是“父亲”,Sybase好比是“母亲”。如果你在冬天没穿衣服就出去,母亲会叫你停下来,穿上衣服,免得感冒;而父亲会一言不发地看着,觉得要是天气寒冷,你会晓得自己添衣服。Sybase驱动程序在连接、语句和结果集管理方面可以为开发人员做大量工作;而Oracle驱动程序只会做开发人员让它做的那些事情。如果开发人员没有结束语句,它恐怕不会自动结束,也肯定不会把会话、进程及打开的游标清理干净。这里不会去研究哪个方向是正确的,我们只是为框架添加了代码,让它们看上去很相似。

图2显示了框架示例,旨在处理上面讨论的JDBC问题。它还在数据库上提供了抽象层,那样开发人员可以更迅速、更安全地访问数据库。CWDatabase类负责管理开发人员的所有访问,它利用CWConnectionPool管理连接、利用CWSqlRepository管理SQL字符串。较低级的Connection和Statement类都进行了封装,以便提供跟踪机制,并保证连接重新签入到连接池后,所有语句和结果集都已关闭。下文讨论了这些类,随后讨论了比较高级的框架特性,用于跟踪执行性能、限制JDBC特性及报告连接池。下面的所有框架类都以代表笔者所在公司CodeWorks Software的“CW”开头,这样它们很容易识别。

重要的类

数据库接口类:CWDatabase和CWParamList

为开发人员添加用于数据库访问的一个简单类。通过创建框架类的实例,他们可以获得运行SQL命令及存储过程的连接及简单方法。异常处理得到了适当的处理及报告;通过使用CWParamList允许用户创建参数列表,数据库管理员就可以牢牢地控制Java数据类型及它们如何绑定到数据库中的基本数据类型。目的在于绝对不允许用户直接控制jdbc.Connection实例。牢牢获得这种控制权的另一个好处是,通常可以获得很高的数据速率,因为数据库管理员可以控制数据库访问。

public class CWDatabase

{

 Connection m_conn = null;

 public CWDatabase()

 {

  m_conn = CWConnectionPool.checkOut();

 }

 public int executeUpdate(String queryName, CWParamList plist)

 public int executeUpdate(String queryName)

 public ResultSet executeQuery(String queryName, CWParamList plist)

 public ResultSet executeQuery(String queryName)

 private void processException(String msg, Throwable ex)

}

有了上述这个类,用户可以运行如下的简单查询:

public void updateSensorType()

{

 CWDatabase theDB = null;

 try

 {

  // 创建数据库实例和参数列表实例

  theDB = new CWDatabase();

  CWParamList plist = new CWParamList();

  // 添加参数

  plist.addParameter(1,"TYPE1");

  plist.addParameter(2,1);

  plist.addParameter(3,"ACT");

  // 执行更新

  int numupdate = theDB.executeUpdate("sensor.updateSensorType",plist);

 }

 catch( Exception exception )

 {

  CWExceptionReporter.write(this,CWExceptionReporter.FATAL,"Error updating sensor type.");

 }

 finally

 {

  theDB.close();

 }

 return;

} // End方法

连接池:CWConnectionPool

建立连接很费资源,所以通过重复使用连接,就可以避免每次重新建立连接带来的成本。系统启动时,可以为连接池提供可随时使用的几个连接。用户创建数据库接口类的实例后,连接就会签出。用户调用关闭命令后,连接重新签入,供其他用户使用。

虽然重复使用连接是连接池的主要目的,但还有许多其他好处。因为所有连接都在一个地方加以管理及创建,所以数据库管理员就能够严格管理隔离级别和数据库选项。SQL Anywhere在这方面的例子包括:DELAYED_COMMITS、ISOLATION_LEVEL和 COOPERATIVE_COMMITS,以及面向原始设备制造商(OEM)版本的软件的授权代码方面的设置。

不过,笔者在建立连接池时发现了一个问题,它们会留下一些“行李(baggage)”。这样一来,多次重复使用会渐渐减慢连接速度。而且,笔者根本查不出这个问题的根源,不过怀疑它与PreparedStatements、ResultSets或者当时出现的其他某种内部跟踪机制有关。鉴于所有连接都由连接池管理,这样就有可能跟踪连接存在了多久;重新签入后,可以“刷新”连接。通过在过了限定时间后丢弃连接,就可以更好地维持很高的性能比率。

这种严密跟踪机制的另一个好处就是,还可以监控谁把连接签出了、时间有多久。长时间保持的连接,尤其是作为成员变量,可能会导致问题。如果连接好几个小时都处于休眠状态,就会超时,进而导致问题。有了这种额外的跟踪机制,数据库管理员就可以报告谁拥有哪个连接、打开状态保持了多久。如果知道签出问题连接的那一行代码,就能找到相应的开发人员,告诉他如何使用框架。因为跟踪连接需要一定开销,笔者在编写框架时在默认状态下禁用了这项特性,不过测试过程中可以启用它。

public class CWConnectionPool

{

 // 维护闲置及签出列表上的连接

 private static ArrayList m_freePool = null;

 private static HashMap m_outPool = null;

 public CWConnectionPool()

 {

  m_freePool = new ArrayList();

  m_outPool = new HashMap();

 }

 public static void initialize()

 public static synchronized Connection checkOut()

 public static synchronized void checkIn(Connection conn)

 public static void discardConnection(Connection conn)

 private static CWConnection createConnection()

 public static reportConnectionPool()

}

SQL存储库:CWSqlRepository

SQL语句最好保存在不同文件中,那样不必重新编译代码就可以修改语句。譬如说,如果发现了某个性能问题,经过分析,发现是数据库优化器选错了索引,这时就很容易添加SQL提示。为了管理SQL语句,笔者使用了CWSQLRepository类。该存储库还允许重复使用代码;又因为所有SQL语句都在同一个地方,数据库管理员就更容易找到可能受模式改变影响的所有语句。

public class CWSqlRepository

{

 private static CWSqlRepository m_SQLRepository;

 private static Properties m_SQLrepositoryTable;

 public static void initialize()

 {

  if (m_SQLRepository == null)

   m_SQLRepository = new CWSqlRepository();

  return;

 }

 private static void loadSQLrepository(String sqlfile)

 public static String getSQLString(String tag)

}

封装JDBC类

说到简化数据库访问,通过提供上面讨论的那几个简单类,就能得到很大成效。不过说到消除JDBC实现在较低层面上的差异,从事重复工作毫无意义。只要封装JDBC类,就可以处理问题、添加功能。JDBC文档齐全,开发人员很熟悉它,数据库管理员也是一样。另外,很容易教人学会,并提供合理使用的示例。通过创建封装器类,数据库管理员可以添加自己需要的任何跟踪、定时及报告机制,还可以消除差异。只有在极少数情况下,开发人员才真正知道自己在使用Connection.prepareStatement()的框架实现,而不是实际的实现。

CWConnection

要封装的最重要的一个类是java.sql.Connection。数据库管理员可以在这里跟踪某连接签出了多久,并维护所有已创建语句的列表。为了调试,数据库管理员可以维护堆栈跟踪信息(stack trace)。这样在建立连接后,一旦发现“连接滥用”,就能更准确地找到建立该连接的代码,譬如说,连接签出时间超过规定。

public class CWConnection implements Connection

{

 // 连接的基本信息

 private Connection _conn = null;

 //封装的java.sql.Connection

 private int _connNum ;

 // 跟踪号码

 private long _createTime;

 // 设定时间

 private ArrayList _stmtTracker;

 // 跟踪语句

 public int getConnectionNum()

 public int getElapseTime()

 public void closeStatements()

 private ArrayList getStatementTracker()

 private void clearStatementTracker()

 String reportConnnection()

 // 封装的JDBC方法

 public Statement createStatement() throws SQLException

  {

   CWStatement stmt = new CWStatement(_conn.createStatement());

   _stmtTracker.add(stmt);

   return stmt;

  }

}

CWStatement、CWPreparedStatement和CWCallableStatement

数据库管理员可以编写这样的框架:很少允许开发人员可以控制Statements、CallableStatements和PreparedStatements。封装这些类的主要原因是可以跟踪时间设定,如果返回结果集的话,还可以跟踪ResultSet实例。虽然在下面讨论了串行化的结果集,但笔者并不建议把结果集隐藏起来,不让开发人员看到,因为接口方面的文档很齐全。主要的滥用现象就是让结果集打开着,不过对此进行跟踪却相当简单。连接签入后,所有语句都被关闭,每个语句保证结果集被关闭。笔者仍封装了ResultSet,但主要目的是消除性能低下的方法,那样开发人员就没法用它们。稍后会讨论这个话题。

public class CWStatement implements Statement

{

 // 语句的基本信息

 private Statement m_stmt = null;

 //封装的java.sql.Statement

 private int m_stmtNum;

 // 跟踪号码

 private long m_createTime;

 // 设定时间

 protected ResultSet m_rsTracker;

 // 跟踪结果集

 private String m_sqlTracker;

 // 随该语句一起发出的Sql

 public String reportStatement()

 public CWStatement( Statement stmt, int stmtNum)

 {

  m_stmt = stmt;

  m_stmtNum = stmtNum;

  m_createTime = System.currentTimeMillis();

  m_sqlTracker = null;

  m_rsTracker = null;

 }

 public ResultSet executeQuery(String sql) throws SQLException

 {

  CWResultSet rs = new CWResultSet(m_stmt.executeQuery(sql));

  m_rsTracker = rs;

  m_sqlTracker = sql;

  return rs;

 }

}

框架的先进思想

上面讨论的话题集中于简化数据库接口、连接管理,并提供防范常见JDBC问题的方法。接下来会介绍框架的附加部分,它们为监控及控制使用数据库的开发人员提供了更有效的机制,包括:解决结果集的问题、限制性能低下的操作、监控SQL性能。

CWResultSetSerialized

许多Java编程人员没有认识到(或者忘了)ResultSets实际上是数据库游标。它们传递引用、把它们存储为成员变量,往往从不关闭,因而占用了数据库资源。为了消除所有风险,可利用JDBC结果集来创建串行化的结果集。这还可以让结果集通过远程方法调用(RMI)在进程之间发送。

在极端情况下,引起阻塞问题的结果集频频传送,以至笔者查不到该在什么地方关闭它。一旦用串行化的结果集取而代之,就能关闭实际的ResultSet,所有问题都立马消失了。串行化结果集的任何实现都需要限制可以创建的行数,因为100万行的串行化结果集会引起性能问题。笔者开始限制在5000行。

下面的代码表明了结果集管理不善,因为它传到了方法外面。现在很难知道它是不是被关闭了,因为方法只有出现了错误才关闭数据库实例。代码可以使用,不过,要是结果集在调用方法里面没有关闭,连接会处于签出状态,游标仍然是打开的。

public ResultSet getAllSensors()

{

 CWDatabase theDB = null;

 ResultSet rs = null;

 try

 {

  // 创建数据库实例和参数列表实例

  theDB = new CWDatabase();

  // 执行查询

  rs = theDB.executeQuery("sensor.getAllSensors");

 }

 catch( Exception exception )

 {

  CWExceptionReporter.write(this,CWExceptionReporter.FATAL,"Error during query: " + " sensor.getAllSensors ");

  theDB.close();

 }

 return rs;

} // End方法

因为有时在开发周期很晚时才发现这类问题,因而无法通过改写方法来解决,可以通过以下办法解决问题:传回串行化的结果集,通过finally块关闭数据库实例,从而保证一切都正常关闭。笔者仍认为,数据库管理员应当给引起问题的工程师出难题,不过系统代码冻结前一天不是改变大量代码的时候。所作的变化用下面的黑体字表明:

public ResultSet getAllSensors()

{

 CWDatabase theDB = null;

 CWResultSetSerialized rss = null;

 try

 {

  // 创建数据库实例和参数列表实例

  theDB = new CWDatabase();

  // 执行更新

  ResultSet rs = theDB.executeQuery("sensor.getAllSensors");

  rss = new ResultSetSerialized(rs);

 }

 catch( Exception exception )

 {

  CWExceptionReporter.write(this,CWExceptionReporter.FATAL,"Error during query: " + " sensor.getAllSensors ");

 }

 finally

 {

  theDB.close();

 }

// 返回串行化结果集

return rss;

} // End方法

性能级别

因性能需求不同,数据库管理员的要求可能大不相同,有的是“只允许速度最快的数据库访问”,有的是“我不在乎访问速度,只要可以使用任何特性”。大部分人介于两者之间。 能够“关闭”已知性能低下的JDBC方法大有帮助。譬如说,使用ResultSet.update()比使用不同的Statement.execute()来执行同样的更新慢得多。允许用户使用rs.last()等方法返回不是“只能向前移动的”结果集也很慢。笔者使用三个基本的性能级别:

● 级别1:开发人员不可以访问连接,也无法发出动态SQL。所有性能低下的方法都被关闭,包括结果集更新和元数据访问。

● 级别2:允许动态查询,但其他所有性能低下的方法仍然受到限制。笔者的架构就使用这种默认值。

● 级别3:全面的JDBC访问,没有任何限制。

关闭JDBC特性后,笔者建议发出异常,这可以解释特性已被关闭,需要联系数据库管理员。在开发期间,数据库管理员可以决定是否真正需要该特性,并确定要不要重新添加到框架上,或者更改该特性的性能级别。

public class CWResultSet implements ResultSet

{

 private ResultSet m_rs;

 private long m_createTime;

 private int m_perf_level;

 public CWResultSet( ResultSet rs, perf_level)

 {

  m_rs = rs;

  m_createTime = System.currentTimeMillis();

  m_perf_level = perf_level;

 }

 public void updateRow() throws SQLException

 {

  if(perf_level < CWDatabase.HIGH_PERF_ONLY)

  {

   CWExceptionReporter.write(this,CWExceptionReporter.ERROR, "Use of method prohibited due to slow performance, “+ " as per the DBA.");

  }

  m_rs.updateRow();

 }

}

为数据库管理员想要开启或者禁用的每一部分JDBC功能赋予名字,并且利用属性文件控制这些值相当简单。不过,笔者发现自己不需要这种灵活性,于是仍采用了基本级别。

SQL性能跟踪

虽然SQL Anywhere提供了监控查询的功能,但笔者还是提供了语句定时和报告机制。这样数据库管理员可以对某个SQL语句及所有语句开启跟踪机制,或者设定时间阈值,报告超过给定阈值的查询。笔者还考虑了在结果集层面的定时,那样就可以确认超过阈值的结果集。这会有所帮助,因为语句定时执行只提供返回首行的所用时间,除非语句里面有“按××排序”或者类似子句。跟连接跟踪的情况很相似,笔者在默认情况下关闭了这项功能,在测试阶段加以利用。

public int executeUpdate(String queryName)

{

 private long startTime;

 < snip >

 // 执行更新

 startQueryTimer();

 int numupdate =theDB.executeUpdate queryName,plist);

 stopQueryTimer(queryName);

}

如果查询跟踪机制开启,查询时间就会记录到数据库里面。

(沈建苗 编译)

(计算机世界报 2006年10月16日 第40期 B31、B32)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多