5件你不知道的事情... Java数据库连接

来源:互联网 发布:淘宝清缓存会删除什么 编辑:程序博客网 时间:2024/06/04 17:57

许多Java开发人员今天通过数据访问平台(如Hibernate或Spring)知道Java数据库连接(JDBC)API。但是JDBC不仅仅是数据库连接的背景播放器。你知道的越多,RDBMS的交互效率就越高。

在本文中,我将演示最新版本的JDBC中介绍的几个功能。考虑到现代软件开发挑战的设计,这些功能支持应用程序的可扩展性和开发人员的生产力 - 这是Java开发人员面临的常见挑战之一。

标量函数

不同的RDBMS实现为SQL和/或增值功能提供不规则支持,旨在使开发人员的生活更轻松。例如,众所周知,SQL提供了一个标量操作, COUNT()以返回满足特定SQL过滤条件(即WHERE谓词)的行数。但是除此之外,尝试修改由SQL返回的值可能是棘手的 - 尝试从数据库获取当前的日期和时间可能会驱动即使是最有耐心的JDBC开发人员(也可能秃头)。

为此,JDBC规范通过标量函数提供对不同RDBMS实现的一定程度的隔离/适配。JDBC规范包括JDBC驱动程序必须识别并适应其特定数据库实现的受支持操作的列表。因此,对于支持返回当前日期和/或时间的数据库,可以像清单1那样简单:

清单1.几点了?
1
2
3
Connection conn = ...; // get it from someplace
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“{fn CURRENT_DATE()}”);

JDBC API识别的标量函数的完整列表在JDBC规范的附录中给出(参见相关主题),但是给定的驱动程序或数据库可能不支持完整列表。您可以使用DatabaseMetaData 返回对象Connection来获取给定JDBC实现支持的函数,如清单2所示:

清单2.你能为我做些什么?
1
2
Connection conn = ...; // get it from someplace
DatabaseMetaData dbmd = conn.getMetaData();

标量函数的列表是String 从各种DatabaseMetaData方法返回的逗号分隔例如,通过getNumericFunctions()调用列出所有数字标量 做一个String.split() 结果和 - voilà!- 即时 equals()-testable列表。

值得一提的是,除了JDBC 4之外,其中一个改进是安全地删除了该Class.forName("some driver class")语句。

2.可滚动的结果集

在JDBC中创建Connection 对象(或获取现有对象)并使用它来创建一个 非常常见的过程StatementStatement,被供给的 SQL SELECT,返回一个ResultSet在 ResultSet随后通过送入while循环(没有什么不同的Iterator),直到ResultSet认为它是空的,用循环提取一列的尸体在左到右的顺序一次。

这个整个操作是如此的普遍,已经变得几乎是神圣的:这样做完全是因为这样做的方式。唉,这是完全不必要的。

介绍可滚动的ResultSet

许多开发人员不知道JDBC多年来已经大大增强,尽管这些增强功能反映在新版本号和版本中。在撰写本文时,JDBC代表版本4.2。

有趣的(虽然经常被忽视)的增强之一是能够“滚动” ResultSet,意思是我们可以向前或向后,甚至两者都按需要。这样做需要一些前瞻性的思想,但是JDBC调用必须表明它ResultSetStatement创建时 需要滚动

如果底层的JDBC驱动程序支持滚动,ResultSet则会从中返回滚动 Statement,但最好是在询问驱动程序之前是否支持滚动性。您可以询问通过DatabaseMetaData对象滚动 ,可以从任何一个获得 Connection,如前所述。

一旦有一个DatabaseMetaData对象,调用 getJDBCMajorVersion()将检索此驱动程序的主要JDBC版本号。当然,一个司机可能就其给定规格的支持程度而言,因此要特别安全地使用,请称之为supportsResultSetType()所需ResultSet类型方法(这是ResultSet课堂上的一个常数 ;我们将在短短一秒钟内讨论每个值。)

清单3.你可以滚动吗?
1
2
3
4
6
int JDBCVersion = dbmd.getJDBCMajorVersion();
boolean srs = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
if (JDBCVersion > 2 || srs == true)
{
    // scroll, baby, scroll!
}

请求可滚动的ResultSet

假设您的司机表示是(如果没有,您需要一个新的驱动程序或数据库),您可以ResultSet通过传递两个参数来请求滚动Connection.createStatement(),如清单4所示:

清单4.我想滚动!
1
2
3
4
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE,
                       ResultSet.CONCUR_READ_ONLY);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");

调用时必须特别小心, createStatement()因为它的第一个和第二个参数都是ints。任何int值(包括错误的常数值)都可以使用createStatement()

表示“可滚动性”的第一个参数 ResultSet可以是三个接受的值之一:

  • ResultSet.TYPE_FORWARD_ONLY:这是我们知道和喜欢的默认的,firehose风格的游标。
  • ResultSet.TYPE_SCROLL_INSENSITIVE:这样 ResultSet可以进行向后迭代以及转发,但是如果数据库中的数据发生变化,ResultSet 则不会反映出来。此滚动ResultSet可能是最常见的类型。
  • ResultSet.TYPE_SCROLL_SENSITIVE: ResultSet创建的不仅可以允许双向迭代,还可以在数据库中更改数据时提供“实时”视图。

第二个参数在下一个提示中讨论,所以挂起来。

定向滚动

一旦你获得了ResultSet从 Statement,通过它向后滚动只是调用的问题previous(),其落后的推移而不是向前行,因为next()会。或者你可以打电话 first()回到开始 ResultSet,或者打电话last()到最后ResultSet,或者...好吧,你得到这个想法。

relative()absolute()方法也可以是有益的:在第一移动指定的行数(正向如果该值是正的,如果向后的值是负的),而后者移动到指定行中的ResultSet不管光标所在。当然,当前行号可以通过 getRow()

如果您计划在特定方向上进行大量滚动,可以ResultSet通过调用来指定方向 来帮助您setFetchDirection()(A ResultSet将无论其滚动方向如何,但事先知道可以优化其数据检索。)

3.可更新的结果集

JDBC不仅支持双向ResultSets,还支持ResultSet的就地更新这意味着,您ResultSet不需要创建一个新的SQL语句来更改当前存储在数据库中的值,所以您可以修改该值中所保留的值 ,并将自动发送到该行该列的数据库。

请求可更新ResultSet类似于请求可滚动的过程ResultSet实际上,你将使用第二个参数createStatement()而不是指定ResultSet.CONCUR_READ_ONLY第二个参数发送ResultSet.CONCUR_UPDATEABLE,如清单5所示:

清单5.我想要一个可更新的ResultSet
1
2
3
4
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE,
                       ResultSet.CONCUR_UPDATABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");

假设您的驱动程序支持可更新的游标(这是JDBC规范的另一个功能,哪些大多数“真实世界”数据库将支持),您可以ResultSet通过导航到该行并调用其中的一个update...() 方法来更新任何给定值在清单6中)。get...()上的方法ResultSetupdate...()超载在实际列类型ResultSet所以要改变名为“ PRICE” 的浮点列,调用 updateFloat("PRICE")但是,这样做只会更新该值 ResultSet要将值推送到数据库备份,请调用updateRow()cancelRowUpdates()

清单6.更好的方法
1
2
3
4
6
7
8
9
10
11
12
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE,
                       ResultSet.CONCUR_UPDATABLE);
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 4.0不仅仅支持更新。如果用户想要添加一个全新的行,而不是创建一个新的Statement并执行一个INSERT,只需调用moveToInsertRow(),调用update...()每一列,然后调用 insertRow()完成工作。如果未指定列值,则假定为SQL NULLSQLException如果数据库模式不允许NULL该列使用 s),则可能会触发该值

当然,如果ResultSet支持更新一行,它也必须支持删除一个,通过deleteRow()

呵呵,在我忘记之前,所有的这种可滚动性和可更新性同样适用于PreparedStatement(通过将这些参数传递给prepareStatement()方法),Statement由于SQL注入攻击的持续危险,这是非常优于常规的

行列

如果所有这些功能在十年的更好的部分已经在JDBC中,为什么大多数开发人员仍然停留在正向滚动 ResultSet和断开连接的访问​​?

主要原因是可扩展性。将数据库连接保持在最低限度是支持Internet可以为公司网站带来的大量用户的关键。因为滚动和/或更新 ResultSet通常需要开放的网络连接,所以许多开发人员不会(或不能)使用它们。

幸运的是,有一个替代方法可以让你做许多与之相同的事情ResultSet,而不需要保持数据库连接的打开。

在概念上,a Rowset本质上是一个ResultSet,但是允许连接的或断开的模型。所有你需要做的是创建一个Rowset,指向一个ResultSet,当它完成它的填充,使用它ResultSet,如清单7所示:

清单7. Rowset替换ResultSet
1
2
3
4
6
7
8
9
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE,
                       ResultSet.CONCUR_UPDATABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
if (wantsConnected)
    JdbcRowSet rs = new JdbcRowSet(scrollingRS); // connected
else
   CachedRowSetImpl crs = new CachedRowSetImpl();  // disconnected
   cachedRowSet.populate(scrollingRS);

JDBC附带了接口的五个“实现”(意思是扩展接口) RowsetJdbcRowSet是一个连接的 Rowset实现; 其余四个断开连接:

  • CachedRowSet只是一个断开连接 Rowset
  • WebRowSet是一个子类,CachedRowSet它知道如何将其结果转换为XML并重新执行。
  • JoinRowSet是一个WebRowSet也知道如何形成相当于一个SQL JOIN而不必连接回数据库。
  • FilteredRowSet是一个WebRowSet也知道如何进一步过滤掉数据,而不必连接到数据库。

Rowsets是完整的JavaBeans,意味着它们支持侦听器风格的事件,因此Rowset如果需要,可以捕获,检查和执行任何修改事实上, Rowset甚至可以管理完整的行为对数据库,如果有UsernamePassword, URL,和DatasourceName属性进行设置(这意味着它会创建使用连接 DriverManager.getConnection())或它的Datasource 属性集(这是很可能是通过JNDI获得)。然后,您将指定要在Command属性中执行的SQL ,调用 execute()并开始使用结果 - 无需进一步的工作。

Rowset实现通常由JDBC驱动程序提供,因此实际的名称和/或包将取决于您使用的JDBC驱动程序。Rowset实施一直是标准分发的一部分,所以你应该能够创建一个 ...RowsetImpl()和去。(在不太可能的情况下,您的驱动程序不提供,Sun提供了参考实现;请参阅链接的相关主题。)

批量更新

尽管它们有用,但Rowset有时候根本就不能满足您的所有需求,您可能需要回避写入直SQL语句。在这些情况下,特别是当您面临一系列工作时,您可能会感到能够进行批量更新,对于数据库执行多个SQL语句,作为一次网络往返的一部分。

要确定JDBC驱动程序是否支持批量更新,可以快速调用该DatabaseMetaData.supportsBatchUpdates()函数生成一个布尔值来讲述故事。假设批量更新被支持(由任何非标记指示SELECT),排队一次并在爆炸中释放它,如清单8所示:

清单8.让数据库拥有它!
1
2
3
4
6
7
8
9
10
11
12
13
14
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()是必要的,因为默认情况下,驱动程序将尝试提交它所提供的每个语句。除此之外,其余的代码是非常简单的:使用StatementPreparedStatement(而不是调用execute())调用通常的SQL函数executeBatch(),它调用 该调用而不是立即发送它。

当整个语句的混乱准备就绪时,将它们全部在数据库中启动executeBatch(),该数组返回一个整数值数组,每个数组都保持与使用相同的结果 executeUpdate()

如果批处理中的语句失败,如果驱动程序不支持批量更新,或者批处理中的语句返回a ResultSet,则驱动程序将抛出一个BatchUpdateException在某些情况下,驱动程序可能尝试在抛出异常后继续执行语句。JDBC规范不要求特定的行为,所以建议您事先对您的驱动程序进行实验,以便您确切了解其行为。(但是当然你会运行单元测试,所以你会发现这个错误很久以前就是一个问题,对吧?)

结论

作为Java开发的主要部分,JDBC API是每个Java开发人员应该知道的,就像他或她的手背。有趣的是,大多数开发人员多年来一直没有跟上API的增强,所以他们错过了本文中描述的节省时间的技巧。

当然,您是否决定使用JDBC的新功能取决于您。要考虑的一个关键方面将是您正在开展的系统的可扩展性。扩展需求越高,对数据库的使用就越有限制,因此您需要减少网络流量。Rowsets,标量调用和批量更新将成为您的朋友。否则,尝试可滚动和可更新 ResultSet的(不会消耗尽可能多的内存 Rowset),并测量可伸缩性命中。它可能不会像你预期的那样糟糕。

有在程序员这条路上遇到瓶颈的朋友可以点击链接加入群【一java的修养】:点击打开链接一起来提升进步 备注好信息 注! 阿里Java高级大牛直播讲解知识点,分享知识,有五大专题都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!
原创粉丝点击