增强您与 JDBC API 的关系。
你认为你知道如何在 J**A 中编程吗?事实是,大多数开发人员只触及了 J**a 平台的表面,并且只学到了足以完成工作的知识。 在本系列中,Ted Neward 深入探讨了 J**A 平台的核心功能,并揭示了一些鲜为人知的事实,以帮助您解决最棘手的编程挑战。 目前,许多开发人员使用 J**a 数据库连接 (JDBC) API 作为数据访问平台,例如 Hibernate 或 SpringMany。 但是,JDBC 不仅在数据库连接中扮演后端角色。 当涉及到JDBC时,你知道的越多,你的RDBMS交互就会越高效。
在这个 5 件事要做的系列中,我将向您介绍一些 JDBC 20 到 JDBC 40 中引入的新功能。 这些新功能在设计时考虑到了现代软件开发的挑战,支持应用程序可扩展性并提高开发人员的工作效率,这是现代 J**A 开发人员面临的两个最常见的挑战。 不同的RDBMS实现为SQL和/或增值功能提供不规则的支持(目的是使程序员的工作更轻松)。 例如,已知 SQL 支持标量运算count()
返回满足特定 SQL 筛选规则的行数(更准确地说,是。where
谓语)。最重要的是,修改 SQL 返回的值可能很棘手——试图从数据库中获取当前日期和时间会让 JDBC 开发人员甚至最有耐心的程序员发疯(甚至憔悴)。
因此,JDBC 规范通过标量函数为不同的 RDBMS 实现提供了一定程度的隔离覆盖。 JDBC 规范包括一个受支持的操作列表,JDBC 驱动程序应根据特定数据库实现的需要识别和覆盖这些操作。 因此,对于支持返回当前日期和/或时间的数据库,时间查询应该像清单 1 一样简单:
清单 1当前时间?
connection conn = ..// get it from someplacestatement stmt = conn.createstatement();resultset rs = stmt.executequery("");
JDBC 规范附录中提供了 JDBC API 识别的标量函数的完整列表(参见参考参考资料),但给定的驱动程序或数据库可能不支持完整列表。 您可以从connection
返回databasemetadata
对象来获取给定 JDBC 支持的函数,如清单 2 所示:
清单 2我能得到什么?
connection conn = ..// get it from someplacedatabasemetadata dbmd = conn.getmetadata();
标量函数列表来自各种来源databasemetadata
该方法返回一个逗号分隔的string
。例如,所有数值标量都由getnumericfunctions()
调用列表,对结果执行一个string.split()
— 看!— 即刻equals()-testable
列表。
创建一个connection
对象并使用它来创建一个statement
,这在 JDBC 中最常用。 提供sql select
之statement
返回一个resultset
。然后,通过一个while
循环(和iterator
没有什么不同)得到resultset
直到resultset
为空,循环体从左到右一次提取一列。
这整个操作过程是如此普遍,以至于它接近神圣:它这样做只是因为它应该这样做。 唉!事实上,这是完全没有必要的。
许多开发人员没有意识到的是,在过去几年中,JBDC已经有了相当多的增强功能,尽管这些改进已经反映在新版本中。 第一个主要增强功能是在 JDBC 2 中0,在使用 JDK 1 时发生2 时期。 在撰写本文时,JDBC 已发展到 JDBC 40。
jdbc 2.0 中一个有趣的(尽管经常被忽视)增强功能是:resultset
这意味着您可以根据需要前进或后退,或两者兼而有之。 但是,这样做需要一些远见,在创建时必须注意 jdbc 调用statement
当您需要一个可以滚动的resultset
验证结果集类型。
如果您怀疑驱动程序实际上可能不支持可滚动resultset
S,不管怎样databasemetadata
怎么写的,你要打电话gettype()
验证resultset
类型。 当然,如果你是一个偏执的人,你可能也不相信gettype()
的返回值。 可以这么说,如果gettype()
隐瞒 关于resultset
在返回值中,它们确实会吃掉你。
如果底层 jdbc 驱动程序支持滚动,则一个是可滚动的resultset
将从那statement
返回。 但在请求它之前,最好弄清楚驱动程序是否支持可滚动性。 你可以这样做databasemetadata
对象探测可滚动性,如上所述,可以从任何connection
。
一旦你有一个databasemetadata
对象,一对getjdbcmajorversion()
将确定驱动程序是否支持 JDBC 规范,至少支持 JDBC 20 规格。 当然,驱动程序可能会隐瞒它对给定规范的支持程度,因此为了安全起见,请使用您期望的内容resultset
类型调用supportsresultsettype()
方法。 (英寸)resultset
传统上,它是一个常数;我们稍后将讨论它的每个值。 )
清单 3你能滚动吗?
int jdbcversion = dbmd.getjdbcmajorversion();boolean srs = dbmd.supportsresultsettype(resultset.type_scroll_insensitive);if (jdbcversion > 2 ||srs == true)
假设驱动程序回答“是”(如果不是,则需要新的驱动程序或数据库),可以通过将两个参数传递给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
之(在 j**a 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
提前知道滚动方向可以优化其数据检索。 )
JDBC 不仅支持双向resultset
,它还支持就地更新resultset
。这意味着,您只需要修改数据库中存储的值,而不需要创建新的 SQL 语句来修改当前存储在数据库中的值resultset
,然后自动发送到数据库中该行的列。
请求可更新的resultset
类似于请求可滚动的resultset
过程。 事实上,在这里您将能够:createstatement()
使用第二个参数。 无需指定第二个参数resultset.concur_read_only
,只需要发送resultset.concur_updateable
就是这样,如清单 5 所示:
清单 5我想要一个可更新的结果集
statement stmt = con.createstatement( resultset.type_scroll_insensitive, resultset.concur_updateable);resultset scrollingrs = stmt.executequery("select * from whatever");
假设您的驱动程序支持可更新的游标(这是 JDBC 20 规范的另一个功能,大多数现实数据库都支持),您可以更新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()
删除一行。
我差点忘了强调所有这些可滚动性和可更新性适用于preparedstatement
(通过。preparestatement()
方法传递参数),因为它一直处于 SQL 注入攻击的危险之中,这不仅仅是一个规则statement
好多了。
既然所有这些功能在 JDBC 中已经存在了大约 10 年,为什么大多数开发人员仍然痴迷于向前滚动resultset
和脱节的访问?
罪魁祸首是可扩展性。 保持最小的数据库连接是支持大量用户通过 Internet 访问公司的关键。 因为滚动和/或更新resultset
通常需要开放的网络连接,许多开发人员通常不(或不能)使用这些连接。
值得庆幸的是,JDBC 30 引入了另一种解决方案,以便您可以在使用之前执行相同的操作resultset
参与方可以在不要求数据库连接保持打开状态的情况下执行操作。
概念rowset
本质上是一个resultset
,但它支持连接或断开模型,您需要做的就是创建一个rowset
将其指向一个resultset
,当它完成填充时,将其作为一个整体resultset
,如清单 7 所示:
清单 7RowSet 替换 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); // connectedelse cachedrowset crs = new cachedrowset(scrollingrs); disconnected
JDBC 还附带了其中的 5 个rowset
接口实现(即扩展接口)。 jdbcrowset
是一个连接的rowset
实现;其余 4 个已断开连接:
cachedrowset
只是一个断开连接的rowset
webrowset
是的cachedrowset
知道如何将其结果转换为 XML 并再转换回来的子集。 joinrowset
是其中之一webrowset
并知道如何形成一个sql join
无需连接到数据库。 filteredrowset
是其中之一webrowset
了解如何进一步筛选传回的数据,而无需连接到数据库。 rowsets
完整的 j**abeans 意味着它们支持侦听类事件,因此可以在需要时捕获、检查和执行它们rowset
任何修改。 事实上,如果rowset
有自己的username
password
url
跟datasourcename
属性集(这意味着将使用它。drivermanager.getconnection()
创建连接)或datasource
属性集(可能由 JNDI 获取),甚至可以管理数据库上的所有操作。 然后你可以在command
要执行(调用)的 SQL 在属性中指定execute()
然后处理结果,无需再进行任何工作。
通常rowset
实现由 JDBC 驱动程序提供,因此实际名称和包或包由您正在使用的 JDBC 驱动程序确定。 以 j**a 5 开头rowset
该实现已经是标准发行版的一部分,因此您只需要创建一个...rowsetimpl()
让它运行。 (如果您的驱动程序没有提供参考实现,Sun 会提供一个,请参见 参考资料 部分中的链接。 )
虽然rowset
它很有用,但有时它不能满足您的需求,您可能需要返回并直接编写 SQL 语句。 在这种情况下,尤其是当您面临大量工作时,您会喜欢批量更新功能,该功能可在单个网络往返中执行数据库中的多个 SQL 语句。
要确定 JDBC 驱动程序是否支持批量更新,请进行快速调用databasemetadata.supportsbatchupdates()
可以生成显式支持或不支持的布尔值。 当支持批量更新时(由一些非select
markup),所有任务都一个接一个地排队,然后在某个时间点同时更新,如清单 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, repeatint updatecount = pstmt.executebatch();conn.commit();conn.setautocommit(true);
默认情况下必须调用它setautocommit()
,驱动程序会尝试传递提供给它的每个语句。 除此之外,其余的都简单易懂:使用statement
或preparedstatement
执行常见的 SQL 操作,但不要调用它们execute()
,然后调用executebatch()
,排队等待呼叫,而不是立即发送。
准备好各种语句后,在数据库中使用它们executebatch()
触发所有语句,这些语句将返回一组整数值,每个值都包含相同的结果,就像它被使用一样executeupdate()
一样。
如果批处理中的一条语句出错,如果驱动程序不支持批处理更新,或者批处理中的一条语句返回resultset
司机会扔一个batchupdateexception
。有时,在引发异常后,驱动程序可能会尝试继续语句。 JDBC 规范不授权行为,因此您应该事先试用驱动程序,以便确切地了解它的工作原理。 (当然,你会想要做单元测试,以确保你在错误成为问题之前发现它们,对吧?)
作为 J**a 开发的一个主题,JDBC API 是每个开发人员都应该熟悉的东西,就像你的右手和左手一样。 有趣的是,在过去几年中,许多开发人员并没有意识到 API 的增强功能,因此,他们错过了本文中介绍的节省时间的技巧。
当然,是否决定使用 JDBC 的新功能取决于您。 要考虑的一个关键因素是您正在使用的系统的可扩展性。 可伸缩性要求越高,数据库的使用就越有限,因此减少的网络流量就越多。rowset
、标量调用和批量更新将对您有所帮助。 另外,尝试可滚动和可更新resultset
(不是那样的。rowset
这会消耗内存)并衡量可伸缩性。它可能没有你想象的那么糟糕。