关于Java Database Connectivity您不知道的5件事:晋升您和JDBC API的干系
副标题#e#
关于Java Database Connectivity您不知道的5件事:晋升您和JDBC API的干系
今朝,很多开拓人员把 Java Database Connectivity (JDBC) API 看成一种数据会见平台,好比 Hibernate 或 SpringMany。然而 JDBC 在数据库毗连中不只仅充当靠山脚色。对付 JDBC,您相识的越多,您的 RDBMS 交互效率就越高。
在本期 5 件事 系列 中,我将向您先容几种 JDBC 2.0 到 JDBC 4.0 中新引入的成果。设计时思量到现代软件开拓所面对的挑战,这些新特性支持应用措施可伸缩性,并提高开拓人员的事情效率 — 这是现代 Java 开拓人员面对的两个最常见的挑战。
1. 标量函数
差异的 RDBMS 实现对 SQL 和/或增值特性(目标是让措施员的事情更为简朴)提供犯科则的支持。譬喻,众所周知,SQL 支持一个标量运算 COUNT(),返回满意特定 SQL 过滤法则的行数(更确切地说,是 WHERE 谓词)。除此之外,修改 SQL 返回的值是很棘手的 — 想要从数据库获取当前日期和时间会使 JDBC 开拓人员、甚至最有耐性的措施员发狂(甚至是心力憔悴)。
于是,JDBC 类型针对差异的 RDBMS 实现通过标量函数提供必然水平的断绝/改写。JDBC 类型包罗一系列受支持的操纵,JDBC 驱动措施应该按照特定命据库实现的需要举办识别和改写。因此,对付一个支持返回当前日期和/或时间的数据库,时间查询该当如清单 1 那样简朴:
清单 1. 当前时间?
Connection conn = ...; // get it from someplace
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("{CURRENT_DATE()}");
JDBC API 识此外标量函数完整列表在 JDBC 类型附录中给出,可是给定的驱动措施或数据库大概不支持完整列表。您可以利用从 Connection 返回的 DatabaseMetaData 工具来获取给定 JDBC 支持的函数,如清单 2 所示:
清单 2. 能为我提供什么?
Connection conn = ...; // get it from someplace
DatabaseMetaData dbmd = conn.getMetaData();
标量函数列表是从各类 DatabaseMetaData 要领返回的一个逗号脱离的 String。譬喻,所有数值标量由 getNumericFunctions() 挪用列出,在功效上执行一个 String.split() — 瞧! — 立刻呈现 equals()-testable 列表。
2. 可转动 ResultSets
建设一个 Connection 工具,并用它来建设一个 Statement,这在 JDBC 中是最常用的。提供应 SQL SELECT 的 Statement 返回一个 ResultSet。然后,通过一个 while 轮回(和 Iterator 没什么差异)获得 ResultSet,直到 ResultSet 为空,轮回体从左到右的每次提取一列。
这整个操纵进程是如此普遍,近乎神圣:它这样做只是因为它应该这样做。唉!实际上这是完全没须要的。
引入可转动 ResultSet
很多开拓人员没有意识到,在已往的几年中 JBDC 已经有了相当大的加强,尽量这些加强在新版本中已经有所反应。 第一次重大加强是在 JDBC 2.0 中,产生在利用 JDK 1.2 期间。写这篇文章时,JDBC 已经成长到了 JDBC 4.0。
JDBC 2.0 中一个有趣的加强(尽量经常被忽略)是 ResultSet 的转动成果,这意味着您可以按照需要前进可能退却,可能两者均可。这样做需要一点前瞻性,然而 — JDBC 挪用必需指出在建设 Statement 时需要一个可以转动的 ResultSet。
#p#副标题#e#
验证 ResultSet 范例
假如您猜疑一个驱动措施事实上大概不支持可转动的 ResultSets,不管 DatabaseMetaData 中是如何写的,您都要挪用 getType() 来验证 ResultSet 范例。虽然,假如您是个偏执的人,您大概也不相信 getType() 的返回值。可以这样说,假如 getType() 隐瞒关于 ResultSet 的返回值,它们确实是 要吃定您。
假如底层 JDBC 驱动措施支持转动,一个可转动的 ResultSet 将从谁人 Statement 返回。可是在请求它之前最好弄清楚驱动措施是否支持可转动性。您可以通过 DatabaseMetaData 工具打听转动性,如上所述,这个工具可从任何 Connection 中获取。
一旦您有了一个 DatabaseMetaData 工具,一个对 getJDBCMajorVersion() 的挪用将会确定驱动措施是否支持 JDBC 类型,至少是 JDBC 2.0 类型。虽然一个驱动措施大概会隐瞒它对给定类型的支持水平,因此为了安详起见,用期望获得的 ResultSet 范例挪用 supportsResultSetType() 要领。(在 ResultSet 类上它是一个常量;稍后我们将对其每个值举办接头。)
清单 3. 可以转动?
int JDBCVersion = dbmd.getJDBCMajorVersion();
boolean srs = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
if (JDBCVersion > 2 || srs == true)
{
// scroll, baby, scroll!
}
请求一个可转动的 ResultSet
#p#分页标题#e#
假设您的驱动措施答复 “是”(假如不是,您需要一个新的驱动措施或数据库),您可以通过通报两个参数到 Connection.createStatement() 挪用来请求一个可转动的 ResultSet,如清单 4 所示:
清单 4. 我想要转动!
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
在挪用 createStatement() 时,您必需出格小心,因为它的第一个和第二个参数都是 int 的。(在 Java 5 之前我们不能利用列举范例!)任何 int 值(包罗错误的常量)对 createStatement() 都有效。
第一个参数,指定 ResultSet 中期望获得的 “可转动性”,应该是以下 3 个值之一:
ResultSet.TYPE_FORWARD_ONLY:这是默认的,是我们相识且喜欢的流水游标。
ResultSet.TYPE_SCROLL_INSENSITIVE:这个 ResultSet 支持向后迭代以及向前迭代,可是,假如数据库中的数据产生变革,ResultSet 将不能反应出来。这个可转动的 ResultSet 大概是最常用到的范例。
ResultSet.TYPE_SCROLL_SENSITIVE:所建设的 ResultSet 不单支持双向迭代,并且当数据库中的数据产生变革时还为您提供一个 “及时” 数据视图。
第二个参数在下一个能力中先容,稍等半晌。
定向转动
当您从 Statement 获取一个 ResultSet 后,通过它向后转动只需挪用 previous(),即向后转动一行,而不是向前,就像 next() 那样。您也可以挪用 first() 返回到 ResultSet 开头,可能挪用 last() 转到 ResultSet 的末端,可能…您本身拿主意。
relative() 和 absolute() 要领也是很有用的:前者移动指定命量的行(假如是正数则向前移动,是负数则向后移动),后者移动 ResultSet 中指定命量的行,不管游标在哪。虽然,今朝行数是由 getRow() 获取的。
假如您规划通过挪用 setFetchDirection() 在一个特定偏向举办一些转动,可以通过指定偏历来辅佐 ResultSet。(无论向哪个偏向转动,对付 ResultSet 都可行,可是预先知道转动偏向可以优化其数据检索。)
3. 可更新的 ResultSets
JDBC 不只仅支持双向 ResultSet,也支持当场更新 ResultSet。这就是说,与其建设一个新 SQL 语句来修改今朝存储在数据库中的值,您只需要修改生存在 ResultSet 中的值,之后该值会被自动发送到数据库中该行所对应的列。
请求一个可更新的 ResultSet 雷同于请求一个可转动的 ResultSet 的进程。事实上,在此您将为 createStatement() 利用第二个参数。您不需要为第二个参数指定 ResultSet.CONCUR_READ_ONLY,只需要发送 ResultSet.CONCUR_UPDATEABLE 即可,如清单 5 所示:
清单 5. 我想要一个可更新的 ResultSet
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
假设您的驱动措施支持可更新光标(这是 JDBC 2.0 类型的另一个特性,这是大大都 “现实” 数据库所支持的),您可以更新 ResultSet 中任何给定的值,要领是导航到该行并挪用它的一个 update…() 要领(如清单 6 所示),如同 ResultSet 的 get…()要领。在 ResultSet 中 update…() 对付实际的列范例是超负荷的。因此要更更名为 “PRICE” 的浮点列,挪用 updateFloat("PRICE")。然而,这样做只能更新 ResultSet 中的值。为了将该值插入支持它的数据库中,可以挪用 updateRow()。假如用户改变调解价值的想法,挪用 cancelRowUpdates() 可以遏制所有正在举办的更新。
清单 6. 一个更好的要领
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS =
stmt.executeQuery("SELECT * FROM lineitem WHERE id=1");
scrollingRS.first();
scrollingRS.udpateFloat("PRICE", 121.45f);
// ...
if (userSaidOK)
scrollingRS.updateRow();
else
scrollingRS.cancelRowUpdates();
JDBC 2.0 不但支持更新。假如用户想要添加一个全新的行,不需要建设一个新 Statement 并执行一个 INSERT,只需要挪用 moveToInsertRow(),为每个列挪用 update…(),然后挪用 insertRow() 完成事情。假如没有指定一个列值,数据库会默认将其看作 SQL NULL(假如数据库模式不答允该列为 NULL,这大概触发 SQLException)。
虽然,假如 ResultSet 支持更新一行,也一定支持通过 deleteRow() 删除一行。
#p#分页标题#e#
差点忘了强调一点,所有这些可转动性和可更新性都合用于 PreparedStatement(通过向 prepareStatement() 要领通报参数),由于一直处于 SQL 注入进攻的危险中,这比一个法则的 Statement 好许多。
4. Rowsets
既然所有这些成果在 JDBC 中约莫有 10 年了,为什么大大都开拓人员仍然沉沦向前转动 ResultSet 和不连贯会见?
祸首罪魁是可伸缩性。保持最低的数据库毗连是支持大量用户通过 Internet 会见公司网站的要害。因为转动和/或更新 ResultSet 凡是需要一个开放的网络毗连,而很多开拓人员凡是不(或不能)利用这些毗连。
幸好,JDBC 3.0 引入另一种办理方案让您同样可以做许多之前利用 ResultSet 方可以做的工作,而不需要数据库毗连保持开放状态。
从观念上讲,Rowset 本质上是一个 ResultSet,可是它支持毗连模子或断开模子,您所需要做的是建设一个 Rowset,将其指向一个 ResultSet,当它完成自我填充之后,将其作为一个 ResultSet,如清单 7 所示:
清单 7. Rowset 代替 ResultSet
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
if (wantsConnected)
JdbcRowSet rs = new JdbcRowSet(scrollingRS); // connected
else
CachedRowSet crs = new CachedRowSet(scrollingRS); disconnected
JDBC 还附带了 5 个 Rowset 接口 “实现”(也就是扩展接口)。JdbcRowSet 是一个毗连的 Rowset 实现;其余 4 个是断开的:
CachedRowSet 只是一个断开的 Rowset.
WebRowSet 是 CachedRowSet 的一个子集,知道如何将其功效转换成 XML,并再次转换返来。
JoinRowSet 是一个 WebRowSet,知道如何形成一个 SQL JOIN,而无需毗连到数据库。
FilteredRowSet 是一个 WebRowSet,知道如何更进一步过滤通报返来的数据,而不需要毗连到数据库。
Rowsets 是完整的 JavaBeans,意味着它们支持侦听类事件,因此,假如需要,也可以捕获、查抄并执行对 Rowset 的任何修改。事实上,假如 Rowset 有本身的 Username、Password、URL 和 DatasourceName 属性集(这意味着它将利用 DriverManager.getConnection() 建设一个毗连)可能 Datasource 属性集(这很大概由 JNDI 获取),它甚至能打点对数据库的全部操纵。然后,您可以在 Command 属性中指定要执行的 SQL,挪用 execute(),然后处理惩罚功效 — 不需要更多的事情。
凡是,Rowset 实现是由 JDBC 驱动措施提供的,因此实际的名称和/或包由您所利用的 JDBC 驱动措施抉择。从 Java 5 开始 Rowset 实现已经是尺度版本(standard distribution)的一部门了,因此您只需要建设一个 …RowsetImpl(),然后让其运行。
5. 批量更新
尽量 Rowset 很实用,但有时候也不能满意您的需求,您大概需要返返来直接编写 SQL 语句。在这种环境下,出格是当您面临一大堆事情时,您就会很谢谢批量更新成果,可在一个网络来回行程中在数据库中执行多条 SQL 语句。
要确定 JDBC 驱动措施是否支持批量更新,快速挪用 DatabaseMetaData.supportsBatchUpdates() 可发生一个昭示支持与否的布尔值。在支持批量更新时(由一些非 SELECT 标示),所有任务逐个列队然后在某一瞬间同时获得更新,如清单 8 所示:
清单 8. 让数据库举办批量更新!
conn.setAutoCommit(false);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO lineitems VALUES(?,?,?,?)");
pstmt.setInt(1, 1);
pstmt.setString(2, "52919-49278");
pstmt.setFloat(3, 49.99);
pstmt.setBoolean(4, true);
pstmt.addBatch();
// rinse, lather, repeat
int[] updateCount = pstmt.executeBatch();
conn.commit();
conn.setAutoCommit(true);
默认必需挪用 setAutoCommit(),驱动措施会试图交付提供应它的每条语句。除此之外,其余代码都是简朴易懂的:利用 Statement 或 PreparedStatement 举办常见 SQL 操纵,可是不挪用 execute(),而挪用 executeBatch(),列队等待挪用而不是当即发送。
筹备好各类语句之后,在数据库中利用 executeBatch() 触发所有的语句,这将返回一组整型值,每个值生存同样的功效,仿佛利用了 executeUpdate() 一样。
#p#分页标题#e#
在批量处理惩罚的一条语句产生错误的环境下,假如驱动措施不支持批量更新,可能批处理惩罚中的一条语句返回 ResultSet,驱动措施将抛出一个 BatchUpdateException。有时候,在抛出一个异常之后,驱动措施大概试着继承执行语句。JDBC 类型不能授权某一行为,因此您应该事先试用驱动措施,这样就可以确切地知道它是如何事情的。(虽然,您要执行单位测试,确保在错误成为问题之前发明它,对吧?)
竣事语
作为 Java 开拓的一个主题,JDBC API 是每个开拓人员应该熟知的,就像您的阁下手那样。有趣的是,在已往的几年中,很多开拓人员并不相识 API 的加强成果,因此,他们错失了本文所讲到的省时能力。
虽然,您是反对定利用 JDBC 的新成果取决于您本身。需要思量的一个要害因素是您所利用的系统的可伸缩性。对伸缩性的要求越高,对数据库的利用就越受限制,因此而淘汰的网络流量就会越多。Rowset、标量挪用和批量更新将会是给您带来辅佐的益友。别的,实验可转动和可更新的 ResultSet(这不像 Rowset 那样耗内存),并怀抱可伸缩性。这大概没您想象的糟糕。
5 件事 系列 的下一期主题是: 呼吁行符号。